--- name: tdd description: Test-driven development with Unity and CMock. Use when adding new modules, writing tests, mocking dependencies, or following Red-Green-Refactor in projects scaffolded from the ctdd template. --- # TDD Skill > Replace `/` with the project source directory (e.g. `ctdd/`, `src/`). > Replace `` with the module name (e.g. `counter`, `timer`). ## Quick Reference | Action | Command | | ----------------------- | ------------------------------------------------------- | | Configure (default) | `cmake -S . -B build -G Ninja` | | Configure (coverage) | `cmake -S . -B build-cov -G Ninja -DENABLE_COVERAGE=ON` | | Build | `ninja -C build` | | Run tests (full output) | `ninja -C build check` | | Run tests (summary) | `ninja -C build test` | | Run tests + coverage | `ninja -C build-cov coverage` | ## Red-Green-Refactor Every change to `/` follows this cycle: 1. **RED** : Write a failing test. Confirm it fails. 2. **GREEN** : Write the minimum code to pass. Confirm it passes. 3. **REFACTOR** : Clean up without changing behavior. Tests still pass. Never skip the RED step. If the test passes immediately, you wrote the test before the implementation was missing. ## Adding a New Module ### 1. Write the header Create `/.h` with the public prototype(s). C: ```c #pragma once #include int fn_name(int arg); ``` C++: ```cpp #pragma once #include auto fn_name(int arg) -> int; ``` ### 2. Write the test Create `tests/test_.c`. Two patterns exist: **State-based** (pure functions, no mocks): ```c #include "unity.h" #include "/.h" void setUp(void) {} void tearDown(void) {} void test_fn_name_returns_expected(void) { TEST_ASSERT_EQUAL_INT(42, fn_name(1)); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_fn_name_returns_expected); return UNITY_END(); } ``` **Interaction-based** (mocking a dependency): ```c #include "unity.h" #include "/.h" #include "Mock.h" void setUp(void) { Mock_Init(); } void tearDown(void) { Mock_Verify(); Mock_Destroy(); } void test_fn_calls_dep(void) { dep_fn_ExpectAndReturn(arg, expected); TEST_ASSERT_TRUE(fn_name()); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_fn_calls_dep); return UNITY_END(); } ``` ### 3. Register the test in `tests/CMakeLists.txt` Add after existing test targets, before the `check` target: **State-based (no mock):** ```cmake add_executable(test_ test_.c) target_include_directories(test_ PRIVATE "${CMAKE_SOURCE_DIR}") target_link_libraries(test_ PRIVATE _ Unity::Unity) target_compile_features(test_ PRIVATE c_std_23) add_test(NAME test_ COMMAND test_) list(APPEND TEST_TARGETS test_) ``` **Interaction-based (with mock):** ```cmake add_executable(test_ test_.c) target_include_directories(test_ PRIVATE "${CMAKE_SOURCE_DIR}") target_link_libraries(test_ PRIVATE _ Unity::Unity CMock::CMock) target_compile_features(test_ PRIVATE c_std_23) cmock_generate_mock(test_ "${CMAKE_SOURCE_DIR}//.h") add_test(NAME test_ COMMAND test_) list(APPEND TEST_TARGETS test_) ``` ### 4. Stub the implementation Create `/.c` with a dummy return: C: ```c #include "/.h" int fn_name(int arg) { return 0; } ``` C++: ```cpp #include "/.h" auto fn_name(int arg) -> int { return 0; } ``` Register the library in `/CMakeLists.txt`: ```cmake add_library(_ .c) target_include_directories(_ PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") target_compile_features(_ PRIVATE c_std_23) ``` ### 5. Confirm RED ```sh ninja -C build check ``` The test must fail. If it does not, something is wrong. ### 6. Implement and confirm GREEN Write the real implementation. Run tests again: ```sh ninja -C build check ``` All tests must pass. ## CMock Patterns ### Expecting a call with arguments ```c dep_fn_Expect(arg1, arg2); ``` ### Expecting a call and returning a value ```c dep_fn_ExpectAndReturn(arg, expected_value); ``` ### Verifying a call was NOT made Do not set any `_Expect`. If the function is called, CMock fails automatically in `tearDown`. ### Multiple calls ```c dep_fn_ExpectAndReturn(first_call_arg, first_return); dep_fn_ExpectAndReturn(second_call_arg, second_return); ``` Calls are matched in order. ## Test Naming - `test__` - Use present tense: `test_upper_converts_lowercase` - Cover: happy path, edge cases, boundaries, and error conditions ## Unity Assertions | Assertion | Use | | -------------------------------- | ---------------- | | `TEST_ASSERT_TRUE(x)` | Boolean true | | `TEST_ASSERT_FALSE(x)` | Boolean false | | `TEST_ASSERT_EQUAL_INT(a, b)` | Integer equality | | `TEST_ASSERT_EQUAL_STRING(a, b)` | String equality | | `TEST_ASSERT_NULL(p)` | Null pointer | | `TEST_ASSERT_NOT_NULL(p)` | Non-null pointer | ## Coverage To check coverage after tests: ```sh ninja -C build-cov coverage ``` Open `build-cov/coverage/index.html` in a browser. ## Checklist Before considering a task done: - [ ] Header declares the public API - [ ] Test covers at least one happy path, one edge case - [ ] Test registered in `tests/CMakeLists.txt` - [ ] Library registered in `/CMakeLists.txt` (if new module) - [ ] `ninja -C build check` passes with zero failures - [ ] No semicolons after closing braces - [ ] Trailing return type on all functions - [ ] East const (`char const*`) - [ ] `snake_case` naming