Test Extraction and Readability

Test Extraction and Readability

Andrew Kelly
Andrew Kelly

October 15, 2013

The overt purpose of testing is to ensure that code does what is expected of it, but tests themselves also act as documentation. Highly readable test suites quickly help other developers get up to speed and become productive. A readable test is one that requires minimal mental effort to comprehend the use case, follow the executions, and understand the expectations. One way to increase readability is through extraction -- encapsulating particular operations in a separate method. Extraction can be used to increase the scannability of a test file and to clarify the intent of a particular expectation or test case.

Many languages allow the comparison of dates using the greater than or less than symbol, which can be helpful but also time consuming to interpret. Comparing dates and times using these operators requires some mental work and can slow down the speed at which someone understands a particular use case being described in the test file. Readability can be improved simply by extracting that comparison into a well named method within the test. Take, for example, a C# test written for an Authenticator class to ensure that tokens are being expired:


public void ExpiresOldTokens()
{
				var token = Authenticator.CreateToken();

				Authenticator.ExpireToken(token);

				Assert.IsTrue(token.expiration < DateTime.now);
}

A particular class may have numerous assertions that compare dates or times. Now that it has been extracted, it can be re-used throughout the test file. Extracting this type of comparison into a method increases overall test coherence through the use of words rather than symbols. The aforementioned date related expectation might be extracted out like so:

private boolean TokenIsExpired(token)
{
				return token.expiration < DateTime.now;
}

This can make the test more human readable:

public void ExpiresOldTokens()
{
				var token = Authenticator.CreateToken();

				Authenticator.Expire(token);

				Assert.IsTrue(TokenIsExpired(token));
}

Assuming that numerous use cases relating to the expiration of tokens are being tested, this may substantially improve readability. This extraction can be particularly useful when a test has multiple assertions using date time comparisons. It can also be a slippery slope.

Test extraction can hinder readability. It can obscure what is happening or just be plain unnecessary. Over-extraction can be counterproductive if it causes other developers to have to dig through the test file. Here’s an example of a test that may have been overly extracted:

public void TokensOlderThanTodayAreExpired()
{
				oldToken = CreateAnExpiredToken();

				newerToken = Authenticator.CreateToken();

				Assert.IsTrue(TokenIsExpired(oldToken));
				Assert.IsFalse(TokenIsExpired(newerToken));
}

There’s a lot going on in this test that is not immediately readable.What is actually happening in the CreateAnOldToken method? It isn’t very apparent at first glance. It might actually look something like this:

private String CreateAnExpiredToken()
{
				token = Authenticator.CreateToken();

				token.expiration = DateTime.Now.AddDays(-1);

				return token;
}

The CreateAnExpiredToken method obscures both the creation and expiration of the token. This forces other developers to look at the extracted methods in detail, which defeats the entire purpose of this particular extraction. Even if the name were more explicit, the CreateAnExpiredToken method would require attention in order to understand the behavior that is being tested. Using extraction to improve the readability of a test suite is a delicate balance.

Ideally, a developer should be able to look at a test file and understand its contents with relative ease. Extraction is helpful only as long as it makes it easy for a developer to read a particular test. Test files should be an easily digestible way for a developer to become acquainted with a project’s use cases. However, it should be used sparingly and only when it actually improves readability.