Earlier this year, I wrote this blog post about exploring Behavior-Driven Development as a better way of doing Test-Driven Development. In this post, I spoke about how to organize unit tests by their context and about how to apply a fluent language approach for naming these contexts/specifications. Here is how the example code of the context/specification from that post looks like:
[TestFixture] [Category("RecordServiceTestFixture")] public class When_retrieving_all_records_for_a_specific_genre : Specification<RecordService> { private const Int64 GenreId = 12; private Genre _genre; private IEnumerable<Record> _records; protected override void Before_each_specification() { _genre = new Genre(); _records = new List<Record>(); SetupResult.For(MockGenreRepository.FindBy(0)) .IgnoreArguments() .Return(_genre); SetupResult.For( MockRecordRepository.GetAllRecordsFor(null)) .IgnoreArguments() .Return(_records); } [Test] public void Then_find_the_genre_for_the_specified_id() { BackToRecord(MockGenreRepository); using(Record) { Expect.Call(MockGenreRepository.FindBy(GenreId)) .Return(_genre); } using(Playback) { CreateSubject().GetAllRecordsForGenre(GenreId); } } [Test] public void Then_find_all_records_for_a_specific_genre() { BackToRecord(MockRecordRepository); using(Record) { Expect.Call( MockRecordRepository.GetAllRecordsFor(_genre)) .Return(_records); } using(Playback) { CreateSubject().GetAllRecordsForGenre(GenreId); } } [Test] public void Should_return_a_list_of_records() { using(PlaybackOnly) { IEnumerable<Record> records = CreateSubject().GetAllRecordsForGenre(GenreId); Assert.That(records, Is.SameAs(_records)); } } private IGenreRepository MockGenreRepository { get { return Mock<IGenreRepository>(); } } private IRecordRepository MockRecordRepository { get { return Mock<IRecordRepository>(); } } }
I’ve been practicing this Context/Specification style of BDD since I wrote that post and I learned a couple of things since then. The code in this simple example still uses the Record/Playback plumbing that was then required by Rhino Mocks. The latest version of Rhino Mocks now supports the new AAA (Arrange, Act, Assert) syntax. I tried to make the code of this example a bit more easier to read by removing the noise of the Record/Playback syntax and some refactoring. Here is what I came up with:
[TestFixture] [Category("RecordServiceTestFixture")] public class When_retrieving_all_records_for_a_specific_genre : AutoInstanceSpecification<RecordService> { protected override void Establish_context() { genre = new Genre(); records = new List<Record>(); MockGenreRepository.Expect(genreRepository => genreRepository.FindBy(GenreId)) .Return(genre); MockRecordRepository.Expect(recordRepository => recordRepository.GetAllRecordsFor(genre)) .Return(records); } protected override void Because() { result = SUT.GetAllRecordsForGenre(GenreId); } [Test] public void Then_find_the_genre_for_the_specified_id() { MockGenreRepository.AssertWasCalled(repository => repository.FindBy(GenreId)); } [Test] public void Then_find_all_records_for_a_specific_genre() { MockRecordRepository.AssertWasCalled(repository => repository.GetAllRecordsFor(genre)); } [Test] public void Should_return_a_list_of_records() { Assert.That(result, Is.SameAs(records)); } private IGenreRepository MockGenreRepository { get { return Mock<IGenreRepository>(); } } private IRecordRepository MockRecordRepository { get { return Mock<IRecordRepository>(); } } private const Int64 GenreId = 12; private Genre genre; private IEnumerable<Record> records; private IEnumerable<Record> result; }
As you can see, the test cases are now reduced to a single line of code. The only thing that remains are the asserts itself. The actual call to the subject-under-test is nicely tucked away in the Because method, which is executed before each test case. This is something I’ve picked up by reading this article from Scott Bellware (which is highly recommended!!) and by looking at SpecUnit. Setting up the context is still done by the Establish_context method and the AutoMockingContainer is still used by the base class.
I’ve also split up the base test fixture of my previous post into three different classes:
public abstract class Specification { [SetUp] public virtual void BaseSetUp() { Establish_context(); Initialize_subject_under_test(); Because(); } [TearDown] public virtual void BaseTearDown() { Dispose_context(); } protected virtual void Establish_context() {} protected virtual void Initialize_subject_under_test() { } protected virtual void Because() {} protected virtual void Dispose_context() {} } public abstract class InstanceSpecification<TSubjectUnderTest> : Specification { protected override void Initialize_subject_under_test() { SUT = Create_subject_under_test(); } protected abstract TSubjectUnderTest Create_subject_under_test(); protected TSubjectUnderTest SUT { get; private set; } } public abstract class AutoInstanceSpecification<TSubject> : InstanceSpecification<TSubject> { private MockRepository _mockRepository; private AutoMockingContainer _autoMockingContainer; protected AutoMockingContainer AutoMockingContainer { get { return _autoMockingContainer; } } protected MockRepository MockRepository { get { return _mockRepository; } } protected override TSubject Create_subject_under_test() { return _autoMockingContainer.Create<TSubject>(); } protected TMock Mock<TMock>() where TMock : class { return GetDependency<TMock>(); } protected TStub Stub<TStub>() where TStub : class { _autoMockingContainer.Mark<TStub>().Stubbed(); return GetDependency<TStub>(); } private TDependency GetDependency<TDependency>() where TDependency : class { var dependency = _autoMockingContainer .Get<TDependency>(); if(false == MockRepository.IsInReplayMode(dependency)) { MockRepository.Replay(dependency); } return dependency; } public override void BaseSetUp() { _mockRepository = new MockRepository(); _autoMockingContainer = new AutoMockingContainer(_mockRepository); _autoMockingContainer.Initialize(); base.BaseSetUp(); } public override void BaseTearDown() { base.BaseTearDown(); _autoMockingContainer = null; _mockRepository = null; } }
This has the advantage that now all test fixtures who don’t need to any mock objects can be derived from the InstanceSpecification base class:
[TestFixture] [Category("RecordTestFixture")] public class When_adding_a_track_to_a_record : InstanceSpecification<Record> { protected override void Because() { SUT.AddTrack(TrackName); } [Test] public void Then_the_record_should_have_the_specified_track() { Assert.That(SUT.HasTrack(TrackName)); } protected override Record Create_subject_under_test() { return new Record(RecordName); } private const string RecordName = "Homework"; private const string TrackName = "Rollin' & Scratchin'"; }
So far, I like this way of using BDD style specifications but I would love to hear any thoughts, remarks, flames, etc. … . I guess that this topic is still somewhat of a moving target, so I’m eager learn and further refine my approach.
Till next time
We’re doing something very similar. Our team just decided the introduction of AAA style tests was a step forward. See
http://geekswithblogs.net/alternativedotnet/archive/2008/10/27/126254.aspx