C: unit testing with a small macro
Report which checks pass and fail by printing the text of each expression.
On this page
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
#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;
}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
#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;}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
| Step / line | What happens | State after step | Output 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.