bgeek.net

CodeCamp: in need input

Posted by Owen Evans on Monday, August 18th, 2008

Ok so this is a bit of a plea.
I’ve a week or so to work on my CodeCamp presentation “MVC and Me” (ok so would have been cooler to link to flight of the navigator but that’s too hard for me to pull off) and I’m still trying to gather as many ideas about what [...]

continue reading

Split And Sprint

Posted by Owen Evans on Thursday, August 14th, 2008

In a forthcoming book (before you ask I’ve no idea what the title is) there’s a little case study about the first project i ever truly worked on as a developer. The project and the team have some of my fondest memories, and I’d want to work with any of the people in the small [...]

continue reading

Post 300

Posted by Owen Evans on Friday, August 8th, 2008

Ok I just had to cheat an make up a reason to post. But this is my 300th post, this blog has been going for more than 5 years (25 Feb 2003 was my first post, those heady days of university). that’s 1992 days, so an average of one post every 6.64 days. (my goodness [...]

continue reading

NSpecify => RSpec… well closer anyway

Posted by Owen Evans on Thursday, February 14th, 2008

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
  end

end

describe 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
  end
end

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: ,,,,

Sphere: Related Content

Posted in: [||||].

discussion by DISQUS

Add New Comment

Viewing 1 Comment

    • ^
    • v
    Firstly, I'd like to thank you for your contributions to the NSpecify project. I really like the Context, BeforeEach, etc synonyms that you've added. The lambda support you've added is also pretty cool.

    In the future I plan to introduce extension methods to allow specifying on the object itself. I actually did a prove of concept a while back. The problems I found was that you can't have the following syntax

    user.Must.Not.Allow(user.Save).With.Error("A user must have a name before being saved");

    but rather as follows

    user.Must().Not.Allow(user.Save).With.Error("A user must have a name before being saved");

    which I don't mind.

    I'm working on a Auto Test library and Notifier application. I have an almost stable version and if you are interested, let me know. With this application I plan to have the same sort of specdoc to be displayed (as in rspec on TextMate). Maybe in the browser, I'll see when I get there...

    My only problem at the moment is I'm stuck in .NET 2.0 land in my day job, so my understanding and exploration of 3.5 features suffers at the moment...
blog comments powered by Disqus