Minitest

[edited 2024-04-07: removed erroneus line 14 of the code block - it was `return 0; \` ]

I started writing a little C program last weekend to show the amount of data piped to its STDIN, because I happened to want one and couldn't find any. I chose C instead of Rust (wdich I have used more recently) as I wanted to refresh my proficiency in C.

I had to relearn quite a bit syntax-wise, from pointer syntax to buffer declarations. But with testing I didn't feel like relearning anything as I stumbled upon 'minunit.h' by Jera Design LLC:

https://jera.com/techinfo/jtns/jtn002

or whip up your own in a few hours

- Jera Design LLC

Instead, inspired by the above quote and the beautiful simplicity of 'minunit.h' I decided to write my own, lightweight testing framework fitting my needs.

/* file: minitest.h
 *
 * Minimal testing library
 * inspired by `minunit.h`
 * from Jera Design LLC:
 * https://jera.com/techinfo/jtns/
 */

#include <stdio.h>
#define mt_assert(test, message) do { if (!(test)) return message; } while (0)
#define mt_assert_eq(left, right) do { \
	if (left != right) \
		return "`" #left "` didn't match `" #right "`"; \
	return 0; \
} while (0)
#define mt_run_test(test) do { \
	char *message = test(); \
	if (message) { \
		fprintf(stderr, "[FAIL] " #test ": %s\n", message); \
		tests_failed++; \
	} else { \
		fprintf(stderr, "[PASS] " #test "\n"); \
	} \
	tests_run++; \
} while (0)
#define mt_test_report() do { \
	if (tests_failed == 0) \
		fprintf(stderr, "[INFO] All %d tests passed.\n", tests_run); \
	else \
		fprintf(stderr, "[INFO] Failure. %d/%d tests failed.\n", tests_failed, tests_run); \
} while (0)
extern int tests_run;
extern int tests_failed;

The greatest difference to 'minunit.h' is that the frameworks outputs all successes and failures instead of just returning on the first failing test encountered.

Below is an example on how 'minitest.h' can be used:

#include <stdio.h>
#include "minitest.h"

int tests_run = 0;
int tests_failed = 0;

int foo() {
	return 3;
}

int bar(int x) {
	return x;
}

static char* test_foo() {
	mt_assert(foo() == 3, "Error, `foo()` didn't return `3`");
	return 0;
}

static char* test_bar() {
	mt_assert_eq(bar(5), 6);
	return 0;
}

static char* boilerplate_tests() {
	mt_run_test(test_foo);
	mt_run_test(test_bar);
	return 0;
}

int main() {
	fprintf(stderr, "[INFO] Minitest running tests...\n\n");

	fprintf(stderr, "[INFO] Running boilerplate tests...\n");
	boilerplate_tests();
	printf("\n");

	mt_test_report();

	return tests_failed != 0;
}

/* Output:
 *
 * [INFO] Minitest running tests...
 *
 * [INFO] Running boilerplate tests...
 * [PASS] test_foo
 * [FAIL] test_bar: `bar(5)` didn't match `6`
 *
 * [INFO] Failure. 1/2 tests failed.
 */

Minitest and the above example are licensed under the MIT license:

gemini://gemini.jorl.fi/gemlog/minitest-license.txt