#include <CUnit/CUnit.h> #include <CUnit/Basic.h> //#include <CUnit/Console.h> // CU_basic_run_tests() 를 사용할 때 인클루드 void sort(int array[], int num); void test_1(void); void test_2(void); void test_3(void); void test_4(void); void test_5(void); int main(){ CU_pTestRegistry test_registry1; // 테스트레지스트리 1 CU_pTestRegistry test_registry2; // 테스트레지스트리 2 CU_pSuite sort_suite1; // 테스트 시트 1 CU_pSuite sort_suite2; // 테스트 시트 2 CU_initialize_registry(); sort_suite1 = CU_add_suite("Sort1", NULL, NULL); CU_add_test(sort_suite1, "test_01", test_1); CU_add_test(sort_suite1, "test_02", test_2); CU_basic_run_tests(); // 여기서 일단의 테스트를 실행 // 테스트 레지스트리를 작성해 액티브를 교체 test_registry2 = CU_create_new_registry(); test_registry1 = CU_set_registry(test_registry2); sort_suite2 = CU_add_suite("Sort2", NULL, NULL); CU_add_test(sort_suite2, "test_03", test_3); CU_add_test(sort_suite2, "test_04", test_4); CU_add_test(sort_suite2, "test_05", test_5); CU_basic_set_mode(CU_BRM_NORMAL); // 요약과 에러만을 표시하도록 설정 CU_basic_run_tests(); // 여기서 2 번째의 테스트레지스트리 실행 CU_cleanup_registry(); // 현재의 액티브는 test_registry2 CU_destroy_existing_registry(&test_registry1); // 레지스트리 파기 return(0); } void sort(int array[], int num){ int i; int j; int val; for(i=0; i<(num-1); i++){ for(j=(num-1); j>i; j--){ if(array[j-1] > array[j]){ val = array[j]; array[j] = array[j]; // 코드 수정 array[j-1] = val; } } } } void test_1(void){ int array[] = {3}; sort(array, 1); CU_ASSERT(array[0] == 3); } void test_2(void){ int array[] = {11,7,5,3,2}; sort(array, 5); CU_ASSERT(array[0] == 2); CU_ASSERT(array[1] == 3); CU_ASSERT(array[2] == 5); CU_ASSERT(array[3] == 7); CU_ASSERT(array[4] == 11); } void test_3(void){ int array[] = {7,11,3,2,5}; sort(array, 5); CU_ASSERT(array[0] ==2); CU_ASSERT(array[1] ==3); CU_ASSERT(array[2] ==5); CU_ASSERT(array[3] ==7); CU_ASSERT(array[4] ==11); } void test_4(void){ int array[] = {10,9,8,7,6,5,4,3,2,1}; sort(array,10); CU_ASSERT(array[0] ==1); CU_ASSERT(array[1] ==2); CU_ASSERT(array[2] ==3); CU_ASSERT(array[3] ==4); CU_ASSERT(array[4] ==5); CU_ASSERT(array[5] ==6); CU_ASSERT(array[6] ==7); CU_ASSERT(array[7] ==8); CU_ASSERT(array[8] ==9); CU_ASSERT(array[9] ==10); } void test_5(void){ int array[] = {2,9,3,6,10,5,8,4,1,7}; sort(array, 10); CU_ASSERT(array[0] ==1); CU_ASSERT(array[1] ==2); CU_ASSERT(array[2] ==3); CU_ASSERT(array[3] ==4); CU_ASSERT(array[4] ==5); CU_ASSERT(array[5] ==6); CU_ASSERT(array[6] ==7); CU_ASSERT(array[7] ==8); CU_ASSERT(array[8] ==9); CU_ASSERT(array[9] ==10); }
컴파일 후에 실행하면, 다음과 같다.
CUnit - A Unit testing framework for C - Version 2.1-0 http://cunit.sourceforge.net/ Suite Sort1, Test test_02 had failures: 1. d.c:73 - array[1] == 3 2. d.c:74 - array[2] == 5 3. d.c:75 - array[3] == 7 4. d.c:76 - array[4] == 11 --Run Summary: Type Total Ran Passed Failed suites 1 1 n/a 0 tests 2 2 1 1 asserts 6 6 2 4 CUnit - A Unit testing framework for C - Version 2.1-0 http://cunit.sourceforge.net/ Suite Sort2, Test test_03 had failures: 1. d.c:84 - array[1] ==3 2. d.c:85 - array[2] ==5 3. d.c:86 - array[3] ==7 4. d.c:87 - array[4] ==11 Suite Sort2, Test test_04 had failures: 1. d.c:97 - array[1] ==2 2. d.c:98 - array[2] ==3 3. d.c:99 - array[3] ==4 4. d.c:100 - array[4] ==5 5. d.c:101 - array[5] ==6 6. d.c:102 - array[6] ==7 7. d.c:103 - array[7] ==8 8. d.c:104 - array[8] ==9 9. d.c:105 - array[9] ==10 Suite Sort2, Test test_05 had failures: 1. d.c:114 - array[1] ==2 2. d.c:115 - array[2] ==3 3. d.c:116 - array[3] ==4 4. d.c:117 - array[4] ==5 5. d.c:118 - array[5] ==6 6. d.c:119 - array[6] ==7 7. d.c:120 - array[7] ==8 8. d.c:121 - array[8] ==9 9. d.c:122 - array[9] ==10 --Run Summary: Type Total Ran Passed Failed suites 1 1 n/a 0 tests 3 3 0 3 asserts 25 25 3 22
위의 결과를 통해, 2 개의 테스트 레지스트리가 실행되는 것을 볼 수 있다.
====== 테스트 시트 ======
앞의 예제에서 보인 것처럼, 액티브 테스트 레지스트리에 테스트 시트를 추가하는 함수는 CU_add_suite() 이다.
| 함수 | CU_pSuite CU_add_suite(const char* strName, CU_InitializeFunc pInit, CU_CleanupFunc pClean) |
| 설명 | 첫번째 인수로 나타나는 이름, 두번째 인수로 나타나는 초기화 함수, 세번째 인수로 나타나는 클린 업 함수를 가지는 테스트 시트 영역을 작성하여, 액티브 테스트 레지스트리에 등록한다. 리턴 값은 테스트시트 영역의 포인터이다. 에러가 발생했을 경우는 NULL 을 돌려준다. 이 함수는 호출전에 테스트레지스트리가 있어야 한다 |
첫번째 인수인 이름은 테스트레지스트리에 같은 이름이 있으면 안됩니다. 초기화 함수와 클린 업 함수는 해당 테스트시트의 실행 전과 실행 후에 자동적으로 호출되는 함수입니다. 이 함수들은 인수가 없으며, 성공적으로 끝났을 때는 0 을 돌려줍니다. (처리가 실패했을 경우에는 0 이외의 값을 돌려줍니다.) 초기화함수/클린업 함수가 불필요하면 NULL 을 지정합니다.
테스트 시트 영역의 포인터가 리턴되므로, 데이터 형식(구조체)에 대해 알아 두면 좋을 것입니다.
typedef struct CU_Suite{ char* pName; CU_pTest pTest; CU_InitializeFunc pInitializeFunc; CI_CleanupFunc pCleanupFunc; unsigned int uiNumberOfTests; struct CU_Suite* pNext; struct CU_Suite* pPrev; }CU_Suite; typedef CU_Suite* CU_pSuite;
구조체는 테스트시트의 이름, 테스트케이스 리스트의 맨 앞쪽 포인터, 초기화 함수와 클린 업 함수, 테스트케이스의 수를 관리하고 있다. 마지막 두개의 요소는 리스트의 요소라는 걸 알 수 있다.
함수 CU_add_suite() 는 테스트시트 영역의 포인터를 돌려주므로, 실패 여부는 리턴 값이 NULL 인지를 확인하는 것으로 알아볼 수 있다. 혹시 에러를 상세히 알고 싶다면 CUnit 의 에러 관리의 구조를 이용하면 된다.
예제를 통해 사용법을 알아보자!
#include <CUnit/CUnit.h> #include <CUnit/Console.h> #include <CUnit/Basic.h> void sort(int array[], int num); void test_1(void); void test_2(void); void test_3(void); void test_4(void); void test_5(void); int main(){ CU_ErrorCode errorCode; // 테스트케이스의 배열 CU_TestInfo test_array1[] = { {"Test_1", test_1}, {"Test_2", test_2}, {"Test_3", test_3}, {"Test_4", test_4}, {"Test_5", test_5}, CU_TEST_INFO_NULL }; // 테스트시트의 배열 CU_SuiteInfo suites_array1[] = { {"Suite_1", NULL, NULL, test_array1}, CU_SUITE_INFO_NULL }; CU_initialize_registry(); errorCode = CU_register_suites(suites_array1); // 여기서 등록 CU_console_run_tests(); CU_cleanup_registry(); return(0); } void sort(int array[], int num){ int i; int j; int val; for(i=0; i<(num-1); i++){ for(j=(num-1); j>i; j--){ if(array[j-1] > array[j]){ val = array[j]; array[j] = array[j]; array[j-1] = val; } } } } void test_1(void){ int array[] = {3}; sort(array, 1); CU_ASSERT(array[0] == 3); } void test_2(void){ int array[] = {11,7,5,3,2}; sort(array, 5); CU_ASSERT(array[0] == 2); CU_ASSERT(array[1] == 3); CU_ASSERT(array[2] == 5); CU_ASSERT(array[3] == 7); CU_ASSERT(array[4] == 11); } void test_3(void){ int array[] = {7,11,3,2,5}; sort(array, 5); CU_ASSERT(array[0] ==2); CU_ASSERT(array[1] ==3); CU_ASSERT(array[2] ==5); CU_ASSERT(array[3] ==7); CU_ASSERT(array[4] ==11); } void test_4(void){ int array[] = {10,9,8,7,6,5,4,3,2,1}; sort(array,10); CU_ASSERT(array[0] ==1); CU_ASSERT(array[1] ==2); CU_ASSERT(array[2] ==3); CU_ASSERT(array[3] ==4); CU_ASSERT(array[4] ==5); CU_ASSERT(array[5] ==6); CU_ASSERT(array[6] ==7); CU_ASSERT(array[7] ==8); CU_ASSERT(array[8] ==9); CU_ASSERT(array[9] ==10); } void test_5(void){ int array[] = {2,9,3,6,10,5,8,4,1,7}; sort(array, 10); CU_ASSERT(array[0] ==1); CU_ASSERT(array[1] ==2); CU_ASSERT(array[2] ==3); CU_ASSERT(array[3] ==4); CU_ASSERT(array[4] ==5); CU_ASSERT(array[5] ==6); CU_ASSERT(array[6] ==7); CU_ASSERT(array[7] ==8); CU_ASSERT(array[8] ==9); CU_ASSERT(array[9] ==10); }
컴파일 후에 실행해보면, 예전과 동일하게 동작하는 것을 확인할 수 있다.
====== 테스트의 실행 ======
테스트를 실행하는 방법으로 콘솔 상에서 인터랙티브하게 사용하는 방법(CU_console_run_tests()를 사용)과 배치처리하는 방법(CU_basic_run_tests()를 사용)을 해 보았다.
CUnit 에서는 이들을 포함하여 모두 4 개의 실행모드가 있다.
===== Automated 모드 =====
배치적으로 처리하는 모드이다. 출력은 XML 형식의 파일이다. Unit/Automated.h 를 인클루드 한다. 모든 플랫폼에서 사용 가능하다.
===== Basic 모드 =====
배치적으로 처리하는 모드이다. 출력은 표준 출력이다. CUnit/Basic.h 를 인클루드 한다. 모든 플랫폼에서 사용가능하다.
===== Console 모드 =====
인터랙티브한 조작을 받아들이는 모드이다. 출력은 표준 출력이다. CUnit/Console.h 를 인클루드 한다. 모든 플랫폼에서 사용가능하다.
===== Curses 모드 =====
인터랙티브한 조작을 받아들이는 모드이다. 표준 출력과는 달리, 화면밖으로 표시되는 정보도, 커서 키 스크롤을 통해 확인할 수 있다. CUnit/CUCurses.h 를 인클루드하고, curses 라이브러리의 링크도 필요하다. UNIX 계열만 사용가능하다.
====== 에러 처리 ======
===== 에러 상태의 취득 =====
테스트시트에서 살펴보았듯이 에러상태를 얻어오는 함수가 있다. 여기에는 에러코드를 리턴하는 함수와 에러 메세지 문자열을 리턴하는 함수 2 가지가 있다.
| 함수 | CU_ErrorCode CU_get_error(void) |
| 설명 | 에러코드를 리턴한다 |
| 함수 | const char* CU_get_error_msg(void) |
| 설명 | 에러 메세지 문자열을 리턴한다 |
에러 코드는 열거형으로써 CUnit/CUError.h 에 정의되어 있다. 이 파일은 CUnit/CUnit.h 에서도 호출되고 있으므로, 파일 인클루드에 상관없이 이용 가능하다. 다음은 에러코드 목록이다.
| CUE_SUCCESS | 성공(에러 없음) |
| CUE_NOMEMORY | 메모리 할당 실패 |
| CUE_NOREGISTRY | 테스트 레지스트리 초기화안됨 |
| CUE_REGISTRY_EXISTS | CU_cleanup_registry()를 실행하지 않고 CU_set_registry() 를 실행 |
===== 에러 발생시의 행동 =====
에러가 발생해도 CUnit 은 계속 동작한다. 하지만, 에러가 발생시 테스트를 멈추거나 경우에 따라서는 어플리케이션을 종료하고 싶은 경우 다음의 함수를 사용한다.
| 함수 | void CU_set_error_action(CU_ErrorAction action) |
| 설명 | 에러 발생시의 행동을 설정한다 |
| 함수 | CU_ErrorAction CU_get_error_action(void) |
| 설명 | 설정된 에러 발생시의 행동을 리턴한다 |
CU_ErrorAction 이라는 타입은 열거형이다. 다음의 값을 이용할 수 있다.
- CUEA_IGNORE : 에러가 발생해도, 계속 진행한다.
- CUEA_FAIL : 에러가 발생하면 테스트를 멈춘다.
- CUEA_ABORT : 에러가 발생하면 어플리케이션에서 빠져 나온다. (exit() 를 실행)
====== 마치면서 ======
더 자세한 내용은 cunit.zip 에서 볼 수 있다.