Readable Test Code

T. Yonekubo
4 min readJan 21, 2021

--

Code is read much more often than it is written.

Guido van Rossum, founder of Python language, said that. Readability is one of the most important attributes of a good software. This is also true for test code, therefore we should treat it as valuable an asset as production code.

Test code is described with concrete values. Number of parameters relevant to the behavior to verify and their possible values eventually boost. These makes test code more redundant than production code.

In this post, I will introduce you some techniques to refactor hard-to-read test code toward easy-to-read one. Sample code is written in JavaScript and test code is written using Jest testing library.

First refactoring: So, we’re going to refactor not-easy-to-read code below. Suppose it is part of software that supports agile development processes using Scrum.

Listing-1

Clarify what the test target is.
The component to be verified in a test is called SUT(system under test). The component on which SUT is depended is called DOC(depended-on components). Differentiate SUT, the leading role in our test story, from DOCs, the supporting roles. To give a name sut to the variable that stores SUT is a good practice.

Listing-2

Verify sole thing in a test case.
Do not put everything into a test case! The above test case should be split into two cases.

Listing-3

Create a shared test fixture in a set-up method.
Text fixture is an environment in which a test will be exercised. Or a set of pre-conditions. Specifically, the state of SUT, the state of DOCs, the state of environments, and so on.
In order to get rid of code duplications, move the code that creates the test fixture to the set-up method. In Jest, it is the beforeEach method.

Listing-4

Apply AAA pattern(or given-when-then).
Test code is consist of four phases below.

  1. Prepare
  2. Exercise
  3. Verify
  4. Tear down

The fourth one, tear-down, is usually needed when the test case uses shared resources like database, storage, network, etc. Therefore, Prepare/Exercise/Verify are the basic elements in a normal test case.
AAA is an initialism for Arrange/Act/Assert, which means the same as Prepare/Exercise/Verify. BDD people will use the term given-when-then.
Keep that in mind when you write test code. Split the test code into three blocks using comments in order to make it look better.

Listing-5

Differentiate values that affect the behavior to verify from those not.
Let us see the test case below.

Listing-6

A Story has several attributes like title, description, points and assignee. Among them, it is points and assignee that affect the behavior, sprint.assignment , we want to verify. Attribute values other than these is just a noise in this test case.
Setting an arbitrary value like 'any' to those irrelevant attributes allows you to highlight the attribute values you’re most interested in.

Listing-7

Use creation methods.
The above example looks better than before, but the value 'any' is still kind of annoying. Instead of directly create objects by new operator, make helper methods to create them.
Define a function that creates a Story object.

Listing-8

If the parameter object passed to aStory function contains an attribute corresponding to one of constructor arguments, its value is passed through to the constructor. Otherwise the default value will be used.
Now we can rewrite the previous test case using the helper method.

Listing-9

Excellent! The less the noise, the more readable the test code is.

Write the formula, not the result of the calculation.
Take a look at the piece of assertion code from the code above.

Listing-10

Four points for Alice is sum of two stories set in the Arrange phase.

Listing-11

This information has been lost from the test code, though. We can make the test code more intent-revealing by replacing the calculation result with the formula.

Listing-12

One more thing. Using constants like below will make it look better.

Listing-13

Make the test case name eloquent.
Regarding the comments in the previous test case:

Listing-14

It is a nice idea to express this specification as the name of the test case.

Listing-15

Second refactoring.
We’re going to refactor the test code for Story object below.

Listing-16

First, we will apply refactoring techniques described so far.

Listing-17

Apply Parameterized Test pattern. The four test cases in the above code looks similar and redundant. Let’s organize them by applying Parameterized Test testing pattern.

Listing-18

The test.each part is written with the tagged template literal. This notation provided by Jest is a kind of DSL(domain-specific language). Each line in the template literal after line 2 will be converted to a test case.
For instance, the second line in the template literal is equal to the test case below.

Listing-19

That’s all for our test code refactoring!

Conclusion. Test code is easy to be cluttered. Hard-to-read test code will lose confidence which might lead to a situation where it won’t be maintained any more.
Test code that has a technical debt will be a significant problem for us, which might cause a big affect on quality and maintainability of production code.

To avoid such a situation, it is important for us to understand the importance of test code readability, and refactor them on the daily basis using techniques like ones introduced in this post.

Final source code.

Test code refactored:

Listing-20

Sample production code tested(SUT):

All sample code is published here in a GitHub repository.

References:

Dustin Boswell, Trevor Foucher. The Art of Readable Code. O’REILLY, 2011
Gerard Meszaros. xUnit Test Patterns. Addison-Wesley, 2007

--

--