Monthly Archives: February 2008

NSpecify => RSpec… well closer anyway

So as I’ve been doing some Ruby+Rails work recently, I’ve become very enamoured of Rspec, it’s ability to turn plain text tests into runable tests is awesome.

I love that I can run this as an automated test:

Story: News Page

As a visitor
I Want to go to the news site
So that I can keep up with all the latest news

Scenario: A visitor goes to the news site and one post exists
Given a news_post named ‘Test News Post’ exists with content ‘This is a news post created for the scenario’
When visitor goes to the home page
And clicks on ‘News’

Then visitor should see the news show page
And page should include a notice ‘Test News Post’
And page should have the news post’s name, and content

it’s really awesome to see this kind of feature, and makes testing in C# pale in comparison… however, there is also the syntax of RSpec that I’m getting used to that allows you to write specifications that look like this:

require File.dirname(__FILE__) '/../spec_helper'describe User, "A new user" do  before(:each) do    @user = User.new  end  it "should be able to retain email address" do    email = "email"    @user.email = email    @user.email.should be_equal(email)  end  it "should be able to retain password" do    password = "password"    @user.password = password    @user.password.should be_equal(password)  end  it "should not be valid for save" do    @user.should_not be_valid  end  it "should require an email address" do    login = "login"    password = "password"    @user.password = password    @user.password_confirmation = password    @user.login = login    @user.should_not be_valid  end  it "should require a password" do    login = "login"    email = "email"    password_confirm = "password"    @user.email = email    @user.password_confirmation = password_confirm    @user.login = login    @user.should_not be_valid  end  it "should require a password confirmation" do    login = "login"    email = "email"    password = "password"    @user.email = email    @user.login = login    @user.password = password    @user.should_not be_valid  end  it "should require a login" do    email = "email"    password = "password"    @user.email = email    @user.password = password    @user.password_confirmation = password    @user.should_not be_valid  end  it "should be valid if login, email password and password confirmation are provided" do    login = "login"    email = "email"    password = "password"    @user.password = password    @user.password_confirmation = password    @user.login=login    @user.email = email    @user.should be_valid  end  it "should only be valid if password and password confirmation match" do    login = "login"    email = "email"    password = "password"    @user.password = password    @user.password_confirmation = password "not the same"    @user.login=login    @user.email = email    @user.should_not be_valid  end  it "should not be valid if password and password confirmation are under 4 characters" do    login = "login"    email = "email"    password = "pas"    @user.password = password    @user.password_confirmation = password    @user.login=login    @user.email = email    @user.should_not be_valid  end  it "shouldn't be valid if the same login name and email exist already" do    login = "login"    email = "email"    password = "password"    @user.password = password    @user.password_confirmation = password    @user.login=login    @user.email = email    @user.save    @user = User.new    @user.password = password    @user.password_confirmation = password    @user.login=login    @user.email = email    @user.should_not be_valid  end  after(:all) do    User.delete_all  endenddescribe User, "A saved user" do  before(:all) do    @initial_password = "password"    login = "login"    user = User.new(:login => login,:password=>@initial_password,:password_confirmation=>@initial_password,:email=>"email")    user.save    @user = User.find_by_login(login)  end  it "should encrypt the password" do    @user.password.should_not equal(@initial_password)  end  after(:all) do    @user.destroy  endend

which gives you an output like this:

User A saved user
- should encrypt the password

User A new user
- shouldn’t be valid if the same login name and email exist already
- should not be valid if password and password confirmation are under 4 characters
- should only be valid if password and password confirmation match
- should be valid if login, email password and password confirmation are provided
- should require a login
- should require a password confirmation
- should require a password
- should require an email address
- should not be valid for save
- should be able to retain password
- should be able to retain email address

Finished in 0.594 seconds

12 examples, 0 failures

It would be great to have this in my C# world as well, but hey I’ve got to work within the boundaries of NUnit, due to the fact that there are other members on the team too and they aren’t as grounded in BDD and TDD as I am.

There is one thing however that I did want to add, and that’s syntax that makes people get away from the idea that Unit tests always have to be about specifying code, and can be more about specifying the behaviour.

I went to work on NSpecify, as the NUnit add-on is easy and seamless to install, everyone on my team can run it, as it’s in with their code under source control (this allows Continuous integration without too many hassles of versioning and setup etc.).

As such I modified NSpecify to add in [Context] to define a “test case” [BeforeAll] to define a “TestFixtureSetUp”, [BeforeEach] to define a “SetUp” [AfterAll] to define a “TestFixtureTearDown” and [AfterEach] to define a “TearDown”.

These are all synonyms so they can be used interchangeably with the existing attributes.

Also I added the ability to have more complex collection matching.

want to specify that a collection of users has a user with a certain Name attribute?

easy:

Specify.That(UserCollection).Must.Contain((User user)=>user.Name==expectedName);

Also I added the ability to define expected exceptions on Method calls, but based on Lambdas (as the above is)

