Testing tips
27 May 2019I’ve done a fair amount of data heavy testing recently and here’s some tricks that make things a little easier.
Fixtures
For this example let’s imagine we have a website selling content subscriptions. We have 3 levels of membership:
Our User
type contains a membership value and a function to determine if a user can access some content:
Our content is then marked with an eligibleMembership
level:
To exhaustively test this function we need to write 9 test examples. This is calculated by looking at the inputs to this function.
NB because this is a member function we also count the member variable membership
.
User.membership - has 3 cases (free|basic|pro)
Content.eligibleMembership - has 3 cases (free|basic|pro)
3 cases x 3 cases = 9 test examples
Writing 9 separate tests is going to have a lot of duplication and quite frankly I struggle to name 1 test let alone 9 similar tests that vary only by input.
This is where I would use fixtures and some funky formatting to help readability.
The basic idea here is to loop over an array of tuples that contain the inputs for all of our examples.
In the body of the loop we do the usual arrange
/act
/assert
dance for unit testing.
What I like about the above is:
- It’s compact and all cases can be in one test.
- Being in a tabular layout makes it easier to read.
- I only have to name the test function once.
What’s bad:
- Performing assertions in a loop makes it hard to see which case is failing.
- Xcode will just highlight the
XCTAssert*
line, which is not going to help when we run multiple examples.
- Xcode will just highlight the
- It takes a little practice and getting used to.
I think we can do better and solve the first point…
Use #line
To get better feed back #line
is your friend.
It adds a little more boilerplate to each fixture but my goal for testing is to make it super easy to read the tests and figure out when things go wrong.
This is why I’m happy to use an odd tabular format and add a sprinkling of boilerplate.
Here’s the above example with #line
added to show how it looks:
The key changes are:
- Update every case to take the
#line
. - Update the call to
XCTAssert*
to take#line
.
With these changes in place Xcode will now highlight the actual fixture that is failing.
Write Comments
Looping back to an earlier point - a goal when writing good tests is that they be easy to read and simple to debug. That’s why it’s really important that you add comments when it makes sense. I often find myself falling into the trap of trying to contort a test function name to reveal all intent but it often ends up in an unreadable mess.
A good comment for a test would explain why some set up is being done.
I also find that it’s sometimes really useful to add // Given
, // When
and // Then
comments throughout a test to help delineate the different phases of the function.
Applying these suggestions to the above example might result in this change:
Add .make()
Extensions
If you are writing a lot of tests and you have types that are awkward to create then you’d benefit from creating factory helpers.
Let’s take the following type:
It only has a couple of properties but it will already be a pain to create lots of users especially if for example we was writing tests that only cared about membership
.
We can simplify things by creating an extension
only within our test target that provides defaults for all values:
With this helper in place we can create new users really easily and just provide the values we care about:
Conclusion
Some of these tips are only useful in certain circumstances but I’ve found it really helpful having these techniques in my tool bag.