How can tests be your best documentation? What small changes can improve the contract between your code and its consumers? Learn how to improve your tests from a real-world example.
Yes, we write them to ensure fewer things break before you push changes (a.k.a regression). The thing is – tests can be more than just “tests”. They can be a living and breathing documentation of your contract with your users/consumers. They can be documentation for other developers.
Let’s review three simple habits that can improve your tests.
Table of Contents
Writing a Test
Our story begins with an accordion component:
This accordion component has an expandmode
API. It can either allow a single item or multiple items to be expanded at the same time.
In the test, we’d like to test the single
and the multi
.
This is the original test written by the accordion’s developer:
The describe
in lines (lines 1 and 14) tell us we test the non multi
and multi
cases.
The tests themselves are also explained in the it
use cases (lines 2 and 15).
The test for the single
mode is as follows:
- Expect both items to be closed (
open
property of both items to be false) - Action – set both items to true
- Assertion – expect the first item to be closed and the second item to be open
Sounds like we are testing what we want, right?
The test for the multi
case is quite similar:
- Change
expandmode
tomulti
- Expect item1 to be open and item2 to be closed
- Open both items
- Expect both items to be open
Tests what we want? Could be. Can we be more explicit in defining our API? Let’s see.
Improve Your Tests Step #1: Describe the API
The “describe” section doesn’t state the API used. We should group the two use cases under expandmode
like this:
By grouping use cases according to their API’s (in this case, the expandmode
property), we make it more apparent to the reader what the API is.
If we follow this small habit throughout our tests, our test files will look like this:
We will create something marvelous called: “Documentation”.
Improve Your Tests Step #2: Documenting the API’s Usage
In the single
step, the API usage is implicit. We should strive to show explicitly how to use it:
By stating the setup explicitly (in this simple case, element.expandmode = 'single'
), we tell the reader: “This is how you use this API”.
Remember we are creating documentation? This is a live example of how the API is used right before we assert the usage works as expected.
Improve Your Tests Step #3: Creating Triple-A Tests
Many standards were developed to help us avoid common pitfalls.
The AAA pattern(Arrange, Act Assert
) is one of them.
In its essence it states that the tests comprise of three parts:
- Arrangement – setup the scenario for the test.
- Action – the action that should trigger the use case tested.
- Assertion – our expectations for the action’s results in the given setup.
Here’s the code after a change to reflect the AAA pattern:
Now all of the setup is being made at the top, the actions in the middle, and the expectations at the bottom.
This pattern repeats itself in all tests; the reader knows what to expect in every test case. This reduces the cognitive load on the reader in many cases.
Another benefit of the AAA pattern is to raise a red flag. Sometimes it would seem that the pattern cannot always be implemented. For example, a test might have more than one step (a.k.a multiple Actions). This could hint on several “smells” like:
- We are testing more than one use-case and should consider splitting the test block.
- Our implementation is too complex (it usually happens with TAD (Test After Development)).
Summary
The code review above shows a refactor to two very simple tests. After this short refactor, we can easily discern our API and how to use it:
expandmode
API => has single
and multi
use cases => we can use it by setting the property with the string value. We also know what to expect from changing it.
When looking at bigger test suites, the benefits of these three steps will be much more noticeable:
- Our API will be fully documented with usage examples
- Our API will be safer to refactor and extend
- We are less likely to miss use cases, and new use cases for API’s have a clear place
- We increased readability project-wide (if said practices were used project-wide)
For more tests tips, check this link