Specify.That(() => { MethodThatThrowsACustomException(); }).Must.Be.Disallowed(typeof(CustomException)).WithMessage(%u201CThis method is disallowed in this context%u201D);

the with message is optional, without it the specification will just match the type of exception without much more.

Next I’m going to put in some negative collection matching like above, and also add in the NUnit.Spec extension methods, but make them throw specification exceptions, as just using them out of the box means that the test shows green in NUnit even thought there’s an exception thrown.

So at the moment I can do this:

[Context]public class SaveCustomerFunctionalityWithAlternativeSyntax{    private Customer customer = null;        [BeforeAll]    public void FunctionalitySetup()    {    }        [BeforeEach]    public void SpecificationSetup()    {        customer = new Customer(123);    }        [Specification("The customer id's should be equal")]    public void LoadCustomer()    {        Specify.That(customer.Id).Must.Equal(123, "The customer id's aren't equal");    }    [Specification("The customer id's should be equal")]    public void LoadCustomerFailed()    {        Specify.That(customer.Id).Must.Not.Equal(125, "The customer id's aren't equal");    }    [AfterEach]    public void SpecificationCleanup()    {        customer = null;    }    [AfterAll]    public void FunctionalityCleanup()    {    }}

but I want to get to the point where I can do this

[Context]public class WhenANewUserIsCreated{    User user;    [BeforeEach]    public void CreateTheNewUser()    {        user = new User();    }    [Specification]    public void TheUserShouldBeMarkedAsNew()    {        user.IsNew.Must.Be.True();    }    [Specification]    public void TheUserShouldNotBeAllowedToBeSaved()    {        user.Must.Not.Allow(user.Save).With.Error("A user must have a name before being saved");    }}

Attached is the patch:

Context+MethodDissalowed+ComplexMatchingDelegates.patch

Just be aware of a couple of things


  • I’ve updated NUnit to 2.4.6
  • The lambda expressions above rely on .net 3.5 so VS2008 is a must.

Enjoy

Technorati Tags: ,,,,

Tagged

kicking and screaming

Sometimes change can be very difficult, most peoples instinct is to stay with the status quo and not disrupt the flow, however there are imperative changes that must happen on any software teams and fighting them is like taking a proverbial piss in the wind.

Right now I’m trying to help people around me facilitate change, and one of the key instruments I use is the retrospective.

First lets get to grips with what the goals of a retrospective should be. I once had a mentor (in Cresta) called David Evans, who was awesome at facilitating retrospectives, so the first time I wanted to run one I asked for some help. There are a number of guides and helps that I (and he) uses but one quote from his original feedback really sums up what the goals of such a process are.

Remember that a retrospective is about getting the team to pause and think about what they have done, and what they might be able to do better. These techniques are just prompts for getting that thought process started. Beer helps too.

The first step in getting value from a retrospective is to get everyone involved, if there are members of the team who are disinterested try and find out why, often it can be because of fear of change, it’s important to help them realise that without change the product will either a) become impossible to maintain or b) other members of the team will be disenfranchised, as they are stuck inside broken processes that cannot be changed. The last step is to tell them that everyone else will be coming along and if they don’t their voice/values won’t be heard.

That aside it’s still very important to make the retrospective process as casual as possible. This is not a process about blame or individual responsibility rather it’s about honesty and ownership. If we want to make the processes as great as possible for everyone, then everyone needs to be part of the process.

So this is the formula that I use for getting a retrospective together.

1) The score:

It’s important that certain parts of peoples mood and feeling about progress can be captured in as easy a way as possible, therefor I pick 10 ish agile processes (my favourite are the crystal clear methodologies introduced to me by John Rusk) the methodologies or processes should be definable while still being applicable to all teams (QA, Dev, Design)

at a push you could just list them as

Communication
Focus
Productivity
Quality

but I prefer more fine grained processes

each quality is given a success rating of

Success   Meaning
:-( (          We do this very badly, or not at all
:-(            We are not doing this well
:-|            We are adequate at this
:-)            We are quite good at this
:-) )          We do this very well

And then an impact rating

Impact  Meaning
0              This has no effect on our success
1              Has some impact on this project
2              Important to this project
3              Critical success factor for this project

The results can then be collated and averaged, and a score can be worked out of success * impact where success runs from -2 to 2

if the result is less than 0 the process isn’t working and has an impact, -6 is the worst score

if the result is higher than 0 the process is working and has an impact 6 being the best score

It’s then good to focus on the extremes, remembering to remind people of the positives.

2) The Actions:

Finally is the open ended aspect, ask people to come up with at least three positive points to the way current work is happening, three things we need to try and change, and three things that we need to work out how to do.

These points then get brainstormed an written up, these become the action points between retrospectives.

I can’t stress how useful it is to get time set aside to do retrospectives, I’ve seen too many teams keep plodding on without taking time to make sure they’re travelling with all the write equipment.


// –>
Tagged
Follow

Get every new post delivered to your Inbox.