all Technical posts

Unveiling the Evolution of Tests as Systems Become Testable

I recently worked on several previously untested projects, and saw this pattern occur when I helped to make various systems testable. Read further to explore this.

No tests = nothing is certain

The project had almost no unit tests, no integration tests, and some outdated system tests. What was categorized as ‘unit tests’ were rudimentary guard checks on public signatures, rather than anything testing the functionality. The reason the functionality wasn’t tested in the unit tests is that the system was not testable by itself. When the mix of external interaction, application configuration, and domain are mixed, you have a system that may act like you want it to, but no way to tell for certain. Gaining back that certainty will not happen by writing unit tests.

Misusing integration tests

As the system is not testable on a lower level, you have no choice but to take a step back. The ‘smallest unit to test’ is not a piece of code anymore, but is an externally accessible endpoint that works through the system. I wrote example-based unit tests, disguised as integration tests. Input validation, edge cases, request combinations, batching, etc., are all things that would normally be extensively unit-tested but were now written as integration tests. Normally, those kinds of tests would reference some kind of test scenario or feature, but in this case, they were sometimes as simple as testing a blank input. Integration tests should also have many failure test scenarios, but the level that was being tested was so low that you felt everything was written in the wrong place. There was no choice: it had to be tested.

Refactor with unit tests

Once you have these special integration tests that show the missing links in the system, you have a fallback to refactor the system. Unit tests then make their appearance for the first time during this test-driven refactoring. This will bring out missing domain validations, hard-coded application configuration, and tightly-coupled dependencies, and more. All the while, you’ll have the out-of-scope integration tests to help you. What you will see is that similar test scenarios and inputs will pop up, but now as unit tests. This will be the first step in moving those special integration test scenarios to a lower level, where they belong. The result will be a system that has numerous unit tests that test lower cases, while a smaller set of integration tests will verify bigger portions of the system.

Conclusion

It’s not enough to finish this exercise and then continue like before. This system-wide refactoring is about the technical debt of testing, which can easily be brought back if the added-value of tests is not understood. The scenario described here is realistic in the sense that the tools at hand are (mis)used for the end-goal we are trying to achieve, even if this means temporarily breaking the test principles rules.

Thanks for reading!
Stijn

Subscribe to our RSS feed

Thanks, we've sent the link to your inbox

Invalid email address

Submit

Your download should start shortly!

Stay in Touch - Subscribe to Our Newsletter

Keep up to date with industry trends, events and the latest customer stories

Invalid email address

Submit

Great you’re on the list!