📖Test-Driven Development for Embedded C

Grenning, James
  • You can test external libraries
  • _

    Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, be definition, not smart enough to debug it. —Brian Kernigan

  • (p.2)

    The biggest schedule killers are unknowns; only testing and running code and hardware will reveal the existence of these unknowns.

  • (p.4) software is fragile
  • immediate feedback

    • show diagram with cost of a bug and where TDD stands
  • with Debug Later Development the feedback is too late to help you learn from your mistakes
  • I’ll be discussing a very fine-grained test increments. Once you’ll get comfortable with TDD, you can move in larger steps. Anyway, I do recommend trying to move with small increments.
  • TDD micro-cycle

    1. Add a small test
    2. Run all the tests and see the new one fail, maybe not even compile
    3. Make the small changes needed to pass the test.
    4. Run all the tests and see the new one pass.
    5. Refactor to remove duplication and improve expressiveness.
  • ^ Red-Green-Refactor
  • how to work with legacy code
  • p.8 - TDD benefits
  • run tests on development system, not a target platform
  • automatically build and run tests whenever a file is saved
  • cmocka
  • modularity is needed to make testable code. Also, the modular design is a natural outcome of TDD (p.27)
  • typedef struct CircularBufferStruct *CircularBuffer

    • you can define pointers to forward-declared structs. it is fine to use them as long as you do not dereference. the struct then is defined in the .c file and is “private”
  • (p.27) ADT definition by Barbara Liskov
  • keep test list (it’s just for you. don’t work too hard creating it)
  • “virtual device”
  • dependency injection

    • (pass MMIO addresses as parameters)
  • it is tempting to write the code that you know you will need—don’t do it. You’ll write them after a test that requires that.
  • 3 laws of TDD (p.37)

    1. Do not write production code unless it is to make a failing unit test pass.
    2. Do not write more of a unit test than is sufficient to fail, and build failures are failures.
    3. Do not write more production code than is sufficient to pass the one failing unit test.
  • q

    The code behind the interface starts with hard-coded return results, so it feels like nothing is being tested. The point is not testing but driving the interface design and getting the simple boundary tests in place. —p.37

  • starting with failing test ensures the test is correct
  • “fake it till you make it” (p.43)

    • You stop faking as soon as it is more trouble to fake it than it is to make it.
  • Keep tests small, focused
  • refactor on green
  • tests are code, too—refactor
  • q

    Idealism increases in direct proportion to one’s distance from the problem. —John Galsworthy

  • “do you have a test for that?”

    • the question is esp. useful in pair-programming
  • when test fails, don’t debug. undo and inspect your work
  • dual-targeting - your code is designed to run on both target platform and development platform
  • you don’t need hardware to develop and debug most of the issues
  • long upload (flashing) times -> lead to larger batch increments -> more rare feedback -> more can go wrong
  • dual-targeting affects architecture but in a good way
  • there are pitfalls to dual-targeting - different compilers could have different bugs, different headers, different data sizes
  • when dual-targeting from the start, it is easier to port on new platform in the future
  • Embedded TDD cycle:

    1. TDD microcycle
    2. Compiler compatibility check (cross-compile to target)
    3. Run tests on eval board
    4. Run on target hardware
    5. Run acceptance tests on hardware
  • extract platform-specific code to its own directory and use linker + compiler (preferably not pre-processor)
  • testing with hardware

    • automated hardware tests (“hardware acceptance tests”)
    • partially-automated (e.g., look that the correct LED is on)
    • tests with external instrumentation
  • p.91 “TDD helps you go faster”
  • TDD != unit-testing
  • p.97 benefits of TDD over test-after development

    • TDD influences design (in a good way)
    • TDD prevents defects
    • TDD saves time debugging
    • TDD is more rigorous and provides better tests coverage
  • test-double (stunt-double = дублер) impersonates some function, data, module, or library