Tuesday, March 25, 2008

Trying For Readable Tests

Whilst playing with using BDD style specifications to drive the granular design of my classes I've tried to see if I can also look at ensuring my tests are as readable as possible and I thought I should document my current thoughts on the results.

In this description I'll use the word specification/test interchangeably...I'm just not ready to talk about state or interaction specifications.

State Tests

I've been following the approach of having small test fixtures (contexts), for example:

using Concerning = System.ComponentModel.CategoryAttribute;
using Specification = NUnit.Framework.TestAttribute;
using Context = NUnit.Framework.TestFixtureAttribute;
using NUnit.Framework;

namespace AddressMapperSpecifications
{
[Context]
[Concerning("AddressMapper")]
public class When_mapping_addresses
{
#region Fields

private AddressDetails _mappingFrom;
private Address _mappingTo;

#endregion

# region
Context

[SetUp]
public void SetupContext()
{
_mappingFrom = AddressObjectMother.CreateAddress();

_mappingTo = new AddressMapper().Map(_mappingFrom);
}

# endregion

#region
Specifications

[Specification]
public void Street_one_is_mapped()
{
Assert.AreEqual(_mappingFrom.StreetOne, _mappingTo.StreetOne);
}

[Specification]
public void Street_two_is_mapped()
{
Assert.AreEqual(_mappingFrom.StreetTwo, _mappingTo.StreetTwo);
}

I like this style because it leads me to have small fixture/context classes, likely only a very few specifications are going to want to share exactly the same fixture so I avoid the issue of my classes getting too big.

I also think that the small fixture size and the class/test names together result in an approach that I find very readable.

Interaction Tests

As I see it you use use mocking for a few reasons, one of the most obvious ones is to for convenience. Say A calls B which calls C, if I write my tests for C but my clients use A then I need to show that when I call A we can expect C in turn to be called (and also can show the arguments/return values). I see this as mocking to make granular testing easier, there are other reasons to use mocking/stubbing but this discussion doesn't necessarily relate to them so well.

The previous example showed a state test/specification where just having an assertion (or assertions) in the specification method worked. Arguably moving the setup and interaction with the system under test (SUT) into the SetupContext method helped make it all more readable. We can't really do that so easily with mocking tests because in a mocking test you have to setup your expectations up front so the tests take take (basically) this form:

  1. Setup including setting expectations on the mock.
  2. Exercise the SUT.
  3. Verify that the expectations on the mock were met and optionally do other verifications.
  4. Cleanup (if required).

If we move the setting up of the mocks into the SetupContext method then we'd end up with a situation where each class had one specification (because its unlikely we'll want to setup the same expectations multiple times). Even worse looking at the specification method would tell you nothing because to understand a mocking test you need to start by looking in detail at the expectations.

I've thus found that so far my interaction tests are taking this form:

namespace AccountMessageMapperSpecifications
{
[Context]
[Concerning("AccountMessageMapper")]
public class Interactions_when_mapping_an_account : RhinoMockBase
{
# region Fields

private Account _subjectOfMessage;
private DomainMessage _messageToBeMapped;

private AccountMessageMapper _underTest;

# endregion

#region
Context

[SetUp]
public void SetupContext()
{
_subjectOfMessage = AccountObjectMother.CreateAccount();
_messageToBeMapped = new TestDomainMessageBuilder().WithDomainEntity(_subjectOfMessage).Build();

_underTest = new AccountMessageMapper();
}

#endregion

#region
Specifications

[Specification]
public void Primary_address_is_mapped()
{
IMapper<IAddress, Address> _mapper = Mocks.CreateMock<IMapper<IAddress, Address>>();

using (Record)
{
Expect.Call(_mapper.Map(_subjectOfMessage.Owner.PrimaryAddress)).Return(new Address());
}

using (Playback)
{
_underTest.Map(_messageToBeMapped);
}

_mocks.VerifyAll();
}

#endregion
}
}

DISCLAIMER: I've just bodged this together so it isn't great. I'm particularly not liking the way it looks when I pass a generic interface in to get a mock for it!

Most of the code is in the actual specification method, including all of the setup, the interaction with the SUT and finally the call to verify that the expectations were met. I've moved as much as I think I sensibly can into the SetupContext method but the fixture is still quite lightweight and reusable, for example I could easily put another method in that showed the interaction with another mapper (e.g. Telephone_number_is_mapped).

Overall though I think this style of interaction test is quite readable, and to me the combination of the two tests explains very well the behaviour of a low-level component (AddressMapper) and also the interaction that a higher level component (AccountMessageMapper) has with it.

Share This - Digg It Save to del.icio.us Stumble It! Kick It DZone

No comments:

Post a Comment