A C23 template project wired for test-driven development using Unity and CMock. Use it as the core template when scaffolding new projects.
All dependencies are fetched automatically via CMake FetchContent with no
manual installation required beyond the tools listed below.
This template is licensed under the MIT License. The license covers the
CMake build files, deps/ scripts, and other template infrastructure.
Because this is a project template, your own source code is not bound by
this license. You are free to choose any license for your project that is
compatible with MIT. Simply replace the LICENSE file with your chosen
license.
| Tool | Purpose |
|---|---|
| CMake ≥ 3.21 | Build system |
| Ninja | Build backend |
| C23 compiler | GCC 14+, Clang 18+ |
| Ruby ≥ 3.0 | CMock mock generation |
| gcovr ≥ 6.0 | Coverage reports, optional (uv tool install gcovr) |
Configure:
cmake -S . -B build -G NinjaBuild:
ninja -C buildFull Unity output with colors:
ninja -C build checkCTest summary only:
ninja -C build testcheck builds all suites and runs CTest with --output-on-failure,
so assertion-level detail appears on any failure without running
binaries by hand.
Configure with coverage instrumentation:
cmake -S . -B build-cov -G Ninja -DENABLE_COVERAGE=ONGenerate the HTML report:
ninja -C build-cov coverageOpen build-cov/coverage/index.html in a browser to view results.
Only ctdd/ source files are measured. Unity, CMock, and generated
mock files are excluded. Requires GCC or Clang with gcov support, and
gcovr on PATH.
Windows note: requires GCC (e.g.
scoop install gcc) or a Clang build that includes compiler-rt. A custom Clang without compiler-rt will fail at link time.
Windows:
./build/main.exeLinux / macOS:
./build/mainTo use this template for a new project, rename ctdd to your project
name:
- Rename the
ctdd/directory to<src>/. - Replace
ctddwith<src>in:CMakeLists.txt(project name,target_link_libraries)ctdd/CMakeLists.txt(library names)- All source
#includedirectives tests/CMakeLists.txt(library references)
- Update the
project()call inCMakeLists.txt. - Update
main.cto include your new headers.
Example: ctdd/ becomes mylib/, ctdd_str becomes mylib_str.
// ctdd/counter.h
#pragma once
int counter_increment(int value);// tests/test_counter.c
#include "unity.h"
#include "ctdd/counter.h"
void setUp(void) {}
void tearDown(void) {}
void test_increment_adds_one(void) {
TEST_ASSERT_EQUAL_INT(2, counter_increment(1));
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_increment_adds_one);
return UNITY_END();
}add_executable(test_counter test_counter.c)
target_include_directories(test_counter PRIVATE "${CMAKE_SOURCE_DIR}")
target_link_libraries(test_counter PRIVATE ctdd_counter Unity::Unity)
target_compile_features(test_counter PRIVATE c_std_23)
add_test(NAME test_counter COMMAND test_counter)
list(APPEND TEST_TARGETS test_counter)Stub ctdd/counter.c with return 0;, run tests to see the failure,
then implement the real logic and confirm they pass.
If your module calls an external function (e.g. log_message), generate
a mock from its header and add it to the test target:
# tests/CMakeLists.txt
add_executable(test_mymodule test_mymodule.c)
target_link_libraries(test_mymodule PRIVATE ctdd_mymodule Unity::Unity CMock::CMock)
target_compile_features(test_mymodule PRIVATE c_std_23)
cmock_generate_mock(test_mymodule "${CMAKE_SOURCE_DIR}/ctdd/logger.h")
add_test(NAME test_mymodule COMMAND test_mymodule)CMock generates Mocklogger.h into the build directory. Include it and
use the generated API in your test:
#include "Mocklogger.h"
void setUp(void) { Mocklogger_Init(); }
void tearDown(void) { Mocklogger_Verify(); Mocklogger_Destroy(); }
void test_something_logs(void) {
log_message_Expect("expected string");
my_function_under_test();
}The _Expect call records the expected argument. CMock verifies the
actual argument matches at call time using string comparison, and
Mocklogger_Verify confirms the expected number of calls were made.
Pass a config file as the third argument to cmock_generate_mock:
cmock_generate_mock(test_mymodule "${CMAKE_SOURCE_DIR}/ctdd/logger.h"
"cmock_config.yml")The file is loaded by the CMock Ruby generator with -o. A typical
tests/cmock_config.yml enables plugins:
:cmock:
:plugins:
- :ignore
- :ignore_arg
- :return_thru_ptrUse :ignore to skip certain mocks, :ignore_arg to stop checking
particular parameters, or :return_thru_ptr to have CMock write
through pointer return values automatically.