[UCR]  
[/\]
Universidad de Costa Rica
Centro de Investigación en
Matemática Pura y Aplicada
[<=] [home] [<>] [\/] [=>]
Google Translate

Self Unit Testing of C++ and C routines

Adolfo Di Mare




Abstract [<>] [\/] [/\]

When writting small C++ routines it is easier to put all test data inside the same file, and use the C preprocesor to get the test for each module. A few macros are enough to avoid using separate test files to get a simple solution to test before programming in C++. Al escribir pequeñas rutinas en C++ es más fácil de poner todos los datos de prueba dentro del mismo archivo, y utilizar el preprocesador C para obtener el programa de pruebaf para cada módulos. Unas pocas macros son suficientes para evitar el uso de archivos de prueba separados para obtener una solución sencilla para hacer los datos prueba antes de la programar en C++.

Motivation [<>] [\/] [/\]

Beck stablished a nice principle that is difficult to learn: test before programming [Beck-2002]. In many context this principle can be enforced using a development environment that forces programmers into a straitjacket. However, many will find wiser to use and approach that educates instead of forcing. As it natural to argue that C++ is a good language to learn programming well [Str-20094], in here I use an example to show how to write the test data before implementing each routine, avoiding the complexity of using a testing tool or environment.


Building a small routine [<>] [\/] [/\]

When dealing with file names, sometimes it is useful to remove the path. This are examples of this result:

"file_name.com" results from remove_dir_name( "file_name.com" )
"file name.com" results from remove_dir_name( "X:/DIR/SubDir/file name.com" )
"file name.com" results from remove_dir_name( "X:\\DIR\\SubDir\\file name.com" )
Figure 1

After definning module behaviour using examples, it is easier to write the specification for the module. In this case this works: "Removes the leading characters that constitute the path and returns the file name". Another way of saying this requires no words:
   /* "C:/DIR/SubDir/file_name.ext" ==> "file_name.ext" */
(I use /* */ comments because I want the a C language implementation of the routine).

The routine header must be written before the implementation. In this case, as this routine will use zero terminated strings, the values used and returned will be const char *:

/* "C:/DIR/SubDir/file_name.ext" ==> "file_name.ext" */
const char* remove_dir_name( const char* str );
Figure 2

Oftentimes, using file names requires using the zero terminated strings that the library routines use or return. C++ mixes the meaning of pointer and arrays, which is sometimes confusing, but for this example results in a simpler implementation.


Test case definition [<>] [\/] [/\]

The first step is to express the usage examples as test data. A simple solution is to use a comparsion function for strings:

#include <string.h> /* strcmp()  */

/** a==b? */
int eqstr( const char* a, const char* b ) {
    return 0==strcmp(a,b);
}

/** test->remove_dir_name() */
void test_remove_dir_name() {
{{  /* test::remove_dir_name() */
    assert( eqstr( "file_name.com" ,remove_dir_name( "file_name.com" ) ) );
    assert( eqstr( "file name.com", remove_dir_name( "X:/DIR/SubDir/file name.com" ) ) );
    assert( eqstr( "file name.com", remove_dir_name( "X:\\DIR\\SubDir\\file name.com" ) ) );
}}
}
Figure 3

Function strcmp() compares 2 zero terminated strings. Hence, eqstr() returns true when its 2 arguments are the same string. Using eqstr() is easy to express the test examples as test cases (the use of double back slashes '\\' is necessary because the slash character is a special escape charater). The strange {{ and }} are a trick used to automatically generate the documentation for the routine using the Doxygen tool [DiM-2011].


Testing framework [<>] [\/] [/\]

To run the test cases, macro MAIN_TEST is used to surround the test program; when this macro is not defined, the compiler will skip all test code, which is the usual behaviour required when the routine is used in a program. To run the test program, this same module remove_dir_name.c should be compiled defining macro MAIN_TEST which will result in the definition of function main() which can be run to execute all test case.

