Combine your software and tests
As software engineers, we’re taught that we should write tests. Tests to examine our software as it hurtles toward production. They stop us breaking things as we add or change features.
As a technical leader, I value that testing code. I spend money making sure that it exists.
But we’re also taught to leave tests behind. To jettison them at the end of our release pipeline.
We have other stuff we use in production. Distinct from tests, we also try to make our systems Observable.
Observability is a measure of how well internal states of a system can be inferred from knowledge of its external outputs.
We’re missing a trick
Testing behaviour before, and observing behaviour after, are almost the same thing. They’re not identical. But there is a massive overlap.
We shouldn’t write tests as software modules we jettison before production. Instead, the majority of tests should be a part of the final product. A product in production must observe its own wellbeing.
Testing before we go live should be listening to the product’s wellbeing. Observability after we go live should be the story the product tells us about itself.
A small set of smoke tests will still be necessary to check the software runs. And, we should test that the product’s self-knowledge is accurate. But generally, most of our test logic should go into production.
Lets imagine a test for an e-commerce website. We might want to test that a customer can:
- Add a product to a cart
- Proceed to checkout
- Complete their purchase
We could write component tests to verify that our UI functions as we expect. Or, we could write edge-to-edge tests against our API code. We may test the journey using Playwright or Selenium by simulating user actions.
But note that, for each of these, we’re create redundant copies of the logic:
- We write a test to express the intent, and verify, the business logic
- We implement the business logic as a feature in our product
- We code (or configure) a tool to observe the business logic
Redundancy isn’t bad. But, why not build the functionality to measure those things into the software itself?
It’s good to say what we mean twice (test and implementation). It helps us think about what we’re doing and communicate (TDD). Redundancy is good. But we can reuse that test logic to make the system self aware. Engineers should combine tests and software together.