Skip to content

Expert · Debugging and tools · topic 54 of 60 · 25 min

assert, logging and testing C code

Explanation

assert(condition) from <assert.h> stops the program with the file, line and condition when an assumption is false; it is removed when NDEBUG is defined, so use it for programmer errors, not for user input. Report expected failures with return codes and errno. Test C code with a small home-made CHECK macro or a framework such as Unity, CMocka or Check, covering edge cases.

assert: checking your own assumptions

c
#include <assert.h>

double average(const int *marks, int n) {
    assert(marks != NULL);       /* a programming error if this is false */
    assert(n > 0);
    long sum = 0;
    for (int i = 0; i < n; i++) sum += marks[i];
    return (double) sum / n;
}
/* if violated: prog: report.c:5: average: Assertion 'n > 0' failed.  (then abort) */

An assertion documents and checks something that must ALWAYS be true if the code is correct. When it fails, the program stops immediately at the real cause, instead of crashing later in a confusing place. Compiling with -DNDEBUG removes every assert, so release builds pay nothing.

Careful: Never put code with side effects inside assert (assert(fclose(f) == 0) or assert(x++ < 10)): with NDEBUG the whole expression disappears, and the program behaves differently in release builds. And never use assert to validate user input or file contents; those checks must stay in release builds and produce friendly errors.

Error codes and errno

c
#include <errno.h>
#include <string.h>

FILE *f = fopen("config.ini", "r");
if (!f) {
    fprintf(stderr, "cannot open config.ini: %s\n", strerror(errno));   /* e.g. "No such file or directory" */
    return -1;
}
Many library functions set errno on failure; strerror turns it into text (perror does both).

A common C convention: functions return 0 on success and a negative number on failure, and put results in output pointers. Document each function's error codes, and check them at every call.

A tiny test harness

c
#include <stdio.h>

static int failures = 0;
#define CHECK(cond) do { \
    if (!(cond)) { fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); failures++; } \
} while (0)

int gcd(int a, int b);

int main(void) {
    CHECK(gcd(84, 36) == 12);
    CHECK(gcd(17, 5) == 1);
    CHECK(gcd(10, 0) == 10);      /* edge case */
    CHECK(gcd(0, 0) == 0);        /* edge case: what SHOULD it return? */
    if (failures) { printf("%d test(s) failed\n", failures); return 1; }
    printf("all tests passed\n");
    return 0;
}
Run it from make test or CI; a non-zero exit status marks the build as failed.

Test frameworks

Larger projects use a framework that organises tests, reports results and helps with mocks: Unity (popular in embedded work, a single C file), CMocka (with mocking support), Check, or GoogleTest (C++, but often used to test C libraries). The ideas are the same as in any language: arrange, act, assert, one behaviour per test, edge cases first.

What to test in C code

  • Boundaries: 0, 1, the maximum size, exactly-full buffers, empty strings.
  • Error paths: allocation failure (inject it), missing files, invalid input.
  • Round trips: write then read a file; encode then decode.
  • Run the whole test suite under sanitizers (topic 3).
Think of it like this: assert is like the checklist a surgeon's team reads aloud before an operation ("correct patient? correct side?"): it costs seconds and stops a disaster at its true cause. User input checks are different: they are the reception desk, which must keep working for every patient, every day.
Tip: Write a test for every bug you fix before fixing it. It reproduces the bug, proves the fix, and stops the bug from coming back.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

assert, logging and testing C code · 1/6

assert and testing

In this lesson you will check assumptions with assert and test C code.

Prefer a video by a teacher? Find free videos on “c assert unit testing unity cmocka tutorial” on YouTube ↗

In real life: Unit tests for a smart-meter tariff module

An energy company's meter firmware has a tariff module that turns units into a bill with slabs. The team keeps 40 Unity tests for it, including the slab boundaries (exactly 100 and 101 units) and zero consumption, and runs them on a PC on every commit, long before flashing a device.

c
void test_slab_boundary(void) {
    TEST_ASSERT_EQUAL_INT(300, bill_paise_units(100) / 100);   /* 100 units × Rs 3 */
    TEST_ASSERT_EQUAL_INT(305, bill_paise_units(101) / 100);   /* +1 unit at Rs 5 */
}

Practice (0/3 done)

Task 1 · Question

What happens to assert(x > 0) when the program is compiled with -DNDEBUG?

Task 2 · Code · Javascript

Build the same kind of tiny test harness in JavaScript, which runs here: write makeHarness() returning { check(cond, name), summary() }. check records a failure (by name) when cond is false; summary() returns { passed, failed, failures } where failures is the list of failed names in order.

Task 3 · Practice

For your linked-list or hash-table code from module 10, write a test file with a CHECK macro (or Unity) containing at least 10 tests: normal cases, empty structures, removing missing items, and freeing. Add asserts for internal assumptions (non-NULL arguments). Run the tests normally, with -DNDEBUG, and under -fsanitize=address,undefined.

Quick quiz

Question 1

1.What should assert be used for?

Question 2

2.Why is assert(fclose(f) == 0) a bug?

Question 3

3.What does strerror(errno) return?

Question 4

4.Which is a C unit-testing framework popular in embedded work?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What should assert be used for?

Answer: Checking conditions that must be true if the code is correct

Programmer errors, not user errors.

Why is assert(fclose(f) == 0) a bug?

Answer: With NDEBUG the fclose call disappears

Side effects vanish in release builds.

What does strerror(errno) return?

Answer: A text description of the last error

For example "Permission denied".

Which is a C unit-testing framework popular in embedded work?

Answer: Unity

Unity is a small C framework.

What does assert_eq! need from the compared type?Related

Answer: PartialEq and Debug

It compares with == and prints both sides on failure.

From Unit tests

In Arrange, Act, Assert, what happens in the Act step?Related

Answer: You call the code being tested

Arrange prepares, Act calls the function once, Assert checks the result.

From Unit testing: checking one piece at a time

Why should real programs not use assert to validate user input?Related

Answer: Running Python with -O removes all asserts, so the checks vanish

Optimised mode (-O) strips assert statements. Use proper exceptions like ValueError for input checks.

From Testing your code

What does Arrange-Act-Assert describe?Related

Answer: The structure of a test: set up, call, check

It keeps tests readable.

From Unit testing with xUnit

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “Debugging and tools” before its exam: A method for debugging.
  2. Module 11 exam: Debugging and tools · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Embedded C, system calls and careers. 60 topics left in the course before the final exam.

← Previous: Sanitizers and warnings