Write Code In English Part IV
01 May 2023
Most written communication is in natural language, so why wouldn't we want to try to mirror that in our code?
This series will present small concrete examples that might help your code read less like code, today's topic is...
Test Setup - 'Any' vs 'Some'
The 'any' convention
There are few things I enjoy more than a good unit test - I can prove it too, if you ask nicely 😉. Consider a very basic person model:
We'd like to add a FullName property, so let's add a unit test (the FullName property is trivial and left as an exercise to the reader). Pay close attention to the test data.
Not bad, but let's consider if any parts of our test that will cause unease to future first-time readers - can spot any? The Email property assignment looks out of place. "Why do we care about the email?" Future readers might ask, and rightly so - we don't in this test, however the Email property is required, so deleting the assignment would lead to a compile-time error. The superfluous data shrouds our test's intent. How would you change the test to shed some of the darkness?
One simple option would be to highlight the Email assignment as unimportant using a natural language convention.
Any Email will suffice since changing its value will have no impact on the test; the 'any' prefix makes this fact clear. Another benefit of using natural language is that the test data still looks like an email, which helps keep the person's data closer to the real world (which in turn I find keeps code more palatable for any real person - hopefully we can all generally agree on what the real world looks like, right?). But why stop there?
Sometimes unit tests aren't so simple. Imagine a fictitious PersonService that validates a Person model using a PersonValidationService, then either persists valid people or throws an exception. Here's an example of an invalid path unit test.
Spot any setup present only included to keep the compiler happy? How would you use our new 'any' convention to highlight the extra setup?
Here's my attempt:
The PersonRepository instance is only included as it is a required parameter in the PersonService constructor and could have any implementation without affecting the test - sounds like a good candidate for an 'any' prefix to me.
That's one tool in the toolbox - highlight test setup that 'just happens' to be present (i.e. isn't used in the action or assertion) with an 'any' prefix (or agree a similar word with the team).
The 'some' convention
Think back to the simple Person unit test, what other questions might future readers ask?
As much as it hits the ego, "What's so special about 'Ned Grady'? Are 'Ned' and 'Grady' some magic strings for a special case of the FullName property?" Absolutely not. How would you rewrite the test to remove doubt from future reader's minds?
How about another natural language convention?
Though the specific contents of FirstName and LastName aren't intrinsic to the test, the properties still play an important role in the test's arrange and assert section. Any data will do, but in contrast to the 'any' scenario FirstName and LastName setup can't be modified without modifying the assertion - in such scenarios one can highlight important test data with a 'some' prefix.
Eagle-eyed readers will note that the person's name now looks less like a real-world name (recall my previous praise of the real world in this blog post) - that's a symptom of the constant battle between ideals & pragmatism in the war to write readable code. The 'any' vs 'some' prefixing might not always be the right thing to do, but I've never regretted considering all possibilities when writing software.