Testing Smarter: Part 5 – Conclusion
December 15, 2023 — Zinderlabs
We will give some actionable advice to wrap up our series on testing smarter.
Avoid Dogmatic Views on Testing and Testing Strategies
Testing should not be dogmatic. Effective testing requires adapting strategies based on context and to the need of each project. To stick to a single approach rigidly can lead to ineffective or burdensome tests.
Embrace ‘Pure’ Coding
It’s important to underscore the value of writing code in a ‘pure’ manner. Strive to model your application’s behavior based on inputs and outputs, minimizing side effects. Pure code simplifies testing and lessens the need to expose internal behavior to validate intended side effects.
Keep your Tests “Clean”
The importance of clean coding practices is widely emphasized in production code, with standards such as proper structure, readability, and maintainability. Yet, similar attention is not always paid to test code.
Remember, clean code principles apply equally to your tests. Tests should be easy to navigate through and to modify. In projects with numerous tests you often spend more time on the test code than the actual production code itself. If the same standards are not upheld, it will take just as much (if not more time) to maintain or alter tests as it would take to find and fix bugs when you have no tests at all.
Organise your Tests
The type of tests within a file should be discernable from the filename. For example, having different scenarios in a large test file can lead to confusion about what is tested and where. This may result in duplicate tests or overlooked updates.
Structure Test Files based on Behavior
Rather than organizing your test files per unit, it might be beneficial to structure them based on behaviour. Take, for example, the CreateBookHandler in part 3, which manages various behaviours around book creation. You could seggregate these behaviours into different test files such as CreateAmazonBookTests, CreateBookWithBelgianAuthorTests, etc.
Prioritise Unit Tests for Domain Logic
Unit tests are especially critical for testing the core functionality (or the “domain logic”) of your program. This might include crucial calculations or operations that your program must perform. Aim to minimize dependencies in these core areas, making your tests more straightforward to maintain and with a minimum number of mocks or stubs. This is particularly important for complex calculations for which test accuracy is paramount.
Testing Smarter: A Recap
This blog post series aims to illustrate that poorly designed tests can become a roadblock in development. Each test requires upkeep, and when testing strategies are applied in contexts they do not make sense in, it will lead to brittle tests that are hard to maintain.
As you write your tests, consider these questions:
- Is it easy to write tests for the production code I want to verify?
- Have I defined the scope of my unit correctly?
- Are there parts of the unit that are mandatory to mock?
- Do my unit tests cover cases that aren’t easily handled by integration tests or cover ones that are too expensive to check (such as minor unhappy paths)?
- Can I navigate my test code with ease? Is it clear what each test file is testing?
Remember, the context of your production code dictates your testing approach. While we can’t cover all possible scenarios in this series, reflecting on these points should lead you toward more efficient tests and application code that is better structured for both readability and testing.
Even if we did not manage to convince you completely, we hope you can see the merit in assessing your testing practices critically. Ultimately, tests should be tools of facilitation and not hurdles in your development process. Let’s uphold this essential principle.
Other posts in this series: