📜 ⬆️ ⬇️

Semi-automatic registration of unit tests on pure C

After reading the book Test Driven Development for Embedded C, I began to get acquainted with the world of unit testing with the cppUtest framework. Not least because in it the newly written test is registered and launched independently. You have to pay for it - using C ++, dynamic memory allocation somewhere in the depths of the framework. Maybe you can somehow simpler?
Most recently, I learned about the minimal minUnit framework, which fits just 4 lines.

I will bring them here for clarity:

#define mu_assert(message, test) do { if (!(test)) return message; } while (0) #define mu_run_test(test) do { char *message = test(); tests_run++; \ if (message) return message; } while (0) extern int tests_run; 

Simple and beautiful. In this case, writing a test looks like this:

 static char * test_foo() { mu_assert("error, foo != 7", foo == 7); return 0; } 

Unfortunately, when I tried to use this framework, I very quickly realized that I was terribly lazy to register each test with my hands. This is because you need to get a header file for a file with tests, each test in the file to register an ad, then go to main and register the call!
')
I looked at other frameworks written in pure C: almost everywhere is the same. Alternatively, there are separate programs that scan sources with tests and generate code to run.
But maybe it can be easier?

This post inspired confidence in me, where a linker is used to register tests. But I did not want to attach to the linker and the compiler-specific attributes.
As far as I know, on pure C it is impossible to make a full test registration. What about semi-automatic?

The idea took shape as follows. For each module, the file module_tests.c is written, all tests for this module are written in it. These tests form a group. In the same file is written the magic function of running all the tests in the group.
And in the main'e need hands to register only the launch of the group, and not each test separately.
This boils down to the following task: you need to somehow get a list of all the functions in the file. In C, this can only be done with a preprocessor. But how? For example, if functions will be called somehow monotonous.

The “service” names of the tests may well be anything, as long as the title of the test is intelligible!
So, you need to use the preprocessor to generate names for function tests, and in the same pattern and in a single pattern. For example, like this:

 #define UMBA_TEST_COUNTER BOOST_PP_COUNTER #define UMBA_TEST_INCREMENT() BOOST_PP_UPDATE_COUNTER() #define UMBA_TOKEN(x, y, z) x ## y ## z #define UMBA_TOKEN2(x, y, z) UMBA_TOKEN(x,y,z) #define UMBA_TEST( description ) static char * UMBA_TOKEN2(umba_test_, UMBA_TEST_COUNTER, _(void) ) 

I admit honestly, I used the boost for the first time in my life and was amazed at the depth of my soul by the power of C preprocessor!
Now you can write tests as follows:

 UMBA_TEST("Simple Test") //  static char * umba_test_0_(void) { uint8_t a = 1; uint8_t b = 2; UMBA_CHECK(a == b, "MATHS BROKE"); return 0; } #include UMBA_TEST_INCREMENT() 

After this inclusion, the counter will be incremented and the name for the next test will generate the name static char * umba_test_1_ (void).

It remains only to generate a function that will run all the tests in the file. For this, an array of pointers to functions is created and filled with pointers to tests. Then the function simply in the loop calls each test from the array.
This function will need to be written at the end of the test file so that the value of UMBA_TEST_COUNTER equals the number of the last test.
To generate an array of pointers, I first went along a simple path and wrote a helper-file of this kind:

 #if UMBA_TEST_COUNTER == 1 #define UMBA_LOCAL_TEST_ARRAY UmbaTest umba_local_test_array[ UMBA_TEST_COUNTER ] = {umba_test_0_}; #elif UMBA_TEST_COUNTER == 2 #define UMBA_LOCAL_TEST_ARRAY UmbaTest umba_local_test_array[ UMBA_TEST_COUNTER ] = {umba_test_0_, umba_test_1_}; … 

In principle, it is quite possible to do this by generating ads for several hundred tests. Then from boost'a will need only one file - boost / preprocessor / slot / counter.hpp.
But, since I started using boost, why not continue?

 #define UMBA_DECL(z, n, text) text ## n ## _, #define UMBA_LOCAL_TEST_ARRAY UmbaTest umba_local_test_array[ UMBA_TEST_COUNTER ] = { BOOST_PP_REPEAT( UMBA_TEST_COUNTER, UMBA_DECL, umba_test_ ) } 

Only two lines, but what power behind them is hidden!
Add the trivial code for the group launch function itself:

 #define UMBA_RUN_LOCAL_TEST_GROUP( groupName ) UMBA_LOCAL_TEST_ARRAY; \ char * umba_run_test_group_ ## groupName ## _(void) \ { \ for(uint32_t i=0; i < UMBA_TEST_COUNTER; i++) \ { \ tests_run++; \ char * message = umba_local_test_array[i](); \ if(message) \ return message; \ } \ return 0; \ } \ 

And to launch it from main:

 #define UMBA_EXTERN_TEST_GROUP( groupName ) char * umba_run_test_group_ ## groupName ## _(void); #define UMBA_RUN_GROUP( groupName ) do { \ char *message = umba_run_test_group_ ## groupName ## _(); \ tests_run++; \ if (message) return message; \ } while (0) 

Voila Now starting a group with any number of tests looks the same:

 UMBA_EXTERN_TEST_GROUP( SimpleGroup ) static char * run_all_tests(void) { UMBA_RUN_GROUP( SimpleGroup ); return 0; } int main(void) { char *result = run_all_tests(); if (result != 0 ) { printf("!!!!!!!!!!!!!!!!!!!\n"); printf("%s\n", result); } else { printf("ALL TESTS PASSED\n"); } printf("Tests run: %d\n", tests_run-1); return 0; } 


I am quite pleased with this result. Mechanical action when writing a test is now noticeably less.
All the macros mentioned fit in 40-50 lines, which, unfortunately, is somewhat larger than minUnit (and much less obvious).
All code is complete.

Yes, there is a lot of functional missing from large frameworks, but, frankly, I have never been able to use something other than a simple check of the CHECK type (if true) in the test.
Description for the test is just thrown away, but it would seem easy to do something useful with it, if you suddenly want to.

What I would like to find out:
  1. Have I invented something new or have they used this trick for many years?
  2. Can this be somehow improved? I don’t really like the need to write some strange connection after each test, but I didn’t find any other implementations of the counter on the preprocessor (__COUNT__ is not supported by my compiler).
  3. Should I use a homemade framework in production?
  4. How the hell does BOOST_PP_COUNTER work ?! Even on stackoverflow, the answer to the corresponding question is “magic”.

Source: https://habr.com/ru/post/240565/


All Articles