Code Coverage refers to the measurable portion of program code that is actually executed by tests. The term originates from software quality assurance and is primarily relevant in software verification and validation. In embedded development, however, code coverage has a significantly greater practical importance than in many classic IT applications because here software is directly linked to physical hardware, safety-critical functions, and often hard-to-reach error scenarios.
Content
What is code coverage needed for?
Code coverage essentially answers the question: Which parts of the code were actually reached by my tests? This isn't about whether the test „passed,“ but whether specific statements, branches, conditions, or paths were executed at all.
Typical types of code coverage are:
- Statement Coverage
Measures which individual instructions have been executed at least once. - Branch Coverage
Checks if both directions of a branch have been tested, for exampleIfandelse. - Condition Coverage
Measures whether individual logical subconditions within complex expressions were true or false. - Path Coverage
Consider complete execution paths through the code. This form is very complex, as the number of possible paths grows rapidly. - Function Coverage
Measures which functions were called by tests.
In many projects, code coverage is expressed as a percentage: „85% % statement coverage“ or „100% % branch coverage.“ At first glance, this figure seems clear-cut, but it is not, in and of itself, proof of quality. High coverage does not automatically mean good tests. It only shows that a lot of code was executed. Whether this code was tested with meaningful inputs, realistic error cases, and plausible boundary values is another question.

