Author and run ThrowTheSwitch Unity (the C unit-testing library) for bare-metal and RTOS C code. Distinct from the Unity game-engine Test Framework at docs.unity3d.com: this is the ThrowTheSwitch C testing library at throwtheswitch.org/unity, a single C file plus headers that runs on 8-bit MCUs through 64-bit hosts. Anchored on the Unity assertion API and configuration macros regardless of execution environment: the TEST_ASSERT_EQUAL_* / _FLOAT / _DOUBLE / _STRING / _MEMORY / _BITS assertion families, setUp/tearDown/RUN_TEST/UNITY_BEGIN/UNITY_END semantics and the exit-code contract, the generate_test_runner.rb generator, build-time config defines (UNITY_INCLUDE_DOUBLE, UNITY_OUTPUT_CHAR, UNITY_EXCLUDE_SETJMP), and CI integration via Ceedling JUnit XML; applies to host builds, cross-builds, and QEMU-run targets alike. For QEMU machine flags, semihosting, and exit-code capture, see qemu-system-test-runner. Use when the unit-under-test is pure C and the target ranges from 8-bit AVR to Cortex-M0 to Linux ARM.
79
99%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
This skill covers ThrowTheSwitch Unity - a C unit-testing
library ("a single C file and a pair of headers") at
throwtheswitch.org/unity
and github.com/ThrowTheSwitch/Unity,
running on 8-bit MCUs through 64-bit hosts with no preprocessor
magic, no auto-generated main, and no C++ requirement.
It is distinct from the Unity game-engine Test Framework
(com.unity.test-framework, a runner inside the Unity 3D editor
at docs.unity3d.com,
covered by unity-test-framework). The two share only a name.
Composes with:
ceedling-build-runner - the build orchestration that calls generate_test_runner.rb and stitches Unity + CMock + the test binary.cmock-reference - the CMock-generated mocks Unity asserts against.qemu-system-test-runner - for running the cross-built binary on a virtual Cortex-M.embedded-coverage-strategy-reference - for the gcov / llvm-cov instrumentation.googletest-embedded-arm.#include "unity.h"
#include "ringbuffer.h"
void setUp(void) {
/* runs before each test */
}
void tearDown(void) {
/* runs after each test */
}
void test_ringbuffer_initially_empty(void) {
ringbuffer_t rb;
ringbuffer_init(&rb);
TEST_ASSERT_TRUE(ringbuffer_is_empty(&rb));
TEST_ASSERT_EQUAL_size_t(0, ringbuffer_size(&rb));
}
void test_ringbuffer_push_increments_size(void) {
ringbuffer_t rb;
ringbuffer_init(&rb);
TEST_ASSERT_EQUAL_INT(0, ringbuffer_push(&rb, 42));
TEST_ASSERT_EQUAL_size_t(1, ringbuffer_size(&rb));
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_ringbuffer_initially_empty);
RUN_TEST(test_ringbuffer_push_increments_size);
return UNITY_END();
}A Unity test is a C function taking no arguments, named with a
test or spec prefix. UNITY_BEGIN() initialises counters;
RUN_TEST(name) invokes setUp -> test fn -> tearDown and
captures the result; UNITY_END() prints the summary and returns
a non-zero exit code on failure.
The common assertions - TEST_ASSERT_TRUE(c),
TEST_ASSERT_EQUAL_INT(a,b) (with _INT8 / _INT16 / _INT32
/ _INT64 and _UINT / _HEX widths),
TEST_ASSERT_EQUAL_FLOAT(a,b) / TEST_ASSERT_FLOAT_WITHIN(delta,a,b),
TEST_ASSERT_EQUAL_STRING(a,b), TEST_ASSERT_EQUAL_MEMORY(a,b,len),
TEST_ASSERT_NULL(p). Append _ARRAY to compare arrays (third
arg is element count) and _MESSAGE to attach a custom failure
string.
The full assertion-family table and the build-time config defines are in references/assertion-api.md.
void test_only_when_calibrated(void) {
if (!device_calibrated()) {
TEST_IGNORE_MESSAGE("device not calibrated; skip");
}
/* normal asserts */
}TEST_IGNORE() records an "ignored" result, distinct from
pass/fail in the summary.
The Ruby script auto/generate_test_runner.rb scans a test file,
finds every test_* and spec_* function, and emits a
<file>_Runner.c that wires them up:
ruby /path/to/Unity/auto/generate_test_runner.rb \
test/test_ringbuffer.c test/test_ringbuffer_Runner.cThen compile test_ringbuffer.c + test_ringbuffer_Runner.c +
unity.c and link. Ceedling does this implicitly - see
ceedling-build-runner.
gcc -Wall -O0 -g -DUNITY_INCLUDE_DOUBLE \
-I src -I ext/Unity/src \
src/ringbuffer.c \
ext/Unity/src/unity.c \
test/test_ringbuffer.c test/test_ringbuffer_Runner.c \
-o test_ringbuffer
./test_ringbuffer-DUNITY_INCLUDE_DOUBLE enables the _DOUBLE macros (off by
default to save flash on tiny MCUs - per the
Unity config docs).
arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -O0 -g \
-DUNITY_OUTPUT_COLOR \
--specs=rdimon.specs \
-I src -I ext/Unity/src \
src/ringbuffer.c ext/Unity/src/unity.c \
test/test_ringbuffer.c test/test_ringbuffer_Runner.c \
-o test_ringbuffer.elf -lrdimon
qemu-system-arm -M mps2-an385 -cpu cortex-m0 \
-nographic -semihosting-config enable=on,target=native \
-kernel test_ringbuffer.elf--specs=rdimon.specs provides the ARM semihosting library
(developer.arm.com GNU Toolchain),
so Unity's printf-based reporting reaches QEMU's stdio.
Key defines (UNITY_INCLUDE_DOUBLE, UNITY_OUTPUT_CHAR,
UNITY_OUTPUT_COLOR, UNITY_FIXTURE_NO_EXTRAS,
UNITY_EXCLUDE_SETJMP) are tabulated in
references/assertion-api.md, per
the Unity configuration guide.
Unity prints one line per result + a summary:
test/test_ringbuffer.c:34:test_ringbuffer_initially_empty:PASS
test/test_ringbuffer.c:42:test_ringbuffer_push_increments_size:PASS
-----------------------
2 Tests 0 Failures 0 Ignored
OKThe format is <file>:<line>:<test_name>:<PASS|FAIL|IGNORE>.
Failures include the assertion details:
test/test_ringbuffer.c:48:test_overflow_returns_minus_one:FAIL: Expected -1 Was 0UNITY_END() returns the failure count (int). Use it from
main:
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_x);
return UNITY_END(); /* 0 on all-pass; non-zero on any failure */
}CI tools that gate on exit code (CMake CTest, GitHub Actions
run: step) work natively.
Unity's text output is intentionally simple - a grep -c ':FAIL:' is enough for a smoke gate:
./test_ringbuffer | tee results.txt
fails=$(grep -c ':FAIL:' results.txt || true)
[ "$fails" -eq 0 ] || exit 1Ceedling wraps Unity and emits a JUnit XML report at
build/artifacts/test/report.xml - see
ceedling-build-runner. The
schema matches GoogleTest's, so the same JUnit pipeline works
for both.
Override UNITY_OUTPUT_CHAR(c) at build time:
/* unity_config.h */
#define UNITY_OUTPUT_CHAR(c) serial_putc(c)
#define UNITY_OUTPUT_FLUSH() serial_flush()On bare-metal, send results over UART → host serial → CI log file.
The standalone (no Ceedling) GitHub Actions pipeline - generate
runners, build + run on host, then cross-build and run under
QEMU - is in
references/ci-integration.md. For
Ceedling-driven projects, see ceedling-build-runner for the
canonical ceedling test:all + JUnit XML flow.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Hand-maintaining the test runner | Drift: a new test_* function is silently skipped | Run generate_test_runner.rb in the build; or use Ceedling |
TEST_ASSERT_EQUAL_INT on size_t | Width mismatch warns on 64-bit hosts, may overflow on 8-bit | Use TEST_ASSERT_EQUAL_size_t or width-specific _UINT32 |
TEST_ASSERT_TRUE(strcmp(a,b) == 0) | Failure message reports "Expected true, got false" - useless | Use TEST_ASSERT_EQUAL_STRING(a,b) |
TEST_ASSERT_EQUAL_MEMORY with len=sizeof(*p) on a struct with padding | Padding bytes vary; intermittent failures | Initialise structs with memset(.., 0, sizeof) before fill, or compare fields individually |
Calling RUN_TEST outside UNITY_BEGIN/UNITY_END | Asserts work but the summary is wrong | Always bracket runs with UNITY_BEGIN / UNITY_END |
| Mixing the C library with the game-engine Test Framework | Build sees two unity.h headers; one wins randomly | Don't co-locate; keep unity-test-framework to game-engine projects |
Float comparison with TEST_ASSERT_EQUAL_FLOAT and exact values | Floating-point equality is fragile | Use TEST_ASSERT_FLOAT_WITHIN(epsilon, a, b) |
UNITY_EXCLUDE_SETJMP on a target that has setjmp | Loses early-abort on fatal assert; tests run on after corruption | Only exclude when the toolchain genuinely lacks setjmp |
TEST_P
equivalent. Loop with a fixture struct and call TEST_ASSERT_*
inside; report the iteration via _MESSAGE suffix.generate_test_runner.rb
(or Ceedling), each test must be manually listed in main.cmock-reference.TEST_ASSERT_EQUAL_FLOAT precision is configurable but
global. UNITY_FLOAT_PRECISION applies to every float
compare in the suite; per-test precision needs _WITHIN.UNITY_FIXTURE_NO_EXTRAS.
Default builds include features (per-test color, fixture
hooks) that bloat tiny MCUs.Cited inline. Foundational documents:
--specs=rdimon.specs, semihosting) -
developer.arm.com Tools and Software / GNU Toolchain.unity-test-framework) -
docs.unity3d.com/Packages/com.unity.test-framework@latest.ceedling-build-runner,
cmock-reference,
googletest-embedded-arm,
qemu-system-test-runner,
embedded-coverage-strategy-reference.