Tuesday, October 16, 2012

Building the www.brightonsausageco.com website - 15/10/2012

This is a follow up to the 1st in this series: http://iwayneo.blogspot.co.uk/2012/10/building-www.html

Second day; second blast on the site: Adding SpecFlow step definitions and driving the UI through Coypu / Selenium. I'll also take a look into the DB choice.

I've wired up an MVC4 app with Castle Windsor IoC and added references to Nunit, FluentAssertions, Moq and Coypu to the behaviors project and made sure it's all ready to go.

To start we want to drive the whole development from the UI down. To do this I want to have the story I created yesterday turned into steps I can use to drive a regression test through the browser - as it's a browser based app. To begin then, I copy that same story into specflow and generate the step definitions:

SpecFlow:

Gist:

The output from the generate steps command gives us the skeleton with which we can build out the tests for the UI. Let's go through each of the steps and populate them with the stuff we need:

[Given(@"I am an admin user and I have logged in")]

Considering we aren't going to bother with security and logining in yet I can leave this one blank for now. There's something you can do to tell SpecFlow that it's not going to be implemented yet but I don't have internet here so can't google it. I'll come back to that when I'm on the train tomorrow morning. For now I can leave a //TODO: remark and it will be added to the task list in Visual Studio.

[Given(@"I have navigated to the manage categories screen")]

This is where we can start adding actual code! To drive the user we use a thing called BrowserSession - this is a Coypu thing. You tell it how to drive the browser and it takes care of pushing stuff to selenium. To get one I add the following to the specflow steps file:

There's some stuff going on here that works the way specflow expects it to work - notedly the FeatureContext stuff. Maybe chat about that more later - if not - google it ;)

With that we can start to drive the UI (note I have set up VS to fire the site up using a static port - i could have added a hosts file entry to resolve some DNS and set up IIS so that it could handle it):

Now, if I go back to the specflow file and hit ctrl>t+r (resharper shortcut for run tests) I see that firefox is spun up to the URL we specified and gets a 404. We have a failing step! The reason is obvs because there is nothing in the site to resolve that URL. To make this pass we need to tell the server how to handle requests on that URL. The way MVC knows how to handle requests and forward them to controllers / actions is by it's routing mechanism. So the very first step we need to make this step pass is to configure the routing. To do this in a TDD way we need to go down a level and add a behavior to check that the routing is configured.

To do this I need a place to hold the routing behaviors. I'll add a new folder called Routing to the AdminRoutingBehaviors.cs file into that folder.

Emergent design

At this point I'll introduce a small helper which will take a URL and return the route data:

So... emergent design: This is where we allow the flow of the test to really drive the development and design of the software. Here's some info on AgileSherpa on the topic. Here's some info on the IBM website about evolving architecture and emergent design

I want to make sure the URL "admin/categories" can be handled by the routing system. A lame but sufficient name might be: When_navigating_to_manage_categories_url_result_is_correct. It's not that great but it will do for now :)

Starting with what we know about this behavior - we want to have some result - and it will be the result we expect. I.e. it will be from the admin controller and it will be the categories action. That's enough to get us started. The very first line we write might be (using fluent assertions):

action.Should().Be(expectedAction);

Now this is going to be glowing red all over VS as we have no action and whats more, trying to compare nothing to expectedAction which also doesn't exist is just never going to work.

Move the cursor to expectedAction and hit ctrl>space to get resharper to offer contextual help and select "Create local variable expectedAction". Make it a const string and give it a value (you should keep using resharper alt+enter to use contextual help to get to this - try not to use the mouse):

Now hover over the result declaration and do the same. This time make it equal to the return value of GetRouteData:

We now have another variable that needs declaring: url. Move the cursor over that and hit alt enter. This time it's going to be a const string again:

Add to that the test for checking the controller part of the url is correct and you should have:

The test is complete in that it will test what we want to test - but it still won't pass - hit ctrl>t+r and you'll see red. To get this to actually pass we need to implement the code in the RouteConfig class in the Mvc project. The route we want looks like:

Heading back over to our specflow file and spinning up the tests still results in a 404! Obviously because sure as shit we've not got a controller or corresponding action to route the request to! To get that done I'm going to need more tests. I'll add a Controllers folder to the behaviors project and inside that I'll add an AdminControllerBehaviors.cs file. I've come up with another snazzy method name for the first test in this class: When_requesting_the_categories_admin_page_result_is_correct

Let's start with the result again and work out from there:

[emersive design video]

Heading back over to the specflow test we're going to find that it still doesn't let us get valid because there's no view to return. We can add a blank view now. Now the specflow test should be showing yellow. This was only a [Given] part of the test - the test setup for the feature if you like. The next step is to implement the first part of the scenario:

Maps to the following step:

To speed this up I'm going to populate all of the steps in this feature and then I can go through and implement them. I end up with the following steps: