chore: add pi skills for tdd, cmake, and git
Create .pi/skills/ with three skills: - tdd: Red-Green-Refactor workflow, Unity assertions, CMock patterns - cmake: build commands, adding modules, dependency FetchContent pattern - git: conventional commits, semver tagging, branching conventions Update AGENTS.md and README.md to reference the skills and template project description.
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
---
|
||||
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 this C23 project.
|
||||
---
|
||||
|
||||
# TDD Skill
|
||||
|
||||
## 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 `ctdd/` 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 `ctdd/<module>.h` with the public prototype(s).
|
||||
|
||||
```c
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
|
||||
auto fn_name(int arg) -> int;
|
||||
```
|
||||
|
||||
### 2. Write the test
|
||||
|
||||
Create `tests/test_<module>.c`. Two patterns exist:
|
||||
|
||||
**State-based** (pure functions, no mocks):
|
||||
|
||||
```c
|
||||
#include "unity.h"
|
||||
#include "ctdd/<module>.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 "ctdd/<module>.h"
|
||||
#include "Mock<dep>.h"
|
||||
|
||||
void setUp(void) { Mock<dep>_Init(); }
|
||||
void tearDown(void) { Mock<dep>_Verify(); Mock<dep>_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_<module> test_<module>.c)
|
||||
target_include_directories(test_<module> PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
target_link_libraries(test_<module> PRIVATE ctdd_<module> Unity::Unity)
|
||||
target_compile_features(test_<module> PRIVATE c_std_23)
|
||||
add_test(NAME test_<module> COMMAND test_<module>)
|
||||
list(APPEND TEST_TARGETS test_<module>)
|
||||
```
|
||||
|
||||
**Interaction-based (with mock):**
|
||||
|
||||
```cmake
|
||||
add_executable(test_<module> test_<module>.c)
|
||||
target_include_directories(test_<module> PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
target_link_libraries(test_<module> PRIVATE ctdd_<module> Unity::Unity CMock::CMock)
|
||||
target_compile_features(test_<module> PRIVATE c_std_23)
|
||||
cmock_generate_mock(test_<module> "${CMAKE_SOURCE_DIR}/ctdd/<dep>.h")
|
||||
add_test(NAME test_<module> COMMAND test_<module>)
|
||||
list(APPEND TEST_TARGETS test_<module>)
|
||||
```
|
||||
|
||||
### 4. Stub the implementation
|
||||
|
||||
Create `ctdd/<module>.c` with a dummy return:
|
||||
|
||||
```c
|
||||
#include "ctdd/<module>.h"
|
||||
|
||||
auto fn_name(int arg) -> int {
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Register the library in `ctdd/CMakeLists.txt`:
|
||||
|
||||
```cmake
|
||||
add_library(ctdd_<module> <module>.c)
|
||||
target_include_directories(ctdd_<module> PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
target_compile_features(ctdd_<module> 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_<function>_<scenario>`
|
||||
- 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 `ctdd/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
|
||||
Reference in New Issue
Block a user