Structure your tests using the Arrange-Act-Assert pattern
Although it’s possible to apply all kinds of metrics and mathematical principles to software testing, in practice most experienced developers just follow their intuition when they write tests and don’t really think that hard about how they write them. As it happens, writing good test cases is often more of an art than a science.
Fortunately there’s that can help you write test cases that are easier to understand and debug: the Arrange-Act-Assert pattern.
The Arrange-Act-Assert pattern is based on the idea that many tests do a combination of the following three things:
-
Arrange: Set up everything that we need before we can start our test, e.g. by creating, initialising or modifying test objects.
-
Act: Perform some action on the system under test (SUT), i.e. the thing that is being tested. The action is usually a function or method call.
-
Assert: Verify that the action was handled properly by the SUT, for instance by checking if a function call produced the expected result.
For example, a simple unit test in Python might look a bit like this:
This test is only three lines long, so it’s easy to see what’s happening here:
first, we create a new Student
object with a date of birth, then we use its
age_on_date
method to calculate the student’s current age in years on a
specific date.
However, most tests that you encounter in real-life codebases consist of more than just three lines. Here’s an example of a slightly longer test:
Not only is this test longer than the test that we saw earlier, it also resembles a wall of text, which makes it hard to scan!
And this test is actually not even that long; some tests require dozens of lines of code just to set up things like mocks and fixtures, or to verify that the SUT produced the expected result.
Longer texts usually consist of multiple paragraphs, which are separated from each other using newlines. We can do the same thing in our code.
That same test already looks a bit more readable and a little less threatening when we add some newlines to it:
The first section (or “paragraph”) is responsible for preparing the student
object. The second section calculates and sets the student’s GPA, while the
third section contains our assertions.
We can restructure this code a bit so that each of the three sections in the test corresponds with “Arrange”, “Act”, and “Assert” respectively:
You can now instantly see that this test covers the is_happy_with_gpa()
method
without having to read the entire test. We had to make some small sacrifices
though:
-
All setup code is now grouped together, even though it consists of two distinct parts.
-
In previous versions the
is_happy_with_gpa()
was called as part of the assertion. But because we separated the action and assertions here, we had to introduce an intermediate variableresult
that stores the result from our action until we’re ready to check its value.
As a final touch, we can use single-line comments to add headings to each of the three major sections. This also allows us to split the “Arrange” section into two paragraphs again, as in the previous snippet:
There are still , but at least we’ve managed to make things a little bit better!
You should know that this was an incredibly opinionated article. Not everyone likes the Arrange-Act-Assert pattern. People don’t always use it. Even I don’t always use it.
In this section I’ll try to explain when and how I use it.
When I write tests I usually create a little template that looks a bit like the following snippet:
The >
has no special meaning. I simply use it to differentiate these section
headings from regular single-line comments:
Some people use capital letters (# ARRANGE
), other symbols (## Arrange
), or
omit the whitespace between the comment symbol and the heading
(#> Arrange
, #arrange
).
Speaking of whitespace: you can also omit the newlines between sections, if you want your tests to look small.
You can also use other section titles (e.g. “Given”/“When”/“Then”) if you want. After all, it’s your code!
Note that you don’t have to include all three sections:
-
Many tests are so simple that they don’t require any setup;
-
Tests that verify if the SUT throws an exception rarely include assertions within the function body;
-
Sometimes the “Act” and “Assert” sections are so trivial that there’s no need to separate them.
If your test is simple enough, you can even leave them out entirely:
It’s also okay to omit headings if you feel that a test is just as easy to understand without them, as we saw in the first snippet or in this modified version that separates the three sections using only newlines:
All of the examples you’ve seen up to this point are written in Python, but the Arrange-Act-Assert pattern works for virtually any language. Here are two examples that use Java and PHP:
You might run into a situation where you want to include two distinct sets of assertions, e.g. when you want to verify that an object can be successfully created and updated within the same test.
. It’s called Arrange-Act-Assert for a reason.
The Arrange-Act-Assert pattern doesn’t work very well for tests that make use of mocks. Such tests often don’t have a section for traditional assertions. Instead, the assertions are written in the form of expectations that are defined during the “Arrange” step.
For such tests you could use a template that includes two “Arrange” sections; one for “normal” variables and objects, and one for mocks: