Getting started on project Rolbot Arena. #blog #fsharp #tdd #bdd Well actually the BDD comes before the TDD. First there's Domain Driven Behaviour then there's Behaviour Driven Design, and that leads to Design Driven Tests which are needed for Test Driven Development. #DDBDDDTDD
Reading a bit more about TDD I realised it would be nice to have a code coverage tool. There's some that cost $$ and even some automated integrated built tools that cost $$$ but there's also PrestoCoverage. Unfortuantely it doesn't trigger automatically when tests are run in VS2009. And the manual PS script to run Coverlet (which actually generates the coverage information) isn't working... because it requires dotnet core version 2.2. So I'm writing this bit while it downloads...
Okay I can get coverage (after some fiddling and reading) or at least I can get the coverage data to generate but not to display in VS19. There's external tools to produce html files to view it but I'd kinda like it in-editor. Hmm there's some great plugins... for VSCode. Okay I should be using that... (and so begins a process a whole different set of plugins and figuring out how to use VSCode.)
After some fiddling around I have some example projects building/running in VS code, and I can generate coverage reports and also see coverage red/green gutters. Which isn't as fancy as that other plugin which worked with older Visual Studio, but it'll do. Also, VSCode is (after restarting it) also picking up on Expecto tests, so I could use those. The frustrating problem about the test explorer plugin is that 'click a test to jump to it in the code' doesn't work in F#. But the test errors do show up in the code. They can't be dismissed though, and can get out of date. I'll have to see how it goes...
Oh and I want to try out a plugin for writing higher level tests in the ghergin language which seems like an interesting way of abstracting high level behaviour descriptions so they're not coupled directly to implementation.
Much later: coverage was briefly working in VSCode but now it isn't (though the console logs say it is). So I'm not getting coverage whether I'm using VS or VSC.
Visual Studio has test discovery and execution working fine (including Expecto now that I have everything installed properly via the "dotnet new expecto" template). The big plus for VS is that I can actually jump from a failed test to the correct location in the code.
VSCode is able to mark failed tests as code errors (including displaying the error output at the failure site) and has working test discovery and execution - except for when it hangs - and sometimes there's extra duplicate list items. It also seems generally slower to run tests. And a lot of it has a hacked-together feeling. Plus I was having issues with debugging and I just remembered I never got around to trying that again. So I'm switching back to Visual Studio for now. I may reconsider if the VSCode Test Explorer plugin gets full F# support (i.e. jumping to tests and showing the test codelenses with all the helpful pass/fail/run displays and buttons.)
While VS doesn't have the ability to make test failures show as an error, it's not much of a loss as 'jump from test failure to the code' is usually far more convenient.
As I hinted at before, the purpose of Test Driven Design isn't just to make us write tests for all our code, but to think about the requirements before we write code. So I'm not just thinking up tests, but considering the requirements and making appropriate tests from them. So, let's go over those requirements in more detail.
But I'm not considering the requirements in order to jump straight to writing unit tests. No, first I need to specify behavours. BDD is the outer loop and TDD is the inner loop. I need to learn about the requirements to know what behavious needs to be present in the system. After confirming that such a behaviour is not yet present (via a failing behaviour test) I can consider what the system needs to actually perform to exhibit some of that behaviour and I can write a unit test.
I found some slides showing and explaining the two loops of BDD and TDD here
Great, let's actually get started! What's the first behaviour I want the system to exhibit?
Wait, what system? There isn't any 'system'. So the existence of something a user can interact with should be required early on. And in what fashion would a user expect to initially interact with a 'robot fight arena simulator' such as I am creating? I would expect some sort of title screen which is followed by a menu screen listing options for further interaction.
No, let's consider a new user. A new user would have no saved data to load and thus doesn't have any options. (an options menu and a method of exiting should be available but aren't necessarily a menu option and should be the subject of future tests. Hmm... Mental Note: some way of configuring options and some way of exiting should be the topic of future behaviour tests.) So I could send a new user directly into an arena-bot fight.
But that's after a title screen. And probably some sort of loading screen. Or rather, a title screen which is shown while loading occurs. By 'loading' I mean initialisation which is required before robot-battling can occur. There isn't any such thing yet since there's nothing to initialise, but if there was, no prospective robot-battler would want the interface to lock up without any feedback while they were forced to wait. But I'm probably getting ahead of myself. A loading screen applies to all users who attempt to start robot-battling, but I was thinking particularly about the case of a new user.
Feature: Opening the Arena
In order to battle robots
Users of Rolbots should be able to
Open the Arena
Scenario: Immediate start for a new user
Given a new user who is opening Rolbots for the first time
When the user leaves the title screen
Then the arena begins to load without showing the main menu
That seems straightforward enough, but I passed through quite a few versions before settling on that one. I might even further alter it. I did start writing another scenario for a user currently at the main menu, but I realised I don't know what menu options I want to be available yet or whether any will go directly to the arena, so that can be a problem for future me.
An explanation for those who are not familar with BDD: the above is a feature in Gherkin format. Currently there's only one scenario for this feature, but I'll add more as I progress. Each feature will be in a separate feature file. The words 'Given', 'When', and 'Then' are important keywords. 'Then' establishes an original state. 'When' defines an action the user performs to initiate a change. 'Then' introduces the description of what occurs as a result.
An important reminder: this will be used to test that the feature is available as described and that the expected behaviour is present. But behaviour or acceptance tests shouldn't be relied upon for determining basic program correctness. That's the job of unit tests. If your basic program correctness is only established by integrated tests - tests which can fail for multiple separate reasons - then you're gonna have a bad time. (As in, an increasing number of integrated tests which don't actually help you prove your system is correct and just make everything more tightly coupled and make it harder to write unit tests and keep fooling you into the necessity of writing even more integrated tests that suck you into a vertex of doom as development grinds to a halt. End-to-end testing and integration testing still have their place of course, but you don't need to attach everything together to find integration problems if you have appropriate contract tests in place...)
I've found a lightweight framework for .NET called TickSpec which I'll use to make the feature files executable as tests with F# code backing them. This means I'll soon have a failing behaviour test! Then I can create my first unit test and then implementation of Real Developer Code can begin!
I'll put my behaviour tests (and thus my feature files) in a separate project to my unit tests. The unit tests will be in a separate project to the code they're testing. Also I'll be keeping all the domain stuff separate from the controller (which will be an intermediate between users and the domain) so I think those will be separate projects to each other (and will have their own testing projects, I expect). (side note: I checked with some quick Googling and Stack Overflow reading to conform that multiple .NET assemblies shouldn't incur an ongoing performance cost, in case that matters.) I'm thinking of something like a hexagonal architecture. There's gonna have to be a persistence layer at some point, which my Domain shouldn't need to care or know about.
So all I need to do is make a new .NET project with the xUnit template, add the TickSpec package, realise the updated version of TickSpec that works for .NET Core is a prerelease version that NuGet won't install unless I specifically tell it to, wonder how to get TickSpec to actually work, realise I have to dig in to the example code in the TickSpec github to figure out how the plumbing works, set my feature file to be an embedded resource, not notice that when I change the build action of a file in Visual Studio it automatically jumps to the next file until after half an hour of wondering why it's bizarrely ignoring the contents of my program.fs
After an embarassingly long time I realised that I'd managed to set the F# source file containing my step definitions to Embedded Resource too. sigh
Now I have some empty step definitions to fill out!
let [<Given>]``a new user who is opening Rolbots for the first time`` () = ()
let [<When>]``the user leaves the title screen`` () = ()
let [<Then>]``the arena begins to load without showing the main menu`` () = ()
These are functions which take () as a parameter (known as 'unit', it's like 'void' but a bit more quantifiable.) and also return (). Actually, () is the entirety of the function body for each one. I need to replace that with something.
I don't currently have any way of representing a user, new or otherwise. (Actually I don't have any way of representing anything.) Thus I'll assume all users are new by default (that's what every program has to do actually. There's no data before the program is run for the first time, which implies the user is new.) So all I need to do here is have the program in a 'starting up' state.
Now that I think about it some more, being 'at the title screen' is more clearly defined as an initial state, compared to 'opening the program'. So I'll change it all around again. But first, it's time I added the project to source control. (For unknown reasons, Git or Visual Studio complained about 'projects outside my solution directory' which wouldn't be added to source control, but this didn't seem to have any effect.)
Now I have the following Title.feature and TitleSteps.fs:
Feature: Title Screen
In order to battle robots
Users of Rolbots should be able to
Leave the title screen
Scenario: Immediate start for a new user
Given a new user who is at the title screen
When the user leaves the title screen
Then the arena begins to load without showing the main menu
module TitleStepDefinition
open TickSpec
let [<Given>]``a new user who is at the title screen`` () = ()
let [<When>]``the user leaves the title screen`` () = ()
let [<Then>]``the arena begins to load without showing the main menu`` () = ()
This makes more sense, as Open is more an action than a feature.
Currently the test passes, because it is empty. Now it's time to change that. In the next part... which is here