/* remove_dir_name.c  (C) 2016  adolfo@di-mare.com */

#ifdef    MAIN_TEST /* Test program */

#include <stdio.h>  /* printf() */

/* Test program. */
int main() {
    void test_remove_dir_name();
    printf( "test->" "remove_dir_name" "()\n" ); test_remove_dir_name();
    return 0;
}

#endif /* MAIN_TEST -> Test program */
Figure 4

The test routine is called test_remove_dir_name(), where "test_" is the preffix of the tested routine. The test program is simple: it has a declaration of the tested routines, and their invocation:

This version of the test program can be compiled in the C language: the test routine is test_remove_dir_name() and it must be declared at the beggining of the instructions block. If many test routines reside in the same file all test routines must be declared upfront, which is cumbersome, as the programmer would need to be careful tu declare and invoke each routine. A work arround this issue is to enclose in a { } block the code.

/* Test program. */
int main() {
    {
        void test_remove_dir_name();
        printf( "test->" "remove_dir_name" "()\n" );
        test_remove_dir_name();
    }
    {
        void test_something_else();
        printf( "test->" "something_else" "()\n" );
        test_something_else();
    }
    return 0;
}
Figure 5

Using this idiom all declarations are at the beggining of each test invocation block, which is required in many versions of the C language. What remains to be done is to use a macro to automate the generation of each test block. For this, I use a well known macro trick: the 'do { } while(0)' block of code forces the programmer to put a semicolon character ';' after each macro invocation.

/* Test program. */
int main() {
    do {
        void test_remove_dir_name();
        printf( "test->" "remove_dir_name" "()\n" );
        test_remove_dir_name();
    } while(0);
    do {
        void test_something_else();
        printf( "test->" "something_else" "()\n" );
        test_something_else();
    } while(0);
    return 0;
}
Figure 6

Macro do_test() automates the generation of the code to invoke each test.

