Giving your unit tests the trust they deserve
Where we learn more about the value of unit tests, and about collaborating closely to better understand the complexities in our software.
In some teams there is a mistrust of unit tests. Business, test, and QA people, don’t think unit tests provide the evidence for working software they require. Engineers say that unit tests are used to give the engineers confidence, but are not for the business. In my experience neither should be true. And it’s a missed opportunity to realise some really valuable qualities, both in how we work and in how we design code. By building trust across the stakeholder landscape we can realise faster feedback loops, better software design, and a far better deep understanding of what the software is there to solve.
Let’s have a look at the value proposition for each.
Fast feedback
With risk of repeating the obvious, the sooner we know something really works as it should, the faster we can move on to the next thing without worrying about rework. The reverse is also true, the faster we know that something doesn’t work the easier it is to fix.
Unit tests are fast to run. We can run thousands in less than a second. This provides us with really fast feedback, telling us if what we just did is working as we intended it to work.
If we also tie this feedback all the way to a specification that the business and/or users understand, this fast feedback becomes so much more valuable.
Well designed software
If we can design our units (I recently explained what I mean with unit here) so we know that when the unit tests pass the business needs are met, we have also designed our units along the lines of business needs. This does not mean that all useful unit tests are useful executable business specifications. We have a lot of stuff in our code that is there to enable the business specification. But it does mean that when business rules change it does not impact technical enablers. Conversely, when we make changes to the technical enablers business logic is not affected. Significantly reducing the blast radius of change.
Key actions to build confidence
This might sound like a lot of work. But if your software is working as it should you are already doing this work. Perhaps you are not conscious, structured or explicit about it, but you are still doing it. If you didn’t your software wouldn’t be doing what you want it to do. So let’s look at how we can make this work visible and structured.
- Collaborate closely to deepen understanding of both the problem domain and the solution domain.
- Design software to mirror the share mental model, in language and modularity.
- Use real data and real examples when writing unit tests.
- Structure and formulate tests to document use cases of the SUT.
- Continuously run tests in every commit.
- Visualise test results to provide the evidence required.
- Test drive defects by adding failing unit tests to trigger the bug before fixing it.
- Compliment the test suite when you find new use cases for the product.
Collaboration
Bring all relevant stakeholders together during planning and refinement, domain experts, testers, UX, engineers, to create a shared model. Use collaborative techniques like:
- Example Mapping helps the team break stories down and create scenarios everyone can trust (and read this to learn how a team uses it).
- Domain Storytelling helps the team visualise and understand the business process.
- Event Storming Helps the team identify key domain events and how to model them.
Encourage stakeholders to express requirements in a simple, clear, scenario-based format using real-life data and examples during these sessions to ensure communication between engineers and non-engineering stakeholders.
Different stakeholder groups will have different needs in your software. They will use different languages. Often to the extent that the same word has different meanings. Identify contexts, in code often expressed in components or containers (as in C4 containers, not docker containers). Where each language can be expressed without ambiguity. Domain Driven Design provides a number of helpful patterns to address this and to create contexts where a common unambiguous language can exist.
- Language Alignment: Stakeholders may use terms differently. Use patters like Ubiquitous Language and Bounded Context to create contexts where ambiguous language is clarified.
- Context Mapping: Shows how different components or parts of the system interact clearly.
Software Design
Separate the needs of different stakeholders (e.g., business processes or compliance requirements) in code using modular design. This limits interference between system parts, simplifies testing, and enables trust in specific areas.
- Use Hexagonal architecture: Keep business logic separate from technical concerns.
- Apply Domain Driven Design: Eliminate technical jargon in domain-oriented language and encourage consistent terms shared with stakeholders.
Real Data and Real Examples
Utilise the examples and shared language from refinement sessions when writing tests. Unit tests verifying domain logic should:
- Directly connect to stakeholder-defined business requirements.
- Provide test reports that clearly demonstrate what the software delivers.
Collaborate with stakeholders on test details, especially for complex scenarios. This builds curiosity, trust, and reinforces confidence that testing aligns with needs.
Formulate tests as documentation
Make sure to write tests as a way to document how the SUT is intended to be used. Kevlin Henney explains this well in his talk Programming with GUTs.
Test Continuously With Every Commit
Automated tests must run frequently for rapid feedback. In continuous delivery we often talk about two key places in the pipeline where we run tests, the commit stage and the acceptance stage. One of the main differentiators is the speed at which they need to complete to provide the feedback we want. The commit stage is there for us to ensure we are good enough to mainline something. It must complete within 5 minutes. Which makes it unsuitable for slower tests. But in five minutes we can run millions of unit tests. This means that if you can create the evidence and surety you require for your core business logic into your unit tests, you will know that your software is good within 5 minutes.
Visualise test results
Work closely with testers and stakeholders to make sure the test result output fulfill requirements for proving that the software works as intended. You can do real magic here. If you have stringent regulatory requirements you can use your test suite and reports to create the evidence required to prove to auditors that every build is compliant. Everything as Code was created to collect learnings from years of work making this reality in the pharma world.
Test drive defects
When defects are found later in the value stream, make sure to first add the tests needed to demonstrate the defect. Yes, the tests must fail, to prove you have surfaced the defect. Push the tests as far down as you can, I.E. to unit level. When we can isolate the culprit and add failing tests to that unit it is so much more easy to fix. It is also the best way to prevent regression.
If the defect is found in your acceptance tests (slower test run), during exploratory testing, or in production, doesn’t matter. The same rule apply.
Complement you test suite with discovered new use cases
When stakeholders discover new ways to use the software, add tests to cover those scenarios. This ensures:
- The software remains reliable as new features are developed.
- Future engineers can understand multiple use cases for specific parts of the system.
Closing Note: Building trust in unit tests isn’t just about code quality — it’s about fostering collaboration, designing software for clear communication, and delivering meaningful outcomes through continuous feedback.