Java concepts in C: test-first development and unit-testing


Unit testing is no rocket science. Do it as often as you can. Writing your own unit-testing library is simple: You need something that calls your test methods, and some form of assert-function that can die with style.

I chose the convention of simply returning 0 if the test is ok, and 1 if it fails:

/* example test function. return value is 0 iff succeeded.*/

int test_tests(void) {

        return (count_tests() > 0 ? 0 : 1);

}

A assertEquals directive you can use.

#define ASSERTEQUALD(result, expected, text) { \

                if(expected == result || expected - result < (expected+result)*0.001 ) { \

                        printf("  subtest ok: %s\n", text); \

                } else { \

                        printf("  ASSERT EQUAL FAILED: expected: %e; got: %e; %s\n", expected, result, text); \

                        return 1; \

                } \

        }

Collect the tests you want to run



/* register of all tests */

int (*tests_registration[])(void)  = {

        /* this is test 1 */ /*test_tests, */

        test_hist,

        test_create,

        test_alloc,

        test_load,

        test_resize,

        test_append,

        test_random,

        test_mod,

        test_write,

        test_write_prob,

        /* register more tests before here */

        NULL,

};

And link with the following test runner. (run-tests.c)

/* we do testing of the inner works */

#include <ctype.h>

#include <errno.h>

#include <stdio.h>

#include <stdlib.h>

extern int (*tests_registration[])(void);

int count_tests() {

	int n = -1;

	while (tests_registration[++n] != NULL)

		;

	return n;

}

/*

 * when testnr in 1..n, execute the specific test.

 * output is conforming to TAP

 */

int test(int testnr) {

	int n = count_tests();

	int r;

	if (testnr <= 0 || testnr > n) {

		fprintf(stderr, "Test number out of range (1..%d)\n", n);

		return -1;

	}

	r = tests_registration[testnr - 1]( ) ;if(r == 0)

	printf("ok %d\n", testnr);

	else

	printf("not ok %d - returned %d\n", testnr, r);

	return r;

}

int test_all() {

	int i = 0;

	int n = count_tests();

	int count = 0;

	printf("1..%d\n", n);

	while (i++ < n) {

		count += test(i);

	}

	if (count != 0) {

		printf("  %d of %d tests unsuccessful\n", count, n);

	} else {

		printf("  all %d tests successful\n", n);

	}

	return count;

}

void usage(char * progname) {

	printf("SYNOPSIS: \n\n"

		"%s               \trun all tests\n"

		"%s [<testnumber>]\trun a specific test by number\n"

		"\n"

		"Tests available: 1..%d\n\n"

		"", progname, progname, count_tests());

}

int main(int argc, char ** argv) {

	int t;

	if (argc == 1) {

		return test_all();

	} else if (argc == 2 && isdigit(argv[1][0])) {

		errno = 0;

		t = strtol(argv[1], (char **) NULL, 10);

		if (errno != 0) {

			usage(argv[0]);

		} else {

			test(t);

		}

	} else {

		usage(argv[0]);

	}

	return 0;

}

You can then run your tests selectively or the whole suite



$ ./tests.exe

...

ok 8

  subtest ok: mod ints

  subtest ok: mod doubles

  subtest ok: mod doubles

ok 9

ok 10

  all 10 tests successful

$ ./tests.exe 4

  subtest ok: x 0

  subtest ok: x 1

  subtest ok: x last

	DEBUG[tests/tests.c:196]: add starting points, ...

  subtest ok: y 0

  subtest ok: y 1

  subtest ok: y last

ok 4

The beauty of this test suite (yes, there is some beauty in it), is that it is pluggable, extendable, debuggable and TAP (Test Anything Protocol) conform.

As with all test suites you will have to separate your code into testable chunks so that they are accessible by your test functions.

  1. #1 by panzi on April 19th, 2009

    IMHO it’s a good idea to use typedefs to make your code more readable:

    typedef int (*test_function_t)(void);
    /* register of all tests */
    test_function_t tests_registration[] = {
    /* this is test 1 */ /*test_tests, */
    test_hist,
    test_create,
    test_alloc,
    test_load,
    test_resize,
    test_append,
    test_random,
    test_mod,
    test_write,
    test_write_prob,
    /* register more tests before here */
    NULL,
    };

    You can interpret this like declaring an interface in Java. ;)

  2. #2 by panzi on April 19th, 2009

    Oh, and a trick in order to not forget the terminating NULL (does not work for empty arrays):

    #include <stdlib.h>
    #include <stdio.h>
    #define ARRAY(...) {__VA_ARGS__, NULL}
    int main() {
    const char* array1[] = ARRAY("foo");
    const char* array2[] = ARRAY("bar", "baz");
    for (const char** it = array1; *it != NULL; ++ it) {
    puts(*it);
    }
    for (const char** it = array2; *it != NULL; ++ it) {
    puts(*it);
    }
    return 0;
    }
(will not be published)

  1. No trackbacks yet.