This is precisely why code coverage is often misunderstood. It is not proof of error-free operation. Code coverage is an analysis tool to reveal blind spots in the testing system, such as dead code. If certain code segments are never executed, then there is effectively no test assertion there. Without coverage measurement, this often goes unnoticed in many projects.
Code Coverage for Embedded Systems
For embedded systems, code coverage is particularly important because several boundary conditions come together there, making testing difficult:
First, embedded software is often tightly coupled to real hardware. Parts of the code react to interrupts, timers, DMA, peripheral registers, communication frames, or error states of external components. Such situations often do not occur completely or only randomly during normal functional testing. Without coverage analysis, it remains unclear whether these sections have ever been tested.
Second, embedded systems often contain many exception and fallback paths. These include watchdog reactions, error handling for communication failures, voltage issues, memory errors, timeouts, invalid sensor data, or startup problems during the boot process. These paths are rarely visible in everyday use precisely because they are only activated under fault conditions. However, from a safety and reliability perspective, they are often more important than the nominal main path. Code coverage helps to check whether such error handling mechanisms have ever been deliberately triggered and tested.
Third, embedded products are often durable and operate in industrial, mobile, safety-critical, or harsh environments. Failures manifest not only as application crashes but potentially as device downtime, faulty readings, communication failures, or dangerous system states. In such environments, superficial functional testing is insufficient. The question of which parts of the code have actually been verified becomes a technical and often regulatory necessity.
Fourth, observability in embedded systems is often worse than in PC or cloud software. There is usually no complete logging, no convenient runtime inspection, and no easy reproducibility of all states. Coverage measurement creates an additional layer of transparency here. It shows what really happened on the target or in the test environment during a test.
Safety-critical electronics
Another important point is the role of code coverage in regulated or safety-critical development processes. In areas such as automotive, railway, aerospace, industrial safety, or medical technology, test coverage is often evaluated not only technically but also procedurally. There, coverage serves as proof that requirements and implementation have been systematically verified. This becomes particularly relevant in conjunction with standards and methods such as structural coverage, MC/DC, or requirement-specific test case derivation.
In an embedded context, a distinction is often made between tests at different levels:
- Unit Tests on Host or Target
- Integration tests with real interfaces
- Low-level hardware tests
- System tests
- Fault injection or robustness testing
Code coverage can be valuable at all of these levels. Unit Test often achieves good coverage in isolated modules, while hardware-dependent states are not mapped there at all. A system test covers real processes better, but often covers specific rare branches less precisely. Therefore, coverage is particularly useful when it is not considered in isolation, but as a combination of several test levels.
More on code coverage in a safety-critical context in this post by Martin Heininger.
Meaning of Code Coverage
Why is code coverage specifically important in embedded projects?
Visibility for untested code
In established embedded codebases, there are often old functions, legacy routines, special cases, or defensive programming parts that no one actively looks at anymore. Coverage makes visible which parts are included in the build but are never tested. This is particularly relevant for ported software, platform changes, or long product cycles.
Proof of fault tolerance and robustness
The most interesting bugs are often not in the nominal main path, but in special cases. Coverage shows whether recovery strategies, error flags, retry mechanisms, timeout handling, or safe state transitions have been tested at all.
Better test prioritization
When coverage data is available, test gaps can be specifically addressed. Instead of writing more arbitrary tests, the team can specifically look at unreached branches, conditions, or functions.
Assistance with refactoring and porting
Especially with embeddedPortings on new Microcontroller, Coverage is valuable regardless of RTOS versions, compilers, or hardware platforms. It helps assess whether the existing test base continues to reach the relevant code after a change, or if important paths have been lost.
Support for Safety and Security
Even though coverage alone does not guarantee safety or security, it is an important foundation. Safety-relevant plausibility checks, authorization checks, secure boot decisions, error reactions, or communication validations should not only exist but demonstrably be executed in testing.
Reduction of false security
Many projects have extensive test suites, yet still have significant gaps. Without coverage, it's easy to get the impression that „a lot has been tested.“ Coverage forces a sober assessment: What has actually been executed, and what hasn't?
Rating and Metrics
It is particularly important to distinguish between good and bad use of coverage. Bad use means optimizing solely for a target number, such as „we need 90 %.“ This often leads to writing trivial tests that merely touch a few lines of code but provide little meaningful insight. Good usage means using coverage as a diagnostic tool: Which security-relevant, error-critical, or hardware-dependent parts are still untested? Where are targeted negative tests missing? Which branches have never been reached?
In embedded systems, technical challenges also arise with coverage measurement itself. Unlike desktop software, the code often runs on microcontrollers with limited memory, restricted runtime observation, or hard real-time requirements. Instrumentation for coverage can alter timing, consume memory, or falsify certain effects. Therefore, it must always be evaluated where and how coverage is measured:
- on the host in a simulated environment,
- on the real target,
- with compiler instrumentation,
- about trace hardware,
- or in combination of different methods.
Every method has limitations. Host-based tests are faster and easier to automate, but often only incompletely represent register accesses, interrupt timing, or hardware errors. Target-based measurements are more realistic, but more complex and expensive. Therefore, a hybrid approach makes sense for many embedded projects.
Another aspect: not all code should be evaluated equally. In embedded projects, there are often sections that are difficult or only indirectly testable, such as startup code, compiler-generated code, hardware abstraction layers, exception handlers, bootloader parts, or diagnostic paths under rare hardware errors. These parts should not simply be ignored, but they often require separate justification. Good development processes therefore also document why certain areas were not covered by coverage or were only covered to a limited extent.
In the safety-related environment, the term "structural coverage" is also frequently used. This refers to the systematic analysis of which program structures have been activated by tests. Different requirements may apply depending on the standard. MC/DC (Modified Condition/Decision Coverage) is particularly well-known in this context, aiming to prove that individual conditions within a decision can independently influence the outcome. This is especially relevant for safety-critical logic, such as in protection functions, state machines, or enable mechanisms.
Summary
Code coverage is not an end in itself or a metric solely for management slides. In embedded development, it is primarily valuable because it makes visible the gap between existing tests and actually executed code. It doesn't show whether a product is good, but it very clearly shows where no robust test assertion currently exists.
Code coverage is a tool for evaluating test coverage at the code level. It helps to make untested instructions, branches, conditions, and error paths visible. It is particularly important in embedded systems because hardware-dependent states, exceptional cases, recovery mechanisms, and safety-relevant functions often cannot be reached automatically by standard tests.
For electronics and embedded development, this specifically means: Those who only test normal operation often fail to test the parts where systems fail in the field. Code coverage makes these gaps visible and is therefore a central tool for robust software, traceable verification, and reliable development processes.
Zurück zum Glossar