Next: , Previous: Test a Little, Up: Tutorial



3.4 Creating a Suite

To run unit tests with Check, we must create some test cases, aggregate them into a suite, and run them with a suite runner. That's a bit of overhead, but it is mostly one-off. Here's a diff for the new version of check_money.c. Note that we include stdlib.h to get the definitions of EXIT_SUCCESS and EXIT_FAILURE.

     --- tests/check_money.2.c	2006-11-21 18:06:36.760265712 -0500
     +++ tests/check_money.3.c	2006-11-21 18:06:36.825255832 -0500
     @@ -1,3 +1,4 @@
     +#include <stdlib.h>
      #include <check.h>
      #include "../src/money.h"
      
     @@ -13,8 +14,27 @@
      }
      END_TEST
      
     +Suite *
     +money_suite (void)
     +{
     +  Suite *s = suite_create ("Money");
     +
     +  /* Core test case */
     +  TCase *tc_core = tcase_create ("Core");
     +  tcase_add_test (tc_core, test_money_create);
     +  suite_add_tcase (s, tc_core);
     +
     +  return s;
     +}
     +
      int
      main (void)
      {
     -  return 0;
     +  int number_failed;
     +  Suite *s = money_suite ();
     +  SRunner *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;
      }

Most of the money_suite() code should be self-explanatory. We are creating a suite, creating a test case, adding the test case to the suite, and adding the unit test we created above to the test case. Why separate this off into a separate function, rather than inline it in main()? Because any new tests will get added in money_suite(), but nothing will need to change in main() for the rest of this example, so main will stay relatively clean and simple.

Unit tests are internally defined as static functions. This means that the code to add unit tests to test cases must be in the same compilation unit as the unit tests themselves. This provides another reason to put the creation of the test suite in a separate function: you may later want to keep one source file per suite; defining a uniquely named suite creation function allows you later to define a header file giving prototypes for all the suite creation functions, and encapsulate the details of where and how unit tests are defined behind those functions. See the test program defined for Check itself for an example of this strategy.

The code in main() bears some explanation. We are creating a suite runner object of type SRunner from the Suite we created in money_suite(). We then run the suite, using the CK_NORMAL flag to specify that we should print a summary of the run, and list any failures that may have occurred. We capture the number of failures that occurred during the run, and use that to decide how to return. The check target created by Automake uses the return value to decide whether the tests passed or failed.

Now that the tests are actually being run by check_money, we encounter linker errors again we try out make check. Try it for yourself and see. The reason is that the money.c implementation of the money.h interface hasn't been created yet. Let's go with the fastest solution possible and implement stubs for each of the functions in money.c:

     --- src/money.1.c	2006-11-21 18:06:37.357174968 -0500
     +++ src/money.3.c	2006-11-21 18:06:37.457159768 -0500
     @@ -0,0 +1,26 @@
     +#include <stdlib.h>
     +#include "money.h"
     +
     +Money *
     +money_create (int amount, char *currency)
     +{
     +  return NULL;
     +}
     +
     +int
     +money_amount (Money * m)
     +{
     +  return 0;
     +}
     +
     +char *
     +money_currency (Money * m)
     +{
     +  return NULL;
     +}
     +
     +void
     +money_free (Money * m)
     +{
     +  return;
     +}

Note that we #include <stdlib.h> to get the definition of NULL. Now, the code compiles and links when we run make check, but our unit test fails. Still, this is progress, and we can focus on making the test pass.