I like to say that tests should, first and foremost, be a human-readable spec. Let's look at what that can mean for how we write assertions.
Suppose we're writing a card game, and we want to assert that a deck of cards is sorted the way you'd find them when you first open the box. (I'm using this simple example as a proxy for the kinds of more complex problems that we see in legacy code. It's up to you to map these ideas to that context.)
An approach I see in a lot of code is to iterate over the cards to assert. Perhaps something like:
This kind of code makes it obvious that an AssertEquals would be valuable, so that on failure you can see the expected and actual values in the test results.
If this test fails, you only know about one incorrect card. If there are more, you won't know until you fix the current error and rerun the test.
A richer assertion library might offer AssertSorted. It could even take a set of 1 or more sort key selectors. The result might look like:
(That's C++ lambda syntax, if you haven't seen it before).
Both of these approaches are "computer science" solutions - they work in the solution domain, and use the language of computer code. If I want my test to be a human readable spec, I need to use the language of the problem domain. I could take a step in that direction by extracting a method, giving:
But we're also doing TDD. In TDD, we want the tests to give us feedback about the design of the code. And this test is saying "the notion of being sorted that is missing from the code under test". Taking an intuitive leap, the class that should hold that notion is a "deck of cards", which is also missing from the code under test. That leads to:
I like the improvements to the design of the code and the way the test reads, but I am sad to lose the ability to provide a detailed report when this assertion fails. I'm not sure how I would fix that, or if it would ever actually matter.
It's interesting to me that we're back to the
bool-only assertion from the first example.