/* Test program. */
int main() {
    #define do_test( NAME )                     \
        do {                                    \
            void test_##NAME();                 \
            printf( "test->" #NAME "()\n" );    \
            test_##NAME();                      \
        } while(0)

    do_test( remove_dir_name );
    do_test( something_else  );

    return 0;
    #undef do_test
}
Figure 7

Stringification is the trick needed to have the macro preprocessor use the argument literally instead of trying to expand it. For this, the macro argument must be preceeded character '#'. To force concatenations, a pair of these characters must be used ##.


assert() vs assertTrue() [<>] [\/] [/\]

The Java testing framework uses assertTrue() to carry out test cases. However, form its begginings, the C language uses the assert() macro to insert assertions into the code. Which should be used is a matter of taste, but I like assert() for little routines (as those produced by students who are learning the language).

#ifdef NDEBUG
    #define assert(x) ((void)0)
#else /* debugging enabled */
    #define assert(e)                                   \
        do {                                            \
            if ( !(e) ) {                               \
                printf( "%s", __FILE__ "__LINE__" #e ); \
                abort();                                \
            }                                           \
        } while (0)
#endif /* NDEBUG */
Figure 8

This simplified version of the assert() macro shows that any invocation to it can be removed by defining macro NDEBUG. This is done when is compiled for productions, also called "RELEASE" compilation. However, aborting when the first test case failure is found is not the best choice. I use a lighter version of assert() that aborts after a few errors.

/* Substitute non-verbose assert() macro */
#ifndef assert
    static unsigned assert_count =  0; /* count assertion failures */
    const  unsigned ASSERT_COUNT = 15; /* when to stop assertion executions */

    #include <stdio.h>  /* printf() */
    #include <stdlib.h> /* abort()  */
    const char* remove_dir_name( const char* str );

    /** ( cond ? () : printf( #cond ) ) */
    #define assert(cond)    \
        do {                \
            if (!(cond)) {  \
                printf( "%s: %d  fail-> %s\n",                      \
                    remove_dir_name(__FILE__) , __LINE__ , #cond ); \
                assert_count++;                                     \
                if ( ASSERT_COUNT==assert_count ) { abort(); }      \
            }                                                       \
        } while (0)
#endif
Figure 9

I seldom use the assert() macro in the library which I substitute by the one shown above; for small programs it is more than enough. Note that I use remove_dir_name() to delete the file path that is usually returned by macro __FILE__. I do not include header file <assert.h> but using the #undef code can be written where the library version of the assert() is mixed with other versions of the macro (as the one shown above).


uUnit.h [<>] [\/] [/\]

Another approach for writting test cases is to use my header file BUnit.h, which is somehow complex and works only for C++ programs [DiM-2008]. My implementation of BUnit.h is inspired in Chuck Allison's work [Allison-2000]. I also implemented uUnit.h, which defines only 2 assertion routines: assertTrue(x) and assertFalse(x). Both BUnit.h and uUnit.h fit in a single header file, which simplifies their use.


The implementation [<>] [\/] [/\]

As promised, you better test before implementation. The whole remove_dir_name.c code follows.

/* remove_dir_name.c  (C) 2016  adolfo@di-mare.com */

/** \file   remove_dir_name.c
    \brief  Tools used in \c ren-remove_dir_name.c
    \author Adolfo Di Mare <adolfo@di-mare.com>
    \date   2016
*/

/* "C:/DIR/SubDir/file_name.ext" ==> "file_name.ext" */
const char* remove_dir_name( const char* str ) {
    const char *p = str;     /* p = str+strlen(str); */
    while ( *p != 0 ) { ++p; }
    while ( *p != '/' && *p != '\\' && *p != ':' ) {
        if ( p == str ) { return str; }
        --p;
    }
    return p+1;
}


#ifdef    MAIN_TEST /* Test program */

#include <stdio.h>  /* printf() */

/* Test program. */
int main() {
    #define do_test( NAME )                     \
        do {                                    \
            void test_##NAME();                 \
            printf( "test->" #NAME "()\n" );    \
            test_##NAME();                      \
        } while(0)

    do_test( remove_dir_name );

    return 0;
    #undef do_test
}
/* Substitute non-verbose assert() macro */
#ifndef assert
    static unsigned assert_count =  0; /* count assertion failures */
    const  unsigned ASSERT_COUNT = 15; /* when to stop assertion executions */

    #include <stdio.h>  /* printf() */
    #include <stdlib.h> /* abort()  */
    const char* remove_dir_name( const char* str );

    /** ( cond ? () : printf( #cond ) ) */
    #define assert(cond)    \
        do {                \
            if (!(cond)) {  \
                printf( "%s: %d  fail-> %s\n",                      \
                    remove_dir_name(__FILE__) , __LINE__ , #cond ); \
                assert_count++;                                     \
                if ( ASSERT_COUNT==assert_count ) { abort(); }      \
            }                                                       \
        } while (0)
#endif

#include <string.h> /* strcmp()  */

/** a==b? */
int eqstr( const char* a, const char* b ) {
    return 0==strcmp(a,b);
}

/** Test->remove_dir_name() */
void test_remove_dir_name() {
{{  /* test::remove_dir_name() */
    assert( eqstr( "file_name.com" ,remove_dir_name( "file_name.com" ) ) );
    assert( eqstr( "file name.com", remove_dir_name( "X:/DIR/SubDir/file name.com" ) ) );
    assert( eqstr( "file name.com", remove_dir_name( "X:\\DIR\\SubDir\\file name.com" ) ) );
}}
}

#endif /* MAIN_TEST -> Test program */

/* EOF: remove_dir_name.c */
Figure 9
The source code is available here:

http://www.di-mare.com/adolfo/p/src/autotest.zip


Acknowledgments [<>] [\/] [/\]

      Alejandro Di Mare made valuable suggestions that helped improve earlier versions of this work. The Centro de Investigación en Matemática Pura y Aplicada [CIMPA] at the Universidad de Costa Rica provided resources for this research. The spelling and grammar were checked using the http://spellcheckplus.com/ tool.


References [<>] [\/] [/\]


           
[Allison-2000] Allison, Chuck: The Simplest Automated Unit Test Framework That Could Possibly Work, C/C++ Users Journal, 18, No.9, Sep 2000.
      http://www.ddj.com/cpp/184401279
[Beck-2002] Beck, Kent: Test driven development, Addison Wesley, Reading, MA, USA, 2002.
[DiM-2008] Di Mare, Adolfo: BUnit.h: Un módulo simple para aprender prueba unitaria de programas en C++, Reporte Técnico ECCI-2008-02, Escuela de Ciencias de la Computación e Informática, Universidad de Costa Rica, 2008.
      ./BUnit.htm
[DiM-2011] Di Mare, Adolfo: Especificación de módulos con ejemplos y casos de prueba, Artículo CAL002 del V Taller de Calidad en las Tecnologías de la Información y las Comunicaciones (TCTIC-2011), realizado del 7 al 11 de febrero de 2011 en el Palacio de Convenciones de la Habana, en la Habana, Cuba.
      ./BUnitXP.htm
[Str-2009] Stroustrup, Bjarne: Programming Principles and Practice Using C++, ISBN 978-0-321-54372-1, Addison-Wesley; 2009.
      http://www.stroustrup.com/programming.html

Index [<>] [\/] [/\]

[1] Motivation
[2] Building a small routine
[3] Test case definition
[4] Testing framework
[5] assert() vs assertTrue()
[6] uUnit.h
[7] The implementation
[8] Acknowledgements

References
Index
About the author
About this document
[/\] Begin [<>] Index [\/] End

 

About the author [<>] [\/] [/\]

Adolfo Di Mare: Investigador costarricense en el Centro de Investigación en Matemática Pura y Aplicada [CIMPA] y en la Escuela de Ciencias de la Computación e Informática [ECCI] de la Universidad de Costa Rica [UCR], en donde ostenta el rango de Profesor Catedrático. Trabaja en las tecnologías de Programación e Internet. También es Catedrático de la Universidad Autónoma de Centro América [UACA]. Obtuvo la Licenciatura en la Universidad de Costa Rica, la Maestría en Ciencias en la Universidad de California, Los Angeles [UCLA], y el Doctorado (Ph.D.) en la Universidad Autónoma de Centro América.
Adolfo Di Mare: Costarrican Researcher at the Centro de Investigación en Matemática Pura y Aplicada [CIMPA] and the Escuela de Ciencias de la Computación e Informática [ECCI], Universidad de Costa Rica [UCR], where he is full professor and works on Internet and programming technologies. He is Cathedraticum at the Universidad Autónoma de Centro América [UACA]. Obtained the Licenciatura at UCR, and the Master of Science in Computer Science from the University of California, Los Angeles [UCLA], and the Ph.D. at the Universidad Autónoma de Centro América.
[mailto]Adolfo Di Mare <adolfo@di-mare.com>

 

About this document [<>] [\/] [/\]

Referencia: Di Mare, Adolfo: Self Unit Testing of C++ and C routines: Technical Report 2016-01-ADH, Centro de Investigación en Matemática Pura y Aplicada [CIMPA], Universidad de Costa Rica, 2016.
Internet: http://www.di-mare.com/adolfo/p/autotest.htm       Google Translate

http://www.di-mare.com/adolfo/p/src/autotest.zip
Author: Adolfo Di Mare <adolfo@di-mare.com>
Contact: Apdo 4249-1000, San José Costa Rica
Tel: (506) 2511-6606       Fax: (506) 2511-4918
Revision: CIMPA, September 2016
Visitantors:

 

Copyright © 2016 Adolfo Di Mare
Derechos de autor reservados © 2016 Adolfo Di Mare <adolfo@di-mare.com>
[home] [<>] [/\]