Developers Club geek daily blog

4 years ago
After reading of the book Test Driven Development for Embedded C I have begun acquaintance to the world unit testing with cppUtest framework. Not least because in it the svezhenapisanny test is registered and started independently. It is necessary to pay for it? use of C ++, dynamic memory allocation somewhere in framework depths. Perhaps, it is possible somehow more simply?
Quite recently I have learned about minimalist framework of minUnit which finds room in only 4 lines.

I will bring them here for descriptive reasons:

#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;

Simply and beautifully. Thus writing of dough looks here so:

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

Unfortunately, when I have tried to use this framework, have very quickly understood that to me awfully laziness hands to register each test. It after all it is necessary to get the heading file for the file with tests, to prescribe each dough in this file declaration, then to go to main and to register call!

I have looked at other frameworks written on pure With: almost everywhere too most. The separate programs scanning source codes with tests and generating code for start are as an alternative offered.
But maybe, it is possible more simply?

Confidence in me was instilled by this post where for registration of tests the linker is used. But me to become attached to linker and attributes, specific to the compiler, did not want.
As far as I know, on pure With it is impossible to make full registration of dough. And how about the semi-automatic?

The idea was issued as follows. For each module the module_tests.c file is written, in it all tests for this module are written. These tests form group. In the same file magic function of start of all tests in group is written.
And in main? e it is necessary to register hands only start of group, but not each dough separately.
It is reduced to the following task: it is necessary to receive somehow the list of all functions in the file. In About it it is possible to make only by means of preprocessor. But how? For example, if functions be called somehow is uniform.

? Office? names of tests can quite be any if only dough had distinct heading!
Means, it is necessary to generate by means of preprocessor names for functions tests, and it is uniform and on uniform template. For example, here so:

#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 boost for the first time in life and up to the depth of soul have been struck with power of preprocessor With!
Now it is possible to 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 that the inkluda the counter proinkrementirutsya and the name for the following dough will be generated the name static char * by umba_test_1 _ (void).

It was necessary only to generate function which will start all tests in the file. The pointer array on function is for this purpose created and filled with pointers on tests. Then function simply in the scraper causes each test from array.
This function will need to be written surely at the end of the file with tests that UMBA_TEST_COUNTER value equaled to number of the last dough.
For generation of pointer array I have at first gone on simple way and have written the helper-file here of such look:

#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 manage also it, having generated declarations for several hundred tests. Then from boost'a only one file will be necessary? boost/preprocessor/slot/counter.hpp.
But, time I have started using boost why not to 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!
We add trivial code for the function of start of group:

#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 for its start 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 start of group with any number of tests looks equally:

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 happy with this result. Now it is much less than mechanical actions when writing dough.
All mentioned macroes find room at 40-50 lines that, unfortunately, it is slightly more, than minUnit (and it is much less obvious).
All code entirely.

Yes, there is no functionality a great lot from big frameworks here, but, honestly admit, I never happened to use in the test something in addition to simple check of type of CHECK (if true).
Description for dough is simply thrown out, but to make with it something useful seemingly simply if suddenly it wants.

That I would like to find out:
  1. Do I have invented something new or many years already use similar trick?
  2. Whether it is possible to improve this somehow? I not really like need to write some strange inklud after each dough, but other implementations of the counter on preprocessor I have not found (__ __ my compiler does not support COUNT).
  3. Whether it is worth using self-made framework in the prodakshena?
  4. How, oh, damn, BOOST_PP_COUNTER works?! Even on stackoverflow the answer to appropriate question is? magic?.

This article is a translation of the original post at habrahabr.ru/post/240565/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus