Unit Test Patterns: The Domain Test Values Class

On my latest Java project, my colleagues and I are keen to improve the readability of test classes. We’ve chosen to use Mockito and Hamcrest because of their natural language approach to mocking and assertions, but on their own they can’t always convey the meaning of values used in a test.

To overcome this, we use a pattern which I’ll call the “Domain Test Values Class”,  the purpose of which is to provide values used in the testing of a domain in a way which improves understanding of test code by increasing readability and adding meaning to values.

Natural language in tests

Mockito and Hamcrest greatly improve test readability by allowing you write tests like this:

AccountService accountService = mock(AccountService.class);
given(accountService.getBalance(1234567)).willReturn(100.0);

double remainingBalance = accountController.withdraw(1234567, 100.0);

assertThat(remainingBalance, is(0.0));
verify(accountService, never()).markOverdrawn();

So in these few lines we can see what’s mocked, that the remaining balance should be 0.0 and that markOverdrawn() should never be called.

Removing the magic numbers

We’re still lacking some clarity because of those magic numbers and it’s always good practice to replace them with constants:

final long accountNumber = 1234567L;
final double startingBalance = 100.0;
final double withdrawalAmount = startingBalance;

AccountService accountService = mock(AccountService.class);
given(accountService.getBalance(accountNumber)).willReturn(startingBalance);

double remainingBalance = accountController.withdraw(accountNumber, withdrawalAmount);

assertThat(remainingBalance, is(0.0));
verify(accountService, never()).markOverdrawn();

Now it’s more obvious from the code what the numbers mean, but it’s still not ideal, we don’t know if it matters that the account number is 1234567. Or if the starting balance has to be 100. Are we right to assume that 0.0 means that the account is empty?

Adding meaning

With a little renaming the test not only becomes more readable, but it starts showing more intent:

final long anAccountNumber = 1234567L;
final double aCreditBalance = 100.0;
final double aZeroBalace = 0.0;

AccountService accountService = mock(AccountService.class);
given(accountService.getBalance(anAccountNumber)).willReturn(aCreditBalance);

double remainingBalance = accountController.withdraw(anAccountNumber, aCreditBalance);

assertThat(remainingBalance, is(aZeroBalance));
verify(accountService, never()).markOverdrawn();

Now simply by choosing some good names the test is very easily understood, so let’s apply this more generally:

The domain test values class

It’s not hard to see that some of the values in the example above will be required in other tests in the account domain, so instead of putting them at the top of the method or as constants in just one test, let’s statically import them and create an AccountDomainTestValues class:

public class AccountDomainTestValues {
    /**A constant test value*/
    public static final long AN_ACCOUNT_NUMBER = anyAccountNumber();
    /**A constant test value*/
    public static final double A_CREDIT = anyCredit();
    /**A simple constant*/
    public static final double ZERO_BALANCE = 0.0;
    /**A generated value*/
    public static final long anyAccountNumber() {
        //randomly generate an account number
    }

    /**A generated value*/
    public static final long anyCredit() {
        //randomly generate a credit value
    }
}

As well as constants, the class also provides methods for generating values. Notice the naming convention – constants are prefixed with “a” or “an” while methods generating values use “any” – this adds intent to the value:

  • “any”: the actual value is not important to the test
  • “a”: the value is fixed and so should be considered important and can be used more than once to mean the same thing during a test

For example:

import static com.manbuildswebsite.AccountDomainTestValues.*;
...

accountService.deposit(AN_ACCOUNT_NUMBER, anyCredit());

assertThat(accountService.isOverdrawn(AN_ACCOUNT_NUMBER), is(false));

Using the “any” methods to populate the constant fields exercises a larger range of values than would otherwise be the case; programmers tend to stick to numbers like 1 or 123, and while randomly generating numbers is not a substitute for testing edge cases, it does ensure a range of real-life values are tested.

We’ve found that by using the domain test values class pattern we’ve improved the readability, and so the understanding and maintainability, of our test code for very little effort.

Disclaimer…

This pattern, or something like it, may well have been catalogued somewhere already, though if it has I’m yet to find it, so I present it here in the hope that it either helps others or that someone will point me to a published version of it!

© 2010 Dave Bower/Man Builds Website
Advertisements

9 thoughts on “Unit Test Patterns: The Domain Test Values Class

  1. Great post! I’ve tried a number of different ways to centralize test data for my unit tests, but this beats anything I’ve tried. At first I was concerned that using randomly-generated values might affect repeatability of the tests, but really I guess it can only help catch corner cases (as you mentioned) which would indicate that the test lacks thoroughness.

    • As a matter of fact, I was a bit concerned about the repeatability too. I’m still not sure whether that’s actually a good idea or not. Maybe we should stick to this “best practice”: http://xkcd.com/221/ 😉

      I completely agree about the fact that putting “natural language in tests” is a great idea. I often end up creating a set of utils for building the fixture that reads like a DSL. The tests should be human readable. 🙂

      I admit that creating a separate class for these utils, constants etc. can be beneficial in many cases, but what I’ve found is that when there are many similar test classes (=different implementations for a common interface) creating a separate abstract base class works fine as well.

      • Thanks for the comments, I think the key point for repeatability is that you should only use these randomly generated values when they are not the subject of your test.

        In other words, if you’re testing a method which has edge cases, you still need to test those edge cases explicitly.

        For example, if you were testing an “isValidAccountNumber” method, using a randomly generated account number would be a repeatability issue.

        However, if you’re testing a method like those shown in the post, where an account number is only required to test other functionality, then using a Domain Test Values class can provide intent – it shows that the account number is not a value under test and any account number will do.

        As for the abstract base class, that’s a last resort for me because it’s so restrictive. It ties your test class into a single type hierarchy leaving your test class unable to inherit from other types.

  2. Pingback: Coffee Notes: The Domain Test Values Class « Christopher Grant
  3. Pingback: A Smattering of Selenium #37A « Official Selenium Blog
  4. I would still go for repeatability. Even if the value is not subject of your test, it doesn´t mean that your code does have a bug with a specific value generatad randomly. In this example, if your code had a bug with negative account numbers, the test would also have random results.

  5. Pingback: TapaGeuR » ITGIF – “IT-God” It’s Friday #22
  6. Howdy! I simply would like to offer you a big thumbs up
    for your excellent information you have got here on this post.
    I will be coming back to your site for more soon.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s