Narrow-Minded Test Properties
People coming from regular example-based testing will probably try to write test properties as cranked-up unit tests. This is a good first step and will expose lots of miscalculations or false assumptions about your code base. The problem, however, is that developers leave it at that when there are many opportunities just beyond.
By using an extension with a similar approach to how we describe the domain, look beyond the simple categories of ‘unit’, ‘integration’, ‘end-to-end’, to the functionality itself. Here, abstraction is important. Ask yourself, ‘how is this going to be used?’ and ‘what conclusions can we draw?’
Test properties are an amazing tool to help you better understand your code. You may think that you know all the details and edge cases, but it is by writing these automated test properties that you can fully delve into it.
xUnit Self-Shunt Test Pattern
The xUnit test patterns (not to be confused with the .NET xUnit test package) describe a ton of patterns encompassing all the moving parts of your test. One of these patterns is called the ‘Test Spy: Self-Shunt‘ pattern. It is a pattern I have used several times when wanting to ‘loopback’ any indirect call that the SUT (system-under-test) makes. Instead of creating a dedicated Test Double, you use the test class as input for your SUT. This way, any backdoor assertion is immediately visible to the test reader, and the assertion can access any state the test sets up.
In this case, however, I will not be using the pattern directly, but the mentality it brings to testing: ‘use yourself during testing’.
Self-Shunt Test Assertions
The F# FPrimitive package is a good example to show how this mentality can be used in practice. The FPrimitive package contains domain specifications that can be used to describe simple and complex domain models. Practically, it defines a series of validation functions that are run on any untrusted value. On success, a close-typed domain model can be created; on failure, a series of error user messages are collected. The built-in validation functions are very broad, ranging from regular equalization to string and sequence functions. One can also create any custom validation rule.
Examples can be used to show in an easy manner how certain validation functions work, and narrow-minded test properties can be used to flesh out any obvious mistakes. However, self-shunt test assertions will fully bring our intention to the tests.
Let’s start with simple value equalization. A validation function that checks if a value is equal to something else will always be different from its reverse: not equal.
👀 Notice how the error user messages (
msg2) are also generated here. The focus of the test is on the difference between
notEqual. Anything outside that scope should be moved to the background to increase Test Ignorance.
There are several other opportunities for this way of thinking:
Property-based testers will automatically look for relationships in the code base, without thinking in separate boxes like ‘unit’, ‘class’ or others. Because of this, the test properties will test a much broader range and flesh out any false assumptions about the code base.
Test properties are by themselves a practical implementation of high Test Ignorance. The test body only contains the necessary code to test the relationship. It results in super small test bodies that are easy to read and quickly to understand. The Expecto testing framework also helps a great deal as we can write the tested relationship as a full sentence in the test name (ie.
inclusive is broader than exclusive).
It would be wrong to only use the xUnit test patterns in the contexts in which they are explained. We can reuse their thought processes, too. In this post, we have used the Test Spy: Self-Shunt idea of using yourself as input or assertion. In property-based testing, this is a natural process as the assertion is seldom concrete, instead encapsulating a relationship.
This way of thinking and testing bridges social assumptions with complex technical implementations. All the while, it maintains high code readability. It really checks all the boxes.
Thanks for reading!
Subscribe to our RSS feed