CUnit:動的テストロードを使用した自動テスト

目的: CUnitフレームワークの上に「フレンドリーな」環境を作成し、開発者/テスターが追加のジェスチャーなしで新しいテストを追加できるようにします。 CUnitがフレームワークとして使用されるのはなぜですか? 簡単です。星はとても収束しています。



ここでは、CUnitの動作方法や、このフレームワークを使用してテストケースとテストスイートを記述する方法については説明しません。 これはすべて、 http://cunit.sourceforge.net/doc/index.htmlにある公式ドキュメントに記載されています





そのため、最初にディレクトリとファイルの構造を決定します。

.tests |-- suites | |-- CMakeLists.txt | |-- suite1.c | |-- suite2.c | |-- suite3.c |-- main.c |-- utils.c |-- utils.h |-- CMakeLists.txt
      
      







各テストスイートは、suitesディレクトリの個別のファイルに配置されます。 開発者またはテスターのタスクは、テストスイートを作成し、それをスイートフォルダーに入れることだけです 。 開発者/テスターから他のジェスチャーは必要ありません。テストスイートはコンパイルのためにビルドシステムによって自動的に選択され、テストの実行時にプログラム自体が選択されます。



それを組み立てた後、 runtests-実行可能プログラムとテストスイート付きモジュールを取得する必要があります。



関数の命名規則



テストケースのプレフィックスがtest_になることに同意します。 つまり、ライブラリ関数foo()をテストする場合、関数のテストケースはtest_foo()と呼ばれるべきです。



テストスイートの各動的モジュールでは、 runSuite()関数をエクスポートする必要があり、これは実行可能プログラムで呼び出されます。 この機能では、テストケースが関連付けられているCUnitを使用してテストスイートを作成する必要があります。 関数プロトタイプ:



void runSuite(vod);





動的モジュールテンプレート-テストスイート



suite1.c:

 /*  - */ static void test_foo(void) { /*  - */ } /*  - */ static void test_foo2(void) { /*  - */ } void runSuite(void) { /*  - */ }
      
      









仕組み



runtests実行可能プログラムが起動されると、環境変数TEST_MODULES_DIRが設定されていない場合は、すべての動的モジュール( スイートディレクトリからテストスイート)がロードされ、各モジュールのrunSuite()関数が実行されます。 環境変数TEST_MODULES_DIRが指定されている場合、モジュールはこの変数が指すディレクトリからロードされます



実装



最初に、メインモジュールと、動的モジュールを検索する補助機能を実装します。 関数はmain.cファイルに実装されます:

 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <dlfcn.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #include <CUnit/Basic.h> #include "utils.h" int modules_alphasort(const char **a, const char **b) { return strcoll(*a, *b); } void (*runSuite)( void); /*     */ size_t searchModulesInDir( char ***m_list, char *dir ) { DIR *modules_dir = NULL; struct dirent *ent = NULL; size_t count = 0; char **modules_list = NULL; char *error = NULL; void *mem_module = NULL; void *module_handle = NULL; unsigned int allocated_mem = 0; errno = 0; if( !dir ) { return -1; } modules_dir = opendir( dir ); if( !modules_dir ) { fprintf( stderr, "%s: %s\n", dir, strerror(errno)); return -1; } while( ( ent = readdir( modules_dir ) ) ) { if( strncmp( ent->d_name, ".", 1 ) == 0 || strstr( ent->d_name, ".so" ) == NULL ) { continue; } size_t mem_len = ( strlen( ent->d_name ) + strlen( dir ) ) * sizeof( char ) + 2; char *module_path = malloc( mem_len ); memset(module_path, 0, mem_len); if( !module_path ) { fprintf( stderr, "%s\n", strerror(errno) ); return -1; } strncat( module_path, dir, strlen( dir ) * sizeof( char ) ); strncat( module_path, "/", 1 ); strncat( module_path, ent->d_name, strlen( ent->d_name ) * sizeof( char ) ); module_handle = dlopen ( module_path, RTLD_LAZY ); if( !module_handle ) { fprintf( stderr, "Could not load module: '%s'\n", dlerror()); free( module_path ); continue; } dlerror(); runSuite= dlsym( module_handle, "runSuite" ); error = dlerror(); if( error ) { fprintf( stderr, "Could not load module: %s\n", error); dlclose( module_handle ); free( module_path ); continue; } mem_module = realloc( modules_list, allocated_mem + strlen(module_path)); allocated_mem += strlen(module_path); if( !mem_module ) { fprintf( stderr, "%s\n", strerror(errno)); free( module_path ); dlclose( module_handle ); return -1; } modules_list = mem_module; modules_list[ count ] = module_path; count++; dlclose( module_handle ); } closedir( modules_dir ); qsort(modules_list, count, sizeof(char *), (int (*)(const void *, const void *))modules_alphasort); *m_list = modules_list; return count; } int main() { char *modules_dir = NULL; char *env_modules_dir = NULL; struct stat dir_info; size_t modules_total = 0; char **modules = NULL; size_t i = 0; void *module_handle = NULL; env_modules_dir = getenv( "TEST_SUITES_DIR" ); modules_dir = ( env_modules_dir ) ? env_modules_dir : "./suites"; if( stat( modules_dir, &dir_info ) < 0 ) { fprintf( stderr, "%s: %s\n", modules_dir, strerror(errno)); return 1; } if( !S_ISDIR( dir_info.st_mode ) ) { fprintf( stderr, "'%s' is not a directory\n", modules_dir); return 1; } if( access( modules_dir, R_OK | X_OK ) != 0 ) { fprintf( stderr, "Directory '%s' is not accessible\n", modules_dir ); return 1; } modules_total = searchModulesInDir( &modules, modules_dir); if(modules_total <= 0) { fprintf( stderr, "No test suites\n"); return 0; } CUnitInitialize(); for( i = 0; i < modules_total; i++ ) { module_handle = dlopen ( modules[i], RTLD_LAZY ); if( !module_handle ) { fprintf( stderr, "Module '%s'\n", dlerror()); continue; } runSuite = dlsym( module_handle, "runSuite" ); runSuite(); } CU_basic_set_mode(CU_BRM_VERBOSE); CU_basic_run_tests(); CUnitUInitialize(); return CU_get_error(); }
      
      









