Unit Test
xUnit Principles
Write test suite for each unit in the program
All test can be executed (automatically) at any time
For each program modification all tests must be passed before the modification is regarded as complete - regression testing
Test First – implement later!
TDD (Test Driven Development) cycle
- Write test case, and check it fails
- Write the new code
- Check that the test passes (and maybe refactor, re-test)
Concepts
Assertions, boolean expression that compares expected and actual results
Test Case
- A composition of concrete test procedures
- May contain several assertions and test for several test objectives
- E.g all test of a particular function
Test Suite
- Collection of related test cases
- Can be executed automatically in a single command
Assertion
// hello.h
#ifndef HELLO_H
#define HELLO_H
char * hello();
#endif
// hello.c
#include <stdio.h>
#include "hello.h"
char * hello()
{
return "Hello ";
}
// world.h
#ifndef WORLD_H
#define WORLD_H
char * world();
#endif
// world.c
#include <stdio.h>
#include "world.h"
char * world()
{
return "World!\n";
}
// test.c
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include "hello.h"
#include "world.h"
int main()
{
assert(strcmp(hello(), "Hello ") == 0);
assert(strcmp(world(), "World!\n") == 0);
printf("Complete the test sucessfully ...\n");
return 0;
}
// CMakeLists.txt 1
cmake_minimum_required(VERSION 2.6)
project(c8)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build/lib)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build/bin)
include_directories(${PROJECT_SOURCE_DIR}/include)
link_directories(${PROJECT_SOURCE_DIR}/build/lib)
add_subdirectory(src)
add_subdirectory(test)
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/install)
set(CMAKE_C_FLAGS "-O2")
// CMakeLists.txt 2
set(CMAKE_MACOSX_RPATH 1)
add_library(helloworld_lib_shared SHARED hello.c world.c)
set_target_properties(helloworld_lib_shared PROPERTIES OUTPUT_NAME "helloworld")
set_target_properties(helloworld_lib_shared PROPERTIES VERSION 1.2 SOVERSION 1)
INSTALL(TARGETS helloworld_lib_shared
LIBRARY DESTINATION lib)
INSTALL(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include)
// CMakeLists.txt 3
file(GLOB_RECURSE c_files *.c)
add_executable(mytest ${c_files})
target_link_libraries(mytest helloworld)
INSTALL(TARGETS mytest
RUNTIME DESTINATION bin)
CUnit
- Write functions for tests (and suite init/cleanup if necessary)
- Initialize the test registry - CU_initialize_registry()
- Add suites to the test registry - CU_add_suite()
- Add tests to the suites - CU_add_test()
- Run tests using an appropriate interface, e.g. CU_console_run_tests
- Cleanup the test registry - CU_cleanup_registry
// util.h
#ifndef UTIL_H
#define UTIL_H
int doubleNum(int);
#endif
// hello.h
#ifndef HELLO_H
#define HELLO_H
char * hello();
#endif
// test_1.h
#ifndef TEST_1_H
#define TEST_1_H
void test_hello();
#endif
// test_2.h
#ifndef TEST_2_H
#define TEST_2_H
void test_doubleNum();
#endif
// hello.c
#include "hello.h"
char * hello()
{
return "Hello World!\n";
}
// util.c
#include "util.h"
int doubleNum(int n)
{
return 2*n;
}
// test_1.c
#include <stdio.h>
#include <string.h>
#include <CUnit/CUnit.h>
#include "hello.h"
void test_hello()
{
CU_ASSERT(strcmp(hello(), "Hello World\n") == 0);
}
// test_2.c
#include <stdio.h>
#include <CUnit/CUnit.h>
#include "util.h"
void test_doubleNum()
{
CU_ASSERT(doubleNum(10) == 20);
CU_ASSERT(doubleNum(0) == 0);
CU_ASSERT(doubleNum(-1) == -2);
}
// test.c
#include <stdio.h>
#include <string.h>
#include <CUnit/CUnit.h>
#include "CUnit/Basic.h"
#include "CUnit/Automated.h"
#include "hello.h"
#include "util.h"
#include "test_1.h"
#include "test_2.h"
int suitInit() {return 0;}
int suitClean() {return 0;}
int main()
{
// initialize registry
if (CUE_SUCCESS != CU_initialize_registry())
return CU_get_error();
// add a suit to the registry
CU_pSuite pSuite = NULL;
pSuite = CU_add_suite("Suite_1", suitInit, suitClean);
if (NULL == pSuite) {
CU_cleanup_registry();
return CU_get_error();
}
// add tests to the suit
if ((NULL == CU_add_test(pSuite, "test of hello()", test_hello)) ||
(NULL == CU_add_test(pSuite, "test of hello()", test_doubleNum)))
{
CU_cleanup_registry();
return CU_get_error();
}
// run all tests using the CUnit Basic interface, output report to stdout
CU_basic_set_mode(CU_BRM_VERBOSE);
CU_basic_run_tests();
// run all tests using the automated interface, output report to xml files
CU_automated_run_tests();
CU_list_tests_to_file();
// clean registry
CU_cleanup_registry();
return CU_get_error();
}
// CMakeLists.txt 1
cmake_minimum_required(VERSION 2.6)
project(unittest)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build/lib)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build/bin)
include_directories(${PROJECT_SOURCE_DIR}/include /usr/local/include)
link_directories(${PROJECT_SOURCE_DIR}/build/lib)
add_subdirectory(src)
add_subdirectory(test)
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/install)
set(CMAKE_C_FLAGS "-O2")
// CMakeLists.txt 2
set(CMAKE_MACOSX_RPATH 1)
add_library(unittest_lib_shared SHARED hello.c util.c)
set_target_properties(unittest_lib_shared PROPERTIES OUTPUT_NAME "unittest")
set_target_properties(unittest_lib_shared PROPERTIES VERSION 1.2 SOVERSION 1)
INSTALL(TARGETS unittest_lib_shared
LIBRARY DESTINATION lib)
INSTALL(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include)
file(GLOB_RECURSE c_files *.c)
add_executable(my_unit_test ${c_files})
find_library(TEST_LIBRARY cunit HINTS /usr/local/lib)
message("Test: " ${TEST_LIBRARY})
target_link_libraries(my_unit_test ${TEST_LIBRARY} unittest)
INSTALL(TARGETS my_unit_test
RUNTIME DESTINATION bin)
Functions
CU_initialize_registry(), initialize CUnit test
- CUE_SUCCESS, initialization was successful
- CUE_NOMEMORY, memory allocation failed
CU_cleanup_registry(), clean up and release memory used by the framework
CU_add_suite(name, initialization function, cleanup function)
- creates a new test collection (suite) having the specified name, initialization function, and cleanup function
- the initialization and cleanup functions are optional, and are passed as pointers to functions to be called before and after running the tests contained in the suite
CU_add_test(suit, name, test function)
- creates a new test having the specified name and test function, and registers it with the specified suite
CU_get_error()
- set an error code indicating the framework error status
CU_BasicRunMode()
- CU_BRM_NORMAL, normal mode, failures and run summary are printed
- CU_BRM_SILENT, silent mode, no output is printed except framework error messages
- CU_BRM_VERBOSE, verbose mode - maximum output of run details
CU_basic_run_tests(), runs all tests in all registered suites
CU_basic_run_suite(CU_pSuite pSuite), run tests in a specific suit
CU_basic_run_test(CU_pSuite pSuite, CU_pTest pTest), runs a single test in a specified suite
CU_basic_show_failures(), print a summary of run failures to stdout
CU_get_failure_list(), return a linked list of failures
CU_automated_run_tests(), runs all tests in all registered suites, generate xml files
CU_list_tests_to_file(), lists the registered suites and associated tests to file
CU_console_run_tests(), the console interface is interactive
Reference