Hardware-connected software fails at boundaries: timing edges, flaky links, and rare protocol states. A strong test strategy must combine fast feedback with realistic integration checks. This post describes a practical testing pyramid for Rust projects in this space.
1. Layered testing model
I use four layers:
- unit tests for pure logic
- contract tests for interfaces
- integration tests with service dependencies
- hardware-in-loop tests for real-world validation
Most bugs should be caught in lower layers because they are faster and cheaper.
2. Unit tests: maximize deterministic coverage
Target pure functions and state transitions:
- parsers
- validators
- retry decision logic
- state machine transitions
Unit tests should run in seconds and be mandatory in every CI run.
3. Contract tests for boundaries
For device protocols and adapters, define contracts:
- accepted message formats
- timeout behavior
- error mapping guarantees
- idempotency expectations
These tests prevent accidental interface breakage during refactors.
4. Integration tests with controlled dependencies
Run service-level tests against disposable dependencies:
- ephemeral database
- local message broker
- mocked auth service
Validate startup, migration, and failure recovery paths. Integration tests should include at least one induced dependency outage.
5. Hardware-in-loop tests
HIL tests are slower but essential. Keep them focused:
- boot and initialization path
- real sensor read sequence
- long-running stability sample
- error injection when feasible
Do not duplicate all unit cases in HIL. Use HIL for behavior that cannot be simulated faithfully.
6. CI pipeline segmentation
Suggested CI stages:
- lint + format + unit tests
- contract + integration tests
- scheduled HIL suite (nightly or per release branch)
This keeps developer feedback fast while still providing realistic confidence.
7. Test data and reproducibility
For unstable hardware inputs, capture and version representative datasets. Replay them in deterministic tests to prevent regressions.
Record environment metadata in HIL runs:
- firmware version
- board revision
- kernel version
- test start time and duration
Without metadata, failures are hard to compare across runs.
8. Quality gates and release policy
Define explicit release gates:
- no failing unit or contract tests
- integration pass rate above threshold
- latest HIL run green within defined time window
If gates are negotiable under schedule pressure, quality will degrade quickly.
Final note
A good testing pyramid balances speed and realism. In Rust hardware projects, this discipline turns intermittent field failures into reproducible bugs you can actually fix.