Unit testing C with Check

On my Lubuntu laptop, I installed Check (sometimes referred to as libcheck) via

sudo apt install check

I don't know exactly what that did but I see this

/usr/include/check.h

and

/usr/include/subunit/child.h

I didn't see it immediately when on the Check homepage but the manual is available by clicking on the Reference tab and it's helpful even though the example given is more involved than a "Hello, world" program. Below is a simpler example.

Testing an add function

I created a toy add function to test

int add(int a, int b)
{
  return a + b;
}

and a header file for add.

int add(int a, int b);

I next created the object file called add.o.

gcc -c -Wall add.c

I created this untested "application" which uses my add function:

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

int main(void)
{
  int a=1;
  int b=3;
  int c = add(a, b);
  printf("%d plus %d is %d\n", a, b, c);
  return 0;
}

and built the binary:

gcc -Wall add.o app.c -o app
./app

All the code to test add is saved in the file test_add.c which includes check.h and add.h. I started by defining the test which I named mytest. The START_TEST macro takes the name of the test as an argument. (The END_TEST macro finishes the test.) I use Check's ck_assert_int_eq function (or maybe it's a macro). Within mytest I can have any number of assertions.

#include <stdlib.h>
#include <check.h>
#include "add.h"

START_TEST(mytest) 
{
  ck_assert_int_eq(add(3, 9), 12);
}
END_TEST

Check wants me to next create a test suite. I created a function called mysuite which returns a pointer to Suite. (This code is also in test_add.c.)

Suite *mysuite(void)
{
  /* Boilerplate */
  Suite *s;
  TCase *tc_core;
  
  /* Create the test suite which I call 'Add' */
  s = suite_create("Add");
  
  /* Core test case [seems always needed?] */
  tc_core = tcase_create("Core");
  
  /* add my test to 'tc_core'? */
  tcase_add_test(tc_core, mytest); 
  
  /* put my tests into the suite? */
  suite_add_tcase(s, tc_core);     
  
  return s;
}

The last part is my main function which is also in test_add.c.

int main(void) 
{
  int number_failed;
  Suite *s;
  SRunner *sr;
  
  s = mysuite(); /* <-- only line specific to my test? */
  sr = srunner_create(s);
  
  srunner_run_all(sr, CK_NORMAL);
  number_failed = srunner_ntests_failed(sr);
  srunner_free(sr);
  return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 
}

I then compiled test_add.c using the following (which involved some trial and error):

gcc test_add.c add.o -lcheck -lsubunit -lm -o test_add

A Makefile

Here is how to build test_add which depends on add.o and how to clean up and run the tests.

add.o: add.c add.h
        gcc -c -Wall add.c

test_add: test_add.c add.o
        gcc test_add.c add.o -lcheck -lsubunit -lm -o test_add

PHONY: clean
clean:
        rm add.o test_add

PHONY: test
test: test_add
        ./test_add

make -f Makefile.check clean
make -f Makefile.check test

A full 100% of the tests in my Add test suite are passing.

Author: Stephen Weigand

Created: 2023-08-23 Wed 23:34

Validate