Back to Home

Unit testing embedded: principles

The article analyzes the advantages of unit testing in embedded development: control, refactoring, documentation. Describes disadvantages with solutions and 20 principles of writing tests, including AAA and HIL on target. Suitable for middle/senior firmware developers.

How to implement unit tests in embedded firmware
Advertisement 728x90

Unit Testing in Embedded Development: Principles and Best Practices

Unit testing firmware for embedded systems lets you verify code functionality, enable safe refactoring, and document APIs. The code author writes the initial test suite, while colleagues add independent checks. This is especially useful for protocols like UDS that involve DTC requirements.

Benefits of Implementing Unit Tests

Unit tests involve calling a function with fixed inputs and verifying the output, similar to SIL testing. They address several key challenges:

  • Functionality verification: Tests confirm the code behaves correctly.
  • Safe refactoring: Changes won't break existing functionality.
  • Documentation: Usage examples are clear in tests, no need for extra comments.
  • Completion criteria: Move to the next component only after tests pass.
  • Contribution tracking: Leads can require tests to validate developer work.

Tests shift responsibility from the author: if code passed yesterday, the issue lies elsewhere. They spot unreachable code via coverage metrics and promote modular structure, avoiding spaghetti code with massive functions and magic numbers.

Google AdInline article slot

Think of it like PCB design: add test points during schematic layout, not after soldering. Same for software—write small functions from the start. Tests ensure portability across platforms (ARM Cortex-M, RISC-V, PowerPC) and compilers (GCC, IAR, Clang). Regression testing catches when new changes break old code.

Running tests is free and fast, unlike manual testing prone to human bias. For managers, it's a progress metric. Debugging gets easier: test reports pinpoint the faulty function.

Drawbacks and Workarounds

Unit tests have limitations, but standard practices solve them.

Google AdInline article slot
  • Memory constraints: On MCUs with limited Flash, split tests into groups and run sequentially via bootloader and CLI. Automate in CI/CD (Jenkins). Or test on MCUs with more Flash and port the validated code.
  • Test bugs: Use code reviews and write robust, failure-resistant tests.
  • Overkill: Focus on code coverage (tools like Testwell CTC++) or spec requirements. One test per requirement.
  • No integration coverage: Pair with integration tests. The classic subway door unit test meme shows unit tests alone don't guarantee the full system.

Principles for Writing Unit Tests

The module author writes the first suite—they know the domain best. Separate code and tests into distinct directories.

  • Each test checks one thing.
  • Before fixing a bug, write a test for it.
  • Determinism: No rand(), use fixed inputs.
  • Simplicity: Short, readable, fits on one screen.
  • AAA: Arrange (setup), Act (action), Assert (verify).
  • No if statements—use ASSERT macros.
  • Cover edge cases.
  • Integrate with CI, run via CLI (UART).
  • Refactoring resilient.
  • Test order doesn't affect outcomes.
  • Run on target (HIL) via Shell/Putty.

Tests either pass or get deleted. Fixing them takes priority over production code. Peer-written tests boost unbiased coverage.

Test frameworks can grow complex, outpacing the product—like testing audio codecs (SGTL5000, MAX9860). But the discipline pays off in reliability.

Google AdInline article slot

Key Takeaways

  • Start unit tests from day one for modularity and portability.
  • Write your own tests: deep understanding speeds things up.
  • Stick to AAA and determinism for rock-solid results.
  • Run on target via CLI—essential for HIL validation.
  • Supplement with integration tests for the full picture.

— Editorial Team

Advertisement 728x90

Read Next