Exploring BDD style specifications as a better TDD
February 3, 2008You might think that I suffer from a severe case of acronymitis judging from the title of this post, but the only thing I suffer from right now is a terrible cold. Anyway, I'm currently looking into how a Behavior-Driven Development approach can solve some issues I have with writing unit tests. I first heard about BDD about a year ago, but I never really paid any attention to it after I've read the following posts about BDD style naming of context/specification:
- Approaching BDD
- Getting started with BDD style Context/Specification base naming
- Attempting to Demystify Behavior Driven Development
There's more to BDD than just its style of naming tests, but I don't have a full picture of it yet. Heck, I'm not even sure if I agree with the whole approach that BDD has to offer. Anyhow, I did a little spike in order to get me started and I was very pleased with the results. I first made up a very simple user story that I would implement for this sample.
As a music fanatic I need to be able to
view which records I own for a specific genre,
so that I can make a choice of what I want to listen.
I first implemented this user story like I would normally do in a test-first way using the AutoMockingContainer I described in a previous post.
Unit tests
[TestFixture]
public class RecordServiceTestFixture
: AutoMockingTestFixture<RecordService>
{
private const Int64 GenreId = 12;
[Test]
public void GetAllRecordsForGenre_
WithGenreIdentifier_
VerifyInteractionWithGenreRepository()
{
using(Record)
{
Expect.Call(MockGenreRepository.FindBy(GenreId))
.Return(CreateGenre());
}
using(Playback)
{
CreateSubject().GetAllRecordsForGenre(GenreId);
}
}
[Test]
public void GetAllRecordsForGenre_
WithGenreIdentifier_
VerifyInteractionWithRecordRepository()
{
using(Record)
{
Genre genre = CreateGenre();
SetupResult.For(MockGenreRepository.FindBy(0))
.IgnoreArguments()
.Return(genre);
Expect.Call(
MockRecordRepository.GetAllRecordsFor(genre))
.Return(CreateListOfRecords());
}
using(Playback)
{
CreateSubject().GetAllRecordsForGenre(GenreId);
}
}
[Test]
public void GetAllRecordsForGenre_
WithGenreIdentifier_
VerifyListOfRecordObjectsIsReturned()
{
IEnumerable<Record> expectedRecords =
CreateListOfRecords();
Genre genre = new Genre();
SetupResult.For(MockGenreRepository.FindBy(0))
.IgnoreArguments()
.Return(genre);
SetupResult.For(
MockRecordRepository.GetAllRecordsFor(null))
.IgnoreArguments()
.Return(expectedRecords);
using (PlaybackOnly)
{
IEnumerable<Record> records =
CreateSubject().GetAllRecordsForGenre(GenreId);
Assert.That(records, Is.SameAs(expectedRecords));
}
}
private static Genre CreateGenre()
{
return new Genre();
}
private static IEnumerable<Record> CreateListOfRecords()
{
return new List<Record>();
}
private IGenreRepository MockGenreRepository
{
get { return Mock<IGenreRepository>(); }
}
private IRecordRepository MockRecordRepository
{
get { return Mock<IRecordRepository>(); }
}
}
(I needed to format the names of the unit tests. My apologies for that.)
Subject-under-test
public class RecordService
{
private readonly IGenreRepository _genreRepository;
private readonly IRecordRepository _recordRepository;
public RecordService(IGenreRepository genreRepository,
IRecordRepository recordRepository)
{
_genreRepository = genreRepository;
_recordRepository = recordRepository;
}
public IEnumerable<Record> GetAllRecordsForGenre(
Int64 genreId)
{
Genre genre = _genreRepository.FindBy(genreId);
IEnumerable<Record> records =
_recordRepository.GetAllRecordsFor(genre);
return records;
}
}
Take a look at the second and third unit test. Did you notice that these tests contain calls to SetupResult.For. These calls are needed in order to get our tests up-and-running. These stubs ensure that I get to the particular point in my code that I want to test. But still, they feel like noise to me. They blur the test though it is a necessary evil.
With this approach, I also have one test fixture for every subject under test. When my subject has more than one method, all the tests for these methods are assembled in this one test fixture.
With the BDD context/specification naming style, every context is now mapped to one test fixture. So, if I have multiple methods on my subject under test, then I'll have multiple test fixtures. The test cases are the specifications itself.
A fluent language approach is used for naming these contexts/ specifications. See for yourself.
[TestFixture]
[Category("RecordServiceTestFixture2")]
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>(); }
}
}
This looks a lot cleaner to me. The name of the test fixture now describes the context for the tests. If I have multiple contexts, then I have multiple test fixtures. I still put all these contexts in one code file, which is named
The test cases itself describe the actual behavior of of my subject-under-test. With this approach I was able to move the setup code to the Before_each_specification method of the context. I made some minor changes to the code of my base test fixture (which is now called Specification) that leverages the AutoMockingContainer.
public abstract class Specification<TSubject>
where TSubject : class
{
private MockRepository _mockRepository;
private AutoMockingContainer _autoMockingContainer;
protected AutoMockingContainer AutoMockingContainer
{
get { return _autoMockingContainer; }
}
protected MockRepository MockRepository
{
get { return _mockRepository; }
}
protected IDisposable Playback
{
get { return MockRepository.Playback(); }
}
protected IDisposable PlaybackOnly
{
get
{
using (Record)
{ }
return Playback;
}
}
protected IDisposable Record
{
get { return MockRepository.Record(); }
}
protected TSubject CreateSubject()
{
return _autoMockingContainer.Create<TSubject>();
}
protected TDependecy Mock<TDependecy>()
where TDependecy : class
{
return _autoMockingContainer.Get<TDependecy>();
}
protected TDependecy Stub<TDependecy>()
where TDependecy : class
{
_autoMockingContainer.Mark<TDependecy>().Stubbed();
return _autoMockingContainer.Get<TDependecy>();
}
protected virtual void Before_each_specification()
{}
protected virtual void After_each_specification()
{}
public void BackToRecord(Object mock)
{
MockRepository.BackToRecord(mock);
}
[SetUp]
public void BaseSetUp()
{
_mockRepository = new MockRepository();
_autoMockingContainer =
new AutoMockingContainer(_mockRepository);
_autoMockingContainer.Initialize();
Before_each_specification();
CreateSubject();
}
[TearDown]
public void BaseTearDown()
{
After_each_specification();
_autoMockingContainer = null;
_mockRepository = null;
}
}
Running the specifications with a test runner gives this results:
This approach results in very readable and concise unit tests. Every unit test describes a specification of the software you're trying to build. It also enables you to focus on a single specification at a time. The obligatory setup code is now banished to the Setup method, which reduces the amount of noise and prevents from having duplicate code in your tests.
For naming the contexts/specifications, I'm using Agile Joe's most excellent BDD macro. There's also a screen cast that is very helpful and its definitely worth your time if you're serious about using this approach. He explains how to setup the BDD macro and how to use it in Visual Studio.
I really like this approach and I can't wait to start using it in my day-to-day coding efforts.
If you and your team want to learn more about how to write maintainable unit tests and get the most out of TDD practices, make sure to have look at our trainings and workshops or check out the books section. Feel free to reach out at info. @ principal-it .be
Jan Van Ryswyck
Thank you for visiting my blog. I’m a professional software developer since Y2K. A blogger since Y2K+5. Provider of training and coaching in XP practices. Curator of the Awesome Talks list. Past organizer of the European Virtual ALT.NET meetings. Thinking and learning about all kinds of technologies since forever.
Comments
Writing Maintainable
Unit Tests
Watch The Videos
Latest articles
-
Contract Tests - Parameterised Test Cases
June 28, 2023
-
Contract Tests - Abstract Test Cases
April 12, 2023
-
Contract Tests
February 1, 2023
-
The Testing Quadrant
June 15, 2022
-
Tales Of TDD: The Big Refactoring
February 2, 2022
Tags
- .NET
- ALT.NET
- ASP.NET
- Agile
- Announcement
- Architecture
- Behavior-Driven Development
- C++
- CQRS
- Clojure
- CoffeeScript
- Community
- Concurrent Programming
- Conferences
- Continuous Integration
- Core Skills
- CouchDB
- Database
- Design Patterns
- Domain-Driven Design
- Event Sourcing
- F#
- Fluent Interfaces
- Functional Programming
- Hacking
- Humor
- Java
- JavaScript
- Linux
- Microsoft
- NHibernate
- NoSQL
- Node.js
- Object-Relational Mapping
- Open Source
- Reading
- Ruby
- Software Design
- SourceControl
- Test-Driven Development
- Testing
- Tools
- Visual Studio
- Web
- Windows
Disclaimer
The opinions expressed on this blog are my own personal opinions. These do NOT represent anyone else’s view on the world in any way whatsoever.
About
Thank you for visiting my website. I’m a professional software developer since Y2K. A blogger since Y2K+5. Author of Writing Maintainable Unit Tests. Provider of training and coaching in XP practices. Curator of the Awesome Talks list. Thinking and learning about all kinds of technologies since forever.
Latest articles
Contract Tests - Parameterised Test Cases
Contract Tests - Abstract Test Cases
Contract Tests
The Testing Quadrant
Contact information
(+32) 496 38 00 82
info @ principal-it .be