A common piece of advice in the test-driven development (TDD) community is to limit yourself to one assertion per test (OAPT). Others disagree, finding the guideline too restrictive and dogmatic. Still others propose a compromise: you can have more than one assertion in a test, but they must all be checking related aspects of the same outcome.
Reasons given for OAPT include:
- Each test has one and only one reason to fail;
- Multiple outcomes of an action should be tested separately so that they can succeed or fail independently;
- Tests should be concise and readable. More assertions make for longer, less-readable tests.
Lately, I’m finding that I follow OAPT more often than not. This is a change from how I used to test, so I’ve been reflecting on why my preferences have changed over time.
I think there are a few reasons why my style has changed.
A minor reason is that of tooling. Smalltalk’s test framework (SUnit) is really designed to be used in the interactive Smalltalk environment where the powerful debugger is available to show you the context of any assertion failure. As a result, there are relatively few assertion methods compared to other languages, and the error messages they produce are not very informative. When these tests run on a CI server or similar, the context is no longer there and so the failure messages are less informative. With multiple assertions in a test, it is harder to know which of the assertions failed without more context. There are ways around this, like using the optional description parameters to provide more information. The descriptions can be useful, but can also clutter up the test, making it less readable. OAPT makes it clear what failed without adding the descriptions.
OAPT also makes later refactoring easier. If I need to move or remove a responsibility from an object, it’s much easier to move or delete the tests for that responsibility if they’re in separate test methods. If I have assertions about the responsibility scattered throughout tests that are also testing other behaviors, I need to be much more careful and surgical.
The most significant reason for the change in my approach, though, is that I found it harder to understand tests with multiple assertions in them. I’d come back to a test suite after several months and I couldn’t remember why I was testing certain things. This is especially true of workflow-style tests (change this, assert that, change something else, assert another thing, etc.).
I find that splitting these tests into multiple tests with OAPT allows me to use the name of each test to communicate what I’m testing in a way that an assertion can’t.
So really, it’s not that I’m strictly following OAPT. It’s that I’m trying to introduce additional communicative test names so that I have a better chance of figuring out what I was thinking when I later revisit the code.