I've been a long practitioner of test-driven development (TDD). I first
started unit testing back in 2004, and it made a profound effect on
me. It was incredibly difficult, however and I began to look for ways
to make my code more testable. This brought me to TDD, which guided me
towards looser-coupled systems that were testable from the get-go
(since we started out testing it!)
Things in the TDD world aren't all peach schnapps and roses (or however the saying goes). Often times you still end up with messy tests that have tons of code duplication and very little solubility. TDD is still difficult for me, even though I've done it for 4 years.
One thing I've noticed lately that I've been slowly losing in my tests is the executable documentation. It's increasingly hard to tell how my system behaves just from reading my unit tests. As our system grew more complex, so did our unit tests and eventually they lost most of their grokability.
Another thing I've noticed is that our tests have so much setup gunk that it clouds the meaning of each test method. By nature each test likely has some particular context in which it intends to run, and this code gets placed in the [Test] itself rather than the [SetUp]. Why? My mocks need to behave differently in each test method, so if I put them in the setup, things either don't work for other tests or there are mocks being set up that are never used for other tests. I directly attribute this to having one [TestFixture] per system under test.
I'd like to state that again, because it bears repeating. One TestFixture per system under test is an anti-pattern. It has taken me three (3!) presentations on Behavior-Driven Design for this fact to really sink in. Contexts are important, and a system that you want to test certainly has more than one context. By separating the fixtures into per-system-context rather than per-system, we can now leverage our [Setup] again to establish our context!
Here is a test class that I might have written with a strict TDD mindset:
//PostControllerTester.cs
[Test]
public void edit_action_gets_post_from_repository()
{
_authService.Stub(x=>x.IsAuthorizedToEdit("bob"))
.IgnoreArguments()
.Return(true);var controller = CreatePostController();
controller.Edit(5);
_repository.AssertWasCalled(x=>x.Find(5));
}
Notice that this test actually has some setup, some sort of action (in this case we're invoking the Edit action), and some verification. This test has 3 pieces to it! Applying a more BDD mindset, you'd likely have something like this:
public class when_invoking_edit_action_as_authorized_user()
{
...
[SetUp]
public void Setup
{
_controller = new PostsController(...);
_authService = MockRepository.GenerateMock<IAuthService>();
_repository = MockRepository.GenerateMock<IPostRepository>();
EstablishContext();
When();
}
public void EstablishContext()
{
_authService.Stub(x=>x.IsAuthorizedToEdit("bob"))
.IgnoreArguments().Return(true);
}
public void When()
{
_controller.Edit(5);
}
[Test]
public void it_should_fetch_post_from_repository()
{
_repository.AssertWasCalled(x=>x.Find(5));
}
}
Notice the name of the class. It describes the context of what we're trying to accomplish. Our [SetUp] now establishes the state of the environment in which we want to act on our system. The When() method is where we act on our system. The only thing remaining is the test which is now dead simple. The test simply verifies some aspect of the system. The name of the test is also only conveying the verification part of the test.
We can now easily glance at this test (or spec, which it is sometimes called) and read it like english:
When invoking the edit action as an authorized user, it should should fetch the post from the repository.
This looks awfully close to acceptance criteria that we write for our user stories.
Any acceptance tests that share the same context can reside in the same fixture. It's perfectly normal to have 1 test per fixture, but at times you'll find that you can have 2 or 3 that indeed share the same context, and those can reside in the same fixture class.
There are plenty of BDD frameworks out there that will do their best to impose their view of BDD on you, and this can seriously hinder your ability to naturally understand the why/how of BDD. Later on, once you have your own opinions, you can leverage a framework (such as MSpec) to have a more terse syntax for describing behaviors in your system.
Until you get BDD, stick to what you know. In my example I call EstablishContext() and When(), and I'll do that for every fixture. Why not leverage a base class? Here's mine:
public abstract class Specification
{
protected Exception ExceptionThrown { get; private set; }
[SetUp]
public void Setup()
{
EstablishContext();try
{
When();
}
catch(Exception exc)
{
ExceptionThrown = exc;
}
}
protected T Mock<T>()
{
return MockRepository.GenerateMock<T>();
}protected abstract void EstablishContext();
protected abstract void When();
[TearDown]
public virtual void TearDown()
{
}
}
Now when you inherit from Specification, you are forced to provide implementations of EstablishContext() and When(). I also wrap the When() in a try/catch so that you can do things like Assert.WasThrown(...);
I'm still a BDD newbie, but I'm now sold on it's organizational structure and it's ability to connect tests back to actual requirements of the system. It provides a clear picture of how to group tests & setup behaviors and combined with the new RhinoMocks AAA syntax, makes my tests much easier to read.
The tipping point for me was attending Raymond Lewallen's BDD talk at Tulsa TechFest. I recorded the first hour of it, and it's available up on Viddler. I hope this helps more people understand BDD. I've seen presentations like this before, but it took some practice (and failure!) on my own time to start to see some speed bumps. With this knowledge I was able to ask some direct questions and get (gasp!) direct answers.
While I cannot justify going back and changing 500 or so unit tests to this form, I will ensure that I start writing BDD style specifications for my new production code moving forward.