ヘルパー関数と環境マクロ



テストケースのプレフィックスを絶えず手動で書き込まないようにするには、さらに悪いことに、プレフィックスが後で変更された場合、すべてのテストケースの名前を変更せずに、補助マクロTEST_FUNCTを作成します。



 #define TEST_FUNCT(name) \ static void test_##name()
      
      







書く代わりに:



 static void test_foo() { /* Some code */ }
      
      







書く:



 TEST_FUNCT(foo) { /* Some code */ }
      
      







別のマクロADD_SUITE_TESTを追加して、テストスイートにテストケースを追加します。

 #define ADD_SUITE_TEST(suite, name) \ if ((NULL == CU_add_test(suite, #name, (CU_TestFunc)test_##name))) {\ CU_cleanup_registry();\ return;\ }\
      
      







さて、最後に必要なのは、テストスイートCUnitCreateSuite()を作成するためのヘルパー関数です。



補助関数のマクロとプロトタイプは、utils.hファイルにあります。



 #ifndef __UTILS_H__ #define __UTILS_H__ #include <stdio.h> #include <stdlib.h> #include <CUnit/Basic.h> #define TEST_FUNCT(name) \ static void test_##name() #define ADD_SUITE_TEST(suite, name) \ if ((NULL == CU_add_test(suite, #name, (CU_TestFunc)test_##name))) {\ CU_cleanup_registry();\ return;\ }\ CU_pSuite CUnitCreateSuite(const char* title); void CUnitInitialize(void); void CUnitUInitialize(void); #endif
      
      







utils.cファイルで、補助機能を実装します。



 #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <CUnit/Basic.h> #include "utils.h" void CUnitUInitialize(void) { CU_cleanup_registry(); } void CUnitInitialize(void) { if (CU_initialize_registry() != CUE_SUCCESS) { fprintf(stderr, "Failed to initialize the CUnit registry: %d\n", CU_get_error()); exit(1); } } static int initSuite(void) { return 0; } static int cleanSuite(void) { return 0; } CU_pSuite CUnitCreateSuite(const char* title) { CU_pSuite suite = NULL; suite = CU_add_suite(title, initSuite, cleanSuite); if (suite == NULL) { CU_cleanup_registry(); return NULL; } return suite; }
      
      







次に、テストスイートを作成しましょう。

 #include <stdio.h> #include <stdlib.h> #include <CUnit/Basic.h> #include "utils.h" TEST_FUNCT(foo) { /*   */ CU_ASSERT_EQUAL(0, 1); } TEST_FUNCT(foo2) { /*   */ CU_ASSERT_EQUAL(1, 1); } void runSuite(void) { CU_pSuite suite = CUnitCreateSuite("Suite1"); if (suite) { ADD_SUITE_TEST(suite, foo) ADD_SUITE_TEST(suite, foo2) } }
      
      







組立



ファイルスイート/ CMakeLists.txt:

 MACRO(ADD_MODULE file) ADD_LIBRARY( ${file} MODULE ${file}.c ../utils.c ) TARGET_LINK_LIBRARIES( ${file} cunit ) SET_TARGET_PROPERTIES( ${file} PROPERTIES PREFIX "" LIBRARY_OUTPUT_DIRECTORY "." ) ENDMACRO(ADD_MODULE file) FILE(GLOB C_FILES RELATIVE "${CMAKE_SOURCE_DIR}/suites" "${CMAKE_SOURCE_DIR}/suites/*.c") INCLUDE_DIRECTORIES ( "${CMAKE_SOURCE_DIR}" ) FOREACH ( module ${C_FILES} ) STRING( REGEX REPLACE ".c$" "" module "${module}" ) MESSAGE(STATUS "Found test suite: ${module}") ADD_MODULE(${module}) ENDFOREACH ( module ${MODULES} )
      
      







CMakeLists.txtファイル:

 CMAKE_MINIMUM_REQUIRED (VERSION 2.6) SET(CMAKE_VERBOSE_MAKEFILE ON) PROJECT("runtest") SET(CMAKE_C_FLAGS " -std=c99 -O3 -Wall -Wextra -Wimplicit") INCLUDE_DIRECTORIES ( "/usr/include" ) ADD_EXECUTABLE(runtests main.c utils.c) TARGET_LINK_LIBRARIES(runtests cunit dl) ADD_CUSTOM_TARGET(test "./runtests" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" VERBATIM) ADD_SUBDIRECTORY(suites)
      
      







 antonio:antonio $ cmakeをテストします。
アントニオ:アントニオ$のテスト
アントニオ:アントニオ$ ./runtestsをテストします









All Articles