| name | tdd-cycle |
| description | Guide a strict test-driven development cycle for a Rexio component. Enforces RED-GREEN-REFACTOR: write failing test first, then implement minimum code to pass, then refactor. Use when implementing component logic, or when the user says /tdd-cycle. |
TDD Cycle Skill — Rexio Flight Controller
You are guiding a strict RED-GREEN-REFACTOR test-driven development cycle for the Rexio
flight controller project. You MUST enforce the discipline at every step. No shortcuts.
Prerequisites
Before starting a cycle, gather context:
- Identify the target component. The user will specify a component name (e.g.,
pid,
mixer, commander, imu, estimator, topics, blackbox).
- Read the component header at
components/rexio_{name}/include/{name}.h to understand
the public API — function signatures, typedefs, enums, struct definitions.
- Read
references/test_patterns.md (relative to this skill) to select the correct test
pattern for the component type:
- Pure logic (PID, mixer): no mocks needed, direct input/output testing.
- State machine (commander): mock time, test state transitions.
- HAL-dependent (IMU, baro, sonar): mock HAL vtables via
test/mocks/.
- Vtable/interface (estimator, controller): instantiate concrete impl, test via ops.
- Topic bus: test publish/subscribe/generation counter.
- Read the existing test file
test/test_{name}.c if it exists, to understand what is
already tested and avoid duplicating coverage.
- Read the existing implementation at
components/rexio_{name}/src/{name}.c if it exists,
to understand what is already implemented.
- Identify which functions from the header still need implementation and tests.
Execution Order
Process functions in dependency order: helper/utility functions first, then the functions
that call them. For each function, execute a full RED-GREEN-REFACTOR mini-cycle before
moving to the next function.
Phase 1 — RED (Write Failing Test)
Steps
-
Choose the next function to implement from the component's header.
-
Write test case(s) in test/test_{name}.c:
- Add a
void test_{function_name}_{scenario}(void) function.
- Wire it into the test runner's
main() with RUN_TEST(test_{function_name}_{scenario}).
- In
setUp(), initialize mock HAL vtables if the component uses hardware:
g_hal_i2c = &mock_hal_i2c; for I2C-dependent components.
g_hal_time = &mock_hal_time; for time-dependent components.
g_hal_gpio = &mock_hal_gpio; for GPIO-dependent components.
- Call the appropriate
mock_*_reset() to clear state.
- Set up preconditions: configure mock expectations, set initial state, prepare input data.
- Call the function under test with specific inputs.
- Assert expected outcomes using Unity assertions:
TEST_ASSERT_EQUAL(expected, actual) for integers and enums.
TEST_ASSERT_EQUAL_UINT16(expected, actual) for unsigned 16-bit values.
TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual) for floating-point with tolerance.
TEST_ASSERT_TRUE(condition) / TEST_ASSERT_FALSE(condition) for booleans.
TEST_ASSERT_NOT_EQUAL(unexpected, actual) for negative checks.
TEST_ASSERT_NULL(ptr) / TEST_ASSERT_NOT_NULL(ptr) for pointers.
TEST_ASSERT_EQUAL_MEMORY(expected, actual, len) for byte buffers.
- For mock-based tests, call
mock_*_verify() after the function under test to confirm
all expected HAL interactions occurred.
-
Follow code style rules:
snake_case for all function and variable names.
- Use SI units in test data: m/s^2 for accel, rad/s for gyro, Pa for pressure, m for altitude.
- No dynamic memory allocation in tests — use stack variables.
- Keep tests focused: one logical assertion per test function where practical.
-
Run make test.
-
CRITICAL GATE — The test MUST fail.
- Acceptable failures: compile error (function not defined), linker error (symbol not found),
assertion failure (wrong return value).
- If the test PASSES: STOP IMMEDIATELY. The test is wrong — it does not actually test
new behavior. Diagnose why:
- Is the function already implemented and the test duplicates existing coverage?
- Is the assertion vacuous (e.g., comparing a variable to itself)?
- Is the mock not actually being used, so the real (or default) behavior satisfies the test?
- Fix the test so it fails, then re-run
make test to confirm the failure.
- Report: "RED PHASE FAILED — test passes without implementation, test is incorrect"
and explain what was wrong.
-
On successful failure, report:
"RED PHASE COMPLETE — test fails as expected: {failure message}"
Include the specific error or assertion failure message.
Phase 2 — GREEN (Minimum Implementation)
Steps
-
Open components/rexio_{name}/src/{name}.c.
-
Write the MINIMUM code to make the failing test pass:
- Implement ONLY the logic that the test exercises.
- Do NOT add error handling that is not tested.
- Do NOT add optimizations.
- Do NOT add features beyond what the test requires.
- It is acceptable (and expected) for the first implementation to be naive or ugly.
- Follow code style:
snake_case for functions and variables.
UPPER_CASE for macros and constants.
_t suffix on all typedefs.
- All public functions return
rexio_err_t (0 = REXIO_OK, negative = error).
- SI units for all physical quantities.
- No
malloc, calloc, or realloc in flight-critical code.
-
If the function needs a new #include, add it. If a new struct or constant is needed
in the header, add it to the header — but only what the current test demands.
-
Run make test.
-
CRITICAL GATE — ALL tests must pass.
- If the new test fails: fix the implementation. Do NOT modify the test unless you
discover a genuine bug in the test logic (e.g., wrong expected value due to a math error).
- If a previously passing test now fails: you broke something. Investigate and fix the
implementation so all tests pass. Do not delete or weaken existing tests.
- Re-run
make test after every fix until all tests are green.
- Do NOT proceed to REFACTOR until the full test suite is green.
-
Report: "GREEN PHASE COMPLETE — all tests pass"
Include the test count (e.g., "12 Tests 0 Failures 0 Ignored").
Phase 3 — REFACTOR (Clean Up)
Steps
-
Review the implementation you just wrote for:
- Naming: Are all names
snake_case? Do typedefs end in _t? Are names descriptive?
- Magic numbers: Extract to
#define or static const with a meaningful name.
- Duplication: If the same pattern appears in multiple places within this function,
consider extracting a helper. But do NOT over-abstract for a single use.
- Comments: Add a brief comment for non-obvious logic. Do not comment the obvious.
- Header consistency: Does the implementation match the header declaration exactly?
- Edge cases: Are there obvious edge cases the test did not cover? If so, note them
and go back to RED to add tests for them before implementing the handling.
-
Review the test you just wrote for:
- Clarity: Can someone reading the test understand what behavior it verifies without
reading the implementation?
- Independence: Does the test depend on other tests' side effects? If so, fix it.
- Boundary values: Did you test at least one boundary condition (zero, max, negative)?
If not, note it as a follow-up cycle.
-
Make refactoring changes if needed. Keep changes small and behavior-preserving.
-
Run make test to confirm nothing broke.
-
Run make lint to confirm formatting.
- If lint fails, run
make format to auto-fix, then re-run make lint to verify.
-
Report: "REFACTOR PHASE COMPLETE — tests still pass, lint clean"
Anti-Patterns — Reject These
If the user (or your own instinct) suggests any of the following, refuse and redirect:
| Anti-pattern | Response |
|---|
| "Let me write the implementation first and add tests after" | NO. The test comes first. Always. Go to Phase 1 — RED. |
| "This is trivial, let's skip the test" | NO. Trivial tests catch regressions. Trivial code has trivial tests — write them fast and move on. |
| "The test passes without me writing any implementation" | The test is WRONG. It does not test the behavior you think it tests. Stop and fix it. |
| "Let me write all the tests for every function, then implement them all" | Partially acceptable: you may write all tests for a SINGLE function's behavior at once. But do not write tests for function B before function A is green. |
| "Let me skip the lint step" | NO. Lint is enforced by pre-commit hook. Fix it now or it blocks the commit. |
| "Let me weaken the test to make it pass" | NO. The test defines the required behavior. Fix the implementation to match the spec, not the other way around. Only modify a test if you find a genuine error in test logic. |
| "Let me add extra features while I'm in here" | NO. Only implement what a test requires. Unneeded code is untested code. Write a test for the feature first. |
Iteration Protocol
After completing one full RED-GREEN-REFACTOR cycle for a function:
- Check: are there more functions in the header that need implementation?
- If yes: start a new RED phase for the next function.
- If no: proceed to the final summary.
Between cycles, do a quick sanity check:
- Run
make test to confirm the full suite is still green.
- If anything is red, fix it before starting the next function's cycle.
Updating Documentation
After all cycles are complete:
- Check if
CHANGELOG.md exists at the project root. If so, add an entry under the
appropriate section (Added/Changed/Fixed) describing what was implemented.
- Check if the component has documentation that needs updating (e.g., comments in the
header file, entries in
docs/architecture.md).
- Do NOT create new documentation files unless the user explicitly asks.
Final Summary
When all requested functions have been implemented through TDD cycles, report:
## TDD Cycle Summary
### Component: {name}
| Function | RED (fail) | GREEN (pass) | REFACTOR |
|----------|-----------|-------------|----------|
| {func1} | {fail msg} | {pass count} | Clean |
| {func2} | {fail msg} | {pass count} | Clean |
| ... | ... | ... | ... |
### Stats
- Functions implemented: {N}
- Tests added: {N}
- Total test suite: {N} tests, 0 failures
- Lint: clean
### Notes
- {Any edge cases deferred, issues encountered, or follow-up work needed}
Quick Reference — File Locations
| What | Where |
|---|
| Component header | components/rexio_{name}/include/{name}.h |
| Component source | components/rexio_{name}/src/{name}.c |
| Component CMake | components/rexio_{name}/CMakeLists.txt |
| Test file | test/test_{name}.c |
| Mock HAL files | test/mocks/mock_hal_*.h and test/mocks/mock_hal_*.c |
| Test CMake | test/CMakeLists.txt |
| Topic data structs | components/rexio_topics/include/topic_data.h |
| Error codes | components/rexio_common/include/rexio_err.h |
| Test patterns ref | This skill's references/test_patterns.md |