portersky 3ce42cc8db chore: add gitattributes, rename guide, and template comments
Add .gitattributes enforcing LF line endings.

Add a rename guide to README.md for scaffolding new projects.

Comment main.c as a template placeholder to replace.
2026-06-15 04:33:55 +02:00
2026-05-09 20:43:32 +02:00
2026-05-09 20:32:55 +02:00
2026-05-10 00:57:59 +02:00

ctdd

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.

Requirements

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)

Build

Configure:

cmake -S . -B build -G Ninja

Build:

ninja -C build

Test

Full Unity output with colors:

ninja -C build check

CTest summary only:

ninja -C build test

check builds all suites and runs CTest with --output-on-failure, so assertion-level detail appears on any failure without running binaries by hand.

Coverage

Configure with coverage instrumentation:

cmake -S . -B build-cov -G Ninja -DENABLE_COVERAGE=ON

Generate the HTML report:

ninja -C build-cov coverage

Open 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.

Run

Windows:

./build/main.exe

Linux / macOS:

./build/main

Renaming the Project

To use this template for a new project, rename ctdd to your project name:

  1. Rename the ctdd/ directory to <src>/.
  2. Replace ctdd with <src> in:
    • CMakeLists.txt (project name, target_link_libraries)
    • ctdd/CMakeLists.txt (library names)
    • All source #include directives
    • tests/CMakeLists.txt (library references)
  3. Update the project() call in CMakeLists.txt.
  4. Update main.c to include your new headers.

Example: ctdd/ becomes mylib/, ctdd_str becomes mylib_str.

1. Write the header

// ctdd/counter.h
#pragma once
int counter_increment(int value);

2. Write a failing test

// 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();
}

3. Register the test in tests/CMakeLists.txt

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)

4. Add a stub, confirm RED, then implement GREEN

Stub ctdd/counter.c with return 0;, run tests to see the failure, then implement the real logic and confirm they pass.

Mocking a dependency

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.

Configuring CMock

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_ptr

Use :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.

S
Description
No description provided
Readme MIT 130 KiB
Languages
C 57.8%
CMake 42.2%