-
Notifications
You must be signed in to change notification settings - Fork 0
Testing Processes
A test plan turns the practices covered in this workshop into a concrete, repeatable process. This chapter explains what a test plan should contain, why each section exists, and when to write each part. The ready-to-use template is on the Test Plan Template page.
Writing tests without a plan leads to uneven coverage: the easy paths are over-tested, edge cases and error paths are missed, and nobody knows what "done" looks like. A test plan answers three questions before a line of test code is written:
- What behaviour needs to be verified?
- At which level (unit, component, integration)?
- How will the result be assessed?
For embedded systems with hardware dependencies and target-only test execution, a test plan also documents which tests require hardware and how the hardware must be configured, so that running the test suite is repeatable by anyone on the team.
Testing is not a separate activity handed off after implementation. The developer who writes the code writes the tests for that code.
| Test level | Who writes the tests | When |
|---|---|---|
| Unit | The developer implementing the class or function | Alongside the implementation, before the branch is merged |
| Component | The developer implementing the subsystem | Before the branch is merged |
| Integration | The developer implementing the hardware adapter | Before the branch is merged; requires target hardware |
Rationale: The developer who implements a class knows its intended behaviour, its edge cases, and its failure modes better than anyone else. Separating test authorship from implementation creates a gap where assumptions go untested. Tests written by a different person after the fact tend to test the implementation that exists rather than the contract that was intended.
No implementation task is complete until its tests are written, passing, and committed. The definition of done for any development ticket that involves business logic is:
- All business logic classes have unit tests
- Unit tests cover all documented branches and edge cases
- Tests build and pass cleanly on the target
- Integration tests are tagged (
*HwIntegration*) and can be filtered in or out - No test is skipped without a comment explaining why
- The test plan (or test plan section) for the ticket is updated
This list is enforced at code review. A pull request that adds business logic without tests is not ready to merge.
All unit and component tests run automatically on every pull request. A failing test blocks the merge.
Developer pushes branch
│
▼
CI runs unit + component tests
│
Pass?──── No ──▶ PR blocked; developer fixes tests
│
Yes
│
▼
Code review (includes test review)
│
▼
Merge permitted
Integration tests that require hardware are tagged and excluded from the PR gate. They are run manually on the target before a release.
# PR gate: unit and component tests only
ctest --test-dir build --label-exclude HwIntegration
# Pre-release: integration tests only
ctest --test-dir build --label-regex HwIntegrationEvery ticket that involves implementation work uses the following template. The quality gates section is mandatory and must be filled in before work begins, not after.
Title: [Short description of the feature or fix]
Type: Feature / Bug / Refactor
Description
What needs to be built or changed, and why.
Acceptance Criteria
What must be true for this ticket to be considered complete from a product perspective.
- e.g. The valve opens within 100 ms of pressure falling below the lower threshold
- e.g. A fault is logged when pressure exceeds the safety limit
Quality Gates
Mandatory. Fill in before starting implementation.
| Gate | Requirement |
|---|---|
| Unit tests | Which classes require unit tests? List them. |
| Branch coverage | All branches in business logic classes covered |
| Integration tests | Which hardware interactions require integration tests? List them, or "none". |
| CI | All unit and component tests must pass on the PR |
| Code review | Tests reviewed alongside production code |
Definition of Done
- Implementation complete
- Unit tests written and passing for all business logic in this ticket
- Integration tests written and tagged
HwIntegration(if applicable) - CI green on the pull request
- Code review approved (production code and tests reviewed together)
- Test plan updated if this ticket changes the scope of an existing plan
- No skipped tests without a documented reason
Test Notes
Any specific test scenarios, edge cases, or boundary values the reviewer should look for.
Tracks ownership and version history. In a codebase under version control the test plan is a committed document, so changes are traceable. The header records who is responsible for the plan and which software version it covers.
Fields: title, component or module name, author, date, version, status (draft / reviewed / approved).
States explicitly what is and is not covered by this plan. Scope prevents the plan from growing unbounded and makes clear when a gap is intentional rather than an oversight.
Include: which classes, modules, or subsystems are tested. Exclude: what is covered by another plan or is outside the current testing effort.
One or two sentences stating the quality goal of the testing effort. The objective gives reviewers a way to judge whether the plan is sufficient. For embedded work a typical objective is: "Verify that all business logic in [component] behaves correctly across its full input range without requiring hardware, and verify that the hardware adapters interact correctly with the target at the integration level."
Defines which testing pyramid levels apply to this component and what is expected at each. This section prevents misunderstandings about what counts as a unit test versus an integration test.
| Level | Scope | Dependencies | Run on |
|---|---|---|---|
| Unit | Single class | All faked | Target (no hardware state required) |
| Component | Subsystem of classes | Key interfaces faked | Target |
| Integration | Full stack | Real hardware | Target with hardware connected |
Describes the physical setup needed to run integration tests. This is the section a new team member reads to set up their environment. It covers: which target board, which hardware peripherals, power supply requirements, connection diagrams or port assignments, and any setup commands that must be run before the test suite executes.
Unit and component tests must be runnable with no hardware state required beyond having the target board powered on. This should be stated explicitly.
The core of the plan. Each test case covers one behaviour. The format is consistent so cases can be reviewed, tracked, and reported against.
For each test case:
-
ID: unique identifier, e.g.
TC-FLOW-001 - Description: one sentence stating what is being verified
- Level: unit / component / integration
- Preconditions: the state the system or test doubles must be in before the test runs
- Steps: the actions the test performs
- Expected result: the exact observable outcome that constitutes a pass
- Hardware required: yes / no, and if yes, what
States the minimum acceptable coverage for the component. For embedded business logic the goal should be 100% branch coverage of all business logic classes. Hardware adapters are covered by integration tests rather than coverage metrics.
Also states which coverage tool is used and how to run it, so results are reproducible.
Defines what constitutes a successful test run for the whole plan. Avoids ambiguity at sign-off time.
Example: "All unit and component tests pass with zero failures. All integration tests pass on the target with the reference hardware configuration. Branch coverage for business logic classes is at or above 100%. No test is marked as skipped without a documented reason."
Documents what the plan does not cover and why. Examples: a vendor library whose internals cannot be observed, a hardware feature not present on the development board, or a scenario that requires a specific failure mode that cannot be safely induced on the target.
A table of changes to the plan itself. Required when the plan is a reviewed or approved document. Each row records the version, date, author, and a brief description of what changed.
| Stage | Action |
|---|---|
| Feature design | Write Scope and Objectives |
| Interface definition | Write Test Levels and identify test cases |
| Implementation | Write detailed test cases and implement tests |
| Code review | Review test cases alongside production code |
| Release | Confirm Pass/Fail Criteria are met, update Revision History |
The plan is a living document. Test cases are added as the implementation grows, and Known Limitations are updated when gaps are discovered.
Each module in this workshop has an implicit test plan. The table below maps the plan components to the workshop content.
| Plan component | Workshop content |
|---|---|
| Scope | Module README: what is and is not compiled |
| Test levels | Test Architecture Patterns: pyramid, unit vs integration |
| Hardware requirements | Module 03 and 07: hardware tags, --gtest_filter
|
| Test cases | Each TEST_F in the module's test file |
| Coverage goals | Business logic classes: 100% branch coverage |
| Pass/fail criteria | All tests pass, integration tests tagged and filterable |
See the Test Plan Template for the ready-to-fill Markdown document.
See also: Test Architecture Patterns, Deterministic Testing
- Home
- Test Architecture Patterns
- Designing for Testability
- Effective Use of Test Doubles
- Refactoring Toward Testability
- Deterministic Testing
- Testing Processes
- Test Plan Template
- Development Ticket Template
- Black-Box Testing from Doxygen
Module 01 — Code Structure
Module 02 — Unit Tests
Module 03 — Hardware Dependencies
Module 04 — Qt Signals & Slots
Module 05 — Dependency Injection