We held our sprint retrospective last Friday. One of the positive items that appeared was the newly introduced AAA style unit tests (Arrange-Act-Assert). I noticed that Jan Van Ryswyck just posted about this style of tests. This is kind of an extension to his post.
What we're doing is very similar. There really is added value to this AAA style:
- Tests tend to become more readable.
- Test boundaries are explicit. You only test what is under test.
- You actually design the system first. When the test is complete, you just implement to make the test pass.
But all these benefits don't just magically appear. The magic to me appears in the development flow.
To illustrate these tests, I will focus on a simple example:
When a customer controller is told to create a customer, it should tell the repository to save the customer.
Decide what you want to test
In Visual Studio, I position my cursor over the solution where my test will be created.
Alt-Insert (=insert file template)
Choose AAA file template. I will not focus on the layout of this template right now. That's for later.
The first highlight will be on WHAT I want to test: I replace system_under_test with ICustomerController.
public class testname : ArrangeActAssert<system_under_test>
This expresses that the class CustomerController will implement this interface.
Decide what scenario to test
I choose to test the behavior of my customer controller when I create a new customer. I express this by changing the name of the class testname to when_customer_controller_is_told_to_create_a_customer
public class when_customer_controller_is_told_to_create_a_customer : ArrangeActAssert<ICustomerController>
Decide what the effect will be of this action
Now I focus on the outcome of the scenario I'm testing. This is the assert part. I expect that the repository will be called to save the customer. So I write that down as a [Test] with a little help from RhinoMocks.
[Test]
public void should_tell_the_repository_to_save_the_new_customer()
{
repository.AssertWasCalled(r => r.Save(customer));
}
Everything I access from this point on will be a field in this testclass. So right now, I have these two fields:
private IRepository repository;
private ICustomer customer;
Decide why we come to this observation
Why do we expect that this repository will be called? Because we call CreateCustomer on the system under test, no? Let's write that down:
public override void Act()
{
sut.CreateCustomer(customer);
}
sut (system under test) in this case is a component that implements ICustomerController. This is what we declared in the first step. We effectively declared (designed?) a new method Save in this interface.
Create the instance of the sut
Finally. The one thing I used to rush to when writing unit tests. Lets write the class we're actually testing.
public override ICustomerController CreateSUT()
{
return new CustomerController();
}
Now is the time to decide how the controller will get his hands on the repository. Will it be a constructor parameter, or will the controller actively request it from my IoC container? I decide (design?) to pass it as a constructor parameter, so I change CreateSUT to
public override ICustomerController CreateSUT()
{
return new CustomerController(repository);
}
Arrange the test so it can actually run for the first time
The test has now clearly been declared. We now have two fields that have not been instantiated. Let's do that now with a little help from RhinoMocks.
public override void Arrange()
{
repository = MockRepository.GenerateStub<IRepository>();
customer = MockRepository.GenerateStub<ICustomer>();
}
Run the test until green
It's a TDD dialect after all, so I run a RED test the first time. Why? Because I have no implementation of my sut yet (the CustomerController).
I go and take a look at the sut now. It looks like this:
public class CustomerController : ICustomerController
{
public CustomerController(IRepository repository)
{
throw new NotImplementedException();
}
public void CreateCustomer(ICustomer customer)
{
throw new NotImplementedException();
}
}
I don't know how you feel about this. To me, the implementation of the sut is not a challenge anymore. Why not? Because this class has been designed before it has been implemented.
Quick recap
How do you like the readability of this test:
public class when_customer_controller_is_told_to_create_a_customer : ArrangeActAssert<ICustomerController>
{
private IRepository repository;
private ICustomer customer;
public override void Arrange() // Arrange (Duh!)
{
repository = MockRepository.GenerateStub<IRepository>();
customer = MockRepository.GenerateStub<ICustomer>();
}
public override ICustomerController CreateSUT()
{
return new CustomerController(repository);
}
public override void Act() // Act (Duh!)
{
sut.CreateCustomer(customer);
}
[Test]
public void should_tell_the_repository_to_save_the_new_customer() // Assert (not so Duh! anymore)
{
repository.AssertWasCalled(r => r.Save(customer));
}
}
To do this kind of work, wee need an ArrangeActAssert class. It looks very simple, but will surely evolve over time.
[TestFixture]
public abstract class ArrangeActAssert<T>
{
[SetUp]
public void SetUp()
{
Arrange();
sut = CreateSUT();
Act();
}
public T sut;
public abstract void Arrange();
public abstract T CreateSUT();
public abstract void Act();
}
Next requirement
I want to pass a customer DTO instead of a customer entity to the controller.
To be continued... (cliffhanger?)