Geeks With Blogs

News
Shelfari: Book reviews on your book blog

.net alternatives by Michel Grootjans

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?)

Posted on Monday, October 27, 2008 11:50 PM .net | Back to top


Comments on this post: AAA

# re: AAA
Requesting Gravatar...
Michel,

thanks for sharing your experience of the AAA testing style and the clear description of the complete flow.
Compared to other blog posts where mostly the overall concept of AAA is explained your post is really an enrichment.
And, yes, I love the readability of the test, but have to admit, that I am biased after participating JP's "Nothin but .NET" ;-)

Looking forward for more!

Dirk

Left by Dirk on Oct 28, 2008 9:27 AM

# re: AAA
Requesting Gravatar...
Hi!Very nice. May i ask what the difference between this (AAA) and BDD is? aside from name

Thanx
Left by peter on Nov 09, 2008 12:46 AM

# re: AAA
Requesting Gravatar...
IMO, BDD is about the expressiveness of the tests your're writing. It's naming your test 'when_customer_controller_is_told_to_create_a_customer.should_tell_the_repository_to_save_the_new_customer'

AAA is about organising your test in a specific way.
Arrange
Create SUT
Act
Assert
Left by Michel Grootjans on Nov 10, 2008 9:27 AM

# re: AAA
Requesting Gravatar...
Hi Michel,



Your AAA class is fantastic. I used it in one of my applications.

We changed it a little bit, so that a cleanup after the test is also possible. It now looks like this:

[TestFixture]
public abstract class ArrangeActAssert<T>
{
[SetUp]
public void SetUp()
{
Arrange();
sut = CreateSystemUnderTest();
Act();
}

[TearDown]
public void TearDown()
{
Cleanup();
}

public T sut;
public abstract void Arrange();
public abstract T CreateSUT();
public abstract void Act();
public virtual void Cleanup()
{
}
}


Koen
Left by Koen Weyts on Apr 03, 2009 4:51 PM

# re: AAA
Requesting Gravatar...
Yep, we did that too. We called it
AfterEachTest();

That way we now have an AAAA class ;-)
Left by Michel Grootjans on Apr 04, 2009 9:23 AM

# re: AAA
Requesting Gravatar...
If you replace the setup attribute by a TestFixtureSetUp attribute, the tests run even faster. Setup is called before every test, while testFixtureSetup is called only once.

Many thanks to my collegue Guy who suggested this.
Left by Koen Weyts on Mar 07, 2011 1:48 PM

# re: AAA
Requesting Gravatar...
True, but this might become tricky later on.

If one test modifies setup data, other tests will be influenced. You will end up with dependent tests, which you DON'T want.
Left by Michel Grootjans on Mar 07, 2011 2:05 PM

Your comment:
 (will show your gravatar)


Copyright © Michel Grootjans | Powered by: GeeksWithBlogs.net