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'which gives you an output like this: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.shouldnot bevalid end
it "should require an email address" do login = "login" password = "password" @user.password = password @user.passwordconfirmation = password @user.login = login @user.shouldnot be_valid end
it "should require a password" do login = "login" email = "email" passwordconfirm = "password" @user.email = email @user.passwordconfirmation = passwordconfirm @user.login = login @user.shouldnot 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.shouldnot bevalid end
it "should require a login" do email = "email" password = "password" @user.email = email @user.password = password @user.passwordconfirmation = password @user.shouldnot 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.passwordconfirmation = password @user.login=login @user.email = email @user.should bevalid end
it "should only be valid if password and password confirmation match" do login = "login" email = "email" password = "password" @user.password = password @user.passwordconfirmation = password "not the same" @user.login=login @user.email = email @user.shouldnot 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.passwordconfirmation = password @user.login=login @user.email = email @user.shouldnot 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.passwordconfirmation = password @user.login=login @user.email = email @user.save @user = User.new @user.password = password @user.passwordconfirmation = password @user.login=login @user.email = email @user.shouldnot bevalid end
after(:all) do User.delete_all end
end
describe User, "A saved user" do before(:all) do @initialpassword = "password" login = "login" user = User.new(:login => login,:password=>@initialpassword,:passwordconfirmation=>@initialpassword,:email=>"email") user.save @user = User.findbylogin(login) end
it "should encrypt the password" do @user.password.shouldnot equal(@initialpassword) end
after(:all) do @user.destroy end end
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.
Technorati Tags: NSpecify,BDD,TDD,RSpec,C#
Sphere: Related Content-
maruismarais