Caswell Software Advisory Ltd

When green test results don’t mean the system works

2026-08-24T09:00:00.000Z

Most engineers know that some software is more difficult to build and test than others.

Fewer of us realise that we need to change the strategy we use to be confident that the system actually works.

It’s worth noting in advance that there are generally two types of system; namely complex and complicated. A complicated system is difficult to build, it has challenging algorithms, difficult maths and so on but given the same input parameters the results are usually the same (provided system behaviour can be determined in advance). As such your testing strategy can reflect that, you write the tests and execute them systematically until all of the algorithms and functionality in the system has been covered. When all tests go green you have a high degree of confidence that the system is correct over the tested input/state space.

Complex systems don’t work this way. They can also contain challenging algorithms, difficult maths etc but they also behave differently. A complex system may be distributed, multi-process, multi-threaded or executing on bespoke hardware or with an RTOS. In such systems timing, concurrency, shared state and the real hardware can create emergent behaviour that no single component will exhibits when tested in isolation. This transition, from complicated to complex, often happens without anyone explicitly realising. When this happens, often at integration time, the testing strategy often remains unchanged.

Which is where it can get dangerous.

When the shift isn’t recognised, organisations continue to run functional test suites with code coverage metrics. All the tests pass, go green, and the organisation ships the product with confidence - without realising there’s a entire category of bugs in the product present simply because of its nature.

Such bugs may surface in the field, given enough time or users. Given Murphy’s law we should probably be cautious. It may be a timing-dependent bug or something that requires a specific state or interrupt latency. It’s often, as I’ve found out from personal experience, a race between two components - each of which passed all of their functional tests. The tests were incapable of detecting the bug because the environment they were running in could not provide the conditions necessary to replicate it.

What makes this awkward is that more testing of the wrong type creates an illusion of quality. A complex product with 100% code coverage tests still contains residual risk. Teams that recognise this are in a much better position than those that think “all tests passed, we’re good to go” as they’re able to manage the risk honestly.

The testing strategies for a complex system have to look different. You have to:

Testing a complex system is not just about proving correctness. It’s about surfacing behaviour that emerges and testing that.

It’s not that any of this is exotic, we’re all familiar with soak and stress testing. What I think is interesting and perhaps a little uncomfortable is accepting that a complex system can’t be exhaustively verified. There’s likely to be some residual side case that can’t be tested repeatably - understanding and accepting this uncertainty is part of the job.

Convincing people that green functional test results on a complex system doesn’t necessarily mean the system is bug free is often as difficult as the engineering itself.

But pretending otherwise is where the real risk lies.