C · Course navigation

C: unit testing with a small macro

Report which checks pass and fail by printing the text of each expression.

On this page
Before you start

C functions, macros, and strings.

  • Report which checks pass and fail by printing the text of each expression.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

A unit test calls a small function with known input and compares the result with the expected value. C has no built-in test framework, but projects use Unity, Check, or CMocka. At heart they all work like the macro here: evaluate a condition, report PASS or FAIL, and count failures.

The preprocessor operator # turns a macro argument into a string literal, so the report shows the exact expression that was tested without extra typing. The do { ... } while (0) wrapper makes the macro behave like one statement. The program returns a non-zero exit status if any check failed, which lets a build system notice.

Code example

C
#include <stdio.h>
#include <string.h>

static int failures = 0;

#define CHECK(cond)                      \
  do {                                   \
    if (cond) {                          \
      printf("PASS %s\n", #cond);        \
    } else {                             \
      printf("FAIL %s\n", #cond);        \
      failures++;                        \
    }                                    \
  } while (0)

static int clamp(int value, int low, int high) {
  if (value < low) return low;
  if (value > high) return high;
  return value;
}

static void reverse(char *text) {
  size_t n = strlen(text);
  for (size_t i = 0; i < n / 2; i++) {
    char tmp = text[i];
    text[i] = text[n - 1 - i];
    text[n - 1 - i] = tmp;
  }
}

int main(void) {
  CHECK(clamp(5, 0, 10) == 5);
  CHECK(clamp(-3, 0, 10) == 0);
  CHECK(clamp(99, 0, 10) == 10);

  char word[] = "stressed";
  reverse(word);
  CHECK(strcmp(word, "desserts") == 0);

  printf("failures: %d\n", failures);
  return failures != 0;
}
Output
PASS clamp(5, 0, 10) == 5
PASS clamp(-3, 0, 10) == 0
PASS clamp(99, 0, 10) == 10
PASS strcmp(word, "desserts") == 0
failures: 0

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1#include <stdio.h>
2#include <string.h>
3
4static int failures = 0;
5
6#define CHECK(cond) \
7 do { \
8 if (cond) { \
9 printf("PASS %s\n", #cond); \
10 } else { \
11 printf("FAIL %s\n", #cond); \
12 failures++; \
13 } \
14 } while (0)
15
16static int clamp(int value, int low, int high) {
17 if (value < low) return low;
18 if (value > high) return high;
19 return value;
20}
21
22static void reverse(char *text) {
23 size_t n = strlen(text);
24 for (size_t i = 0; i < n / 2; i++) {
25 char tmp = text[i];
26 text[i] = text[n - 1 - i];
27 text[n - 1 - i] = tmp;
28 }
29}
30
31int main(void) {
32 CHECK(clamp(5, 0, 10) == 5);
33 CHECK(clamp(-3, 0, 10) == 0);
34 CHECK(clamp(99, 0, 10) == 10);
35
36 char word[] = "stressed";
37 reverse(word);
38 CHECK(strcmp(word, "desserts") == 0);
39
40 printf("failures: %d\n", failures);
41 return failures != 0;
42}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
A global count of failed checks, used for the exit status.—
—
A macro that evaluates the condition and reports the result. Inside it, #cond turns the argument into a string, so the report shows the source text of the test.—
—
A value inside the range is unchanged.—
PASS clamp(5, 0, 10) == 5
Values outside the range are limited to the nearest bound.—
PASS clamp(5, 0, 10) == 5
PASS clamp(-3, 0, 10) == 0
PASS clamp(99, 0, 10) == 10
The string is reversed in place. Compare strings with strcmp, never with ==.—
PASS clamp(5, 0, 10) == 5
PASS clamp(-3, 0, 10) == 0
PASS clamp(99, 0, 10) == 10
PASS strcmp(word, "desserts") == 0
No check failed, so main returns 0.failures = 0
PASS clamp(5, 0, 10) == 5
PASS clamp(-3, 0, 10) == 0
PASS clamp(99, 0, 10) == 10
PASS strcmp(word, "desserts") == 0
failures: 0

Behind the scenes

How C processes this code

The C compiler produces a native program. Variables occupy memory, and execution enters main.

Cost and common mistakes

Each check is O(1) plus the cost of the code under test.

  • A macro argument is evaluated where it is used. CHECK(i++ < 3) would increment i more than once in other macros, so keep arguments free of side effects.
  • Comparing strings with == compares pointers, not contents.
  • A failing test should make the program's exit code non-zero. Otherwise automated builds will not notice the failure.

Try it yourself

How would you test that clamp handles low equal to high?

Reveal answer and reasoning

CHECK(clamp(7, 4, 4) == 4); with equal bounds every input must map to that single value.

Further reading