CUnit编程指南(中文)

1 篇文章 0 订阅

文章目录


本文为对《CUnit Programmer Guide》翻译,加入了一些自己的理解和重新排版,省略了一些表格,链接为英文原版的链接:

http://cunit.sourceforge.net/doc/index.html

###简介
CUnit是一个用于在C中编写,管理和运行单元测试的系统,它被构建为一个与用户测试代码链接的静态库。CUnit是一个平台无关框架与各种用户界面的组合。核心框架为管理测试注册表,套件和测试用例提供了基本支持。用户界面便于与框架交互以运行测试和查看结果。
CUnit的框架组织方式类似于传统的单元测试框架:

CUnit

主要分为三个级别,所有的测试用例分别放到不同的测试包(suite)里,而这些测试包由放到同一个测试注册表(registry)中。注册表中的所有包/测试都可以使用单个函数调用运行,或可以运行所有的测试。
CUnit框架使用的典型步骤如下:
cunit-process

###编写CUnit测试用例

####测试函数
除了不应该修改CUnit框架内的函数外,CUnit对测试函数的内容是没有限制的,同时,测试函数可以调用其他函数。下面是返回两个int型数值中最大值的函数测试用例:

	int maxi(int i1, int i2)
    {
      return (i1 > i2) ? i1 : i2;
    }

    void test_maxi(void)
    {
      CU_ASSERT(maxi(0,2) == 2);
      CU_ASSERT(maxi(0,-2) == 0);
      CU_ASSERT(maxi(2,2) == 2);
    }

####CUnit断言
CUnit提供了一组测试逻辑条件的断言。这些断言的成功或失败由框架跟踪,并且可以在测试运行完成时查看。每个断言只能测试一个逻辑条件,失败则为FALSE。失败时,除非用户选择了 xxx_FATAL 版本的断言,否则测试程序不会结束。如果用户选择了 xxx_FATAL 版本的断言,测试程序(函数)会立即返回,测试程序将没有机会清除注册表。所以,FATAL版本的断言需要谨慎使用。
还有一些特殊的“断言”并不是用来检测逻辑条件,这些用于测试控制流程或其他不需要逻辑测试的条件:

void test_longjmp(void)
{
	jmp_buf buf;
	int i;

	i = setjmp(buf);
	if (i == 0) {
		run_other_func();
	    CU_PASS("run_other_func() succeeded.");
	}
	else
	    CU_FAIL("run_other_func() issued longjmp.");
}

测试函数调用的其他函数也可以自由的使用CUnit断言,同样也可以使用FATAL的版本,如果在这样的断言处失败,测试函数(整个调用链)将会被打断。

###测试注册表(registry)


/***************************** 
- 内部结构体
- 测试注册表是测试套件的仓库,CUnit维护一个活动的测试注册表,用户在添加套件或者测试时会自动更新这个注册表。
*****************************/
typedef struct CU_TestRegistry
{
	unsigned int uiNumberOfSuites;  //< 存储在注册表中的所有套件数
    unsigned int uiNumberOfTests;   //< 所有测试数
    CU_pSuite    pSuite;            //< 指向注册套件的链表头的指针
} CU_TestRegistry;
typedef CU_TestRegistry*  CU_pTestRegistry

/***************************** 
- 初始化注册表,一个测试注册表在使用之前必须被初始化,这个函数必须在所有的测试函数调用之前调用
- 返回值可能有两个 
- CUE_SUCCESS 初始化成功
- CUE_NOMEMORY 初始化时内存申请失败 
*****************************/
CU_ErrorCode     CU_initialize_registry(void)

/***************************** 
- 测试结束时,用户需要调用此函数以清除并释放框架使用的内存,这个函数框架中最后一个被调用的函数(与上面那个函数是成对出现的)
- 该函数如果未调用或者执行失败可能会造成内存泄漏,所以可以调用多次确保不会失败。同时要注意的是,这个函数一旦调用,注册表里的所有套件以及测试都将被销毁,所以指向这些套件或者测试的指针在这之后也不能再引用。
*****************************/
void             CU_cleanup_registry(void)

/***************************** 
- 返回指向测试注册表的指针
- CUnit并不推荐使用该接口对内部的注册表进行直接的操作,推荐使用API来操作
*****************************/
CU_pTestRegistry CU_get_registry(void)

/***************************** 
- 用一个测试注册表去替换当前活动的测试注册表,而返回的是之前活动的注册表的指针。用户需要自己去销毁原来的那个注册表(CU_destroy_existing_registry)。应该注意的是不要显示的销毁当前活动的注册表。
*****************************/
CU_pTestRegistry CU_set_registry(CU_pTestRegistry pTestRegistry)

/***************************** 
- 创建一个新注册表,返回一个指向它的指针。这个新的注册表将不包含任何的套件和测试,同样,它的销毁将由用户自己负责。
*****************************/
CU_pTestRegistry CU_create_new_registry(void)

/***************************** 
- 销毁一个指定的注册表并释放所有相关的内存
- 当前活动的注册表不能用该函数进行销毁,当前活动的注册表将由CU_cleanup_registry来销毁
*****************************/
void             CU_destroy_existing_registry(CU_pTestRegistry* ppRegistry)

###管理套件和测试(suite & test)

typedef struct CU_Suite
typedef CU_Suite*  CU_pSuite

typedef struct CU_Test
typedef CU_Test*  CU_pTest

/***************************** 
- 三个函数指针
*****************************/
typedef void (*CU_TestFunc)(void)  
typedef int  (*CU_InitializeFunc)(void)
typedef int  (*CU_CleanupFunc)(void)

/***************************** 
- 向注册表中添加套件
- 创建一个新的测试套件(suite),该套件有一个strName作为标志,同时有自己的初始化函数和清除函数。新的套件向注册表注册并被其拥有,所以注册表必须在添加套件之前就已经初始化完成。(当前并不支持创建独立于注册表以外的套件)
- 套件的名称必须是唯一的,而初始化和清除函数则是可选的,并作为函数指针传递到此函数中。这些函数不带参数,如果成功则返回0,否则为非0。如果一个套件不需要这两个函数中的一个或两个,则传递NULL即可。
- 函数返回一个指向新建的套件的指针,这个指针在添加测试时会被用到。如果新建过程中出现错误,将返回NULL。框架的ERROR_CODE也会被设为以下的某一个值:
  CUE_SUCCESS     新建成功
  CUE_NOREGISTRY  没有被初始化的注册表
  CUE_NO_SUITENAME  没有指定套件名
  CUE_DUP_SUITE   套件的名称不唯一
  CUE_NOMENORY    内存分配失败
*****************************/
CU_pSuite CU_add_suite(const char* strName,
                       CU_InitializeFunc pInit,
                       CU_CleanupFunc pClean);


/***************************** 
- 将测试添加到套件中
- 同样是指定一个特定的名字strName(这个测试名只需要不同于该套件里的其他测试即可),不同的是测试函数不能为NULL,同时需要制定一个添加的目标套件。
- 传入的函数指针指向的函数既没有传参也没有返回值。
- 返回值是一个指向新建的测试的指针,如果在新建过程中出现错误,将返回NULL。同样,会给框架的ERROR_CODE设定一些特定的值
  CUE_SUCCESS	新建测试成功
  CUE_NOSUITE	没有指定合法的套件
  CUE_NO_TESTNAME	没有指定测试名
  CUE_NO_TEST	没有指定测试函数名
  CUE_DUP_TEST	测试名不唯一
  CUE_NOMEMORY	内存分配失败
*****************************/
CU_pTest  CU_add_test(CU_pSuite pSuite,
                      const char* strName,
                      CU_TestFunc pTestFunc);
//< 这个宏的意义很明显,将测试函数名直接作为测试名,并将测试加入到指定的suite中去
#define CU_ADD_TEST(suite, test) \
        ( CU_add_test(suite, #test, (CU_TestFunc)test) )                   

对于包含很多测试和套件的大型测试结构,测试的管理、套件的关联即注册等冗长且很容易出错。CUnit提供了一个特殊的注册系统来帮助管理套件和测试。它的主要优点在于集中的注册套件和相关测试,并最大限度的减少用户需要写的错误检查代码。

typedef struct CU_TestInfo
typedef struct CU_SuiteInfo

/***************************** 
- 每个数组元素包含一个唯一的测试名和一个函数函数,数组必须要争有一个NULL值为元素结束,包含在单个数组中的测试用例将会被注册到单个测试套件中去。
*****************************/
CU_TestInfo test_array1 [] = { 
  {"testname1",test_func1},
  {"testname2",test_func2},
  {"testname3",test_func3},
  CU_TEST_INFO_NULL,
};

CU_SuiteInfo suites[] = {
  { "suitename1", suite1_init-func, suite1_cleanup_func, test_array1 },
  { "suitename2", suite2_init-func, suite2_cleanup_func, test_array2 },
  CU_SUITE_INFO_NULL,
};

/***************************** 
- 第一个函数可直接将上面定义的套件数组注册到注册表中去,如果中间任何一个套件或者测试注册错误,都会返回一个错误码
- 第二个函数可以注册多个suite的数组
*****************************/
CU_ErrorCode CU_register_suites(CU_SuiteInfo suite_info[]);
CU_ErrorCode CU_register_nsuites(int suite_count, ...);

//< e.g.:
CU_ErrorCode error = CU_register_nsuites(2, suites1, suites2);

###执行测试
下面所有的模式在官方的实例中均有截图实例来说明具体的操作,链接如下,这里我只介绍API

http://cunit.sourceforge.net/screenshots.html

####自动模式

/***************************** 
- 自动模式
- 自动化界面是非交互式的,测试启动后结果会输出到XML文件中,住处的测试和套件的列表也可以记录到XML文件中。
*****************************/
#include <CUnit/Automated.h>

/*
- 执行所有注册了的套件,执行结果将输出到一个名为ROOT-Reslut.xml文件中
- ROOT(文件名)可以用CU_set_output_filename函数来设置,否则使用默认的文件名:CUnitAutomated-Result.xml
- Notice: 如果每次运行之前不设置目标文件名ROOT,则结果文件将被覆盖
*/
void CU_automated_run_tests(void);

/*
将注册的套件和测试也输出到XML文件里
*/
CU_ErrorCode CU_list_tests_to_file(void);

/*
设置结果输出的文件名,输入的文件名加上-Result.xml为结果文件,加上-Listing.xml为套件和测试列表的文件
*/
void CU_set_output_filename(const char* szFilenameRoot);

####基本模式

/***************************** 
- 基本模式
- 基本模式同样是非交互式的,测试输出到标准输出中(stdout),这个模式支持执行单独的套件或测试,并且支持由客户端来控制每次执行的输出类型。这个模式为希望简化访问CUnit API的客户端提供了最大的灵活性
*****************************/
#include <CUnit/Basic.h>
  typedef enum CU_BasicRunMode
  
/*
在所有注册套件中运行所有测试,返回测试运行期间发生的第一个错误代码,输出类型由当前的运行模式控制,这个模式可以有CU_basic_set_mode来设置。
*/
  CU_ErrorCode CU_basic_run_tests(void)

/*
执行指定套件中的所有测试,同样返回测试中发生的第一个错误的代码
*/
  CU_ErrorCode CU_basic_run_suite(CU_pSuite pSuite)

/*
执行特定套件中的特定测试,同样返回测试中发生的第一个错误的代码
*/
  CU_ErrorCode CU_basic_run_test(CU_pSuite pSuite, CU_pTest pTest)

/*
设置基本模式的运行模式,控制输出的类型
  CU_BRM_NROMAL   失败和执行的总结会打印出来
  CU_BRM_SILENT   错误信息以外的输出都不会打印出来
  CU_BRM_VERBOSE  打印所有可能输出的细节
*/
  void CU_basic_set_mode(CU_BasicRunMode mode)

/*
返回当前的运行模式
*/
  CU_BasicRunMode CU_basic_get_mode(void)

/*
将所有的失败信息打印到stdout中,不依赖于运行模式!
*/
  void CU_basic_show_failures(CU_pFailureRecord pFailure)

####交互式控制台模式

/***************************** 
- 控制台模式
- 控制台模式是可交互的,被测试程序(客户端)只需要启动控制台会话,然后用户以交互方式来控制测试的进行。
- 操作包括选择和运行注册的套件和测试,查看测试结果
*****************************/
#include <CUnit/Console.h>
  void CU_console_run_tests(void)

####交互式Curse模式

/***************************** 
- 交互式Curses模式(与控制台模式很类似)
- 同样是可交互的,被测试程序(客户端)只需要启动Curses界面会话,然后用户以交互方式来控制测试的进行。
- 操作包括选择和运行注册的套件和测试,查看测试结果
*****************************/
#include <CUnit/CUCurses.h>
  void CU_curses_run_tests(void)

####获取测试结果

/***************************** 
- 客户端代码有时候需要直接访问测试的结果,包括测试执行中的各种计数以及一个保存测试失败的细节的链表!
- 每次启动新的测试或者是在初始化、清除出侧标时,测试结果都会被覆盖!
*****************************/
#include <CUnit/TestRun.h> //< (included automatically by <CUnit/CUnit.h>)

/*
以下若干接口可用来报告套件的数量、测试的数量、失败或者通过的断言数量。
*/
  unsigned int CU_get_number_of_suites_run(void)
  unsigned int CU_get_number_of_suites_failed(void)
  unsigned int CU_get_number_of_tests_run(void)
  unsigned int CU_get_number_of_tests_failed(void)
  unsigned int CU_get_number_of_asserts(void)
  unsigned int CU_get_number_of_successes(void)
  unsigned int CU_get_number_of_failures(void)

/*
一次获取所有的运行数据,所有上面的运行数据保存在同一个结构体里
*/
  typedef struct CU_RunSummary
  {
    unsigned int nSuitesRun;
    unsigned int nSuitesFailed;
    unsigned int nTestsRun;
    unsigned int nTestsFailed;
    unsigned int nAsserts;
    unsigned int nAssertsFailed;
    unsigned int nFailureRecords;
  } CU_RunSummary;
  typedef CU_Runsummary* CU_pRunSummary
  const CU_pRunSummary CU_get_run_summary(void)

/*
获取测试中所有失败的一个链表,每一个失败都包含一些定位信息和状态信息。
*/
  typedef struct CU_FailureRecord
  {
    unsigned int  uiLineNumber;
    char*         strFileName;
    char*         strCondition;
    CU_pTest      pTest;
    CU_pSuite     pSuite;

    struct CU_FailureRecord* pNext;
    struct CU_FailureRecord* pPrev;

  } CU_FailureRecord;
  typedef CU_FailureRecord*  CU_pFailureRecord
  const CU_pFailureRecord CU_get_failure_list(void)

/*
获取测试中保存失败的链表的长度(失败数量)
Notice: 这个数量可能大于测试失败的数量,因为像套件初始化失败等等错误也会记录在里面!
*/
  unsigned int CU_get_number_of_failure_records(void)

###异常处理

#include <CUnit/CUError.h>  //<  (included automatically by <CUnit/CUnit.h>)

typedef enum   CU_ErrorCode
/*****************************
- 大多数的CUnit函数都可以设置一个error_code来标志框架的异常状态。有些函数直接返回这个异常码,而另外的会设置这个code(返回其他信息)。以下两个函数用来获取当前框架的异常状态。
- 第一个返回异常值
- 第二个返回异常的描述
- 所有的异常值和描述不列出,可参考手册原文
*****************************/
CU_ErrorCode CU_get_error(void);
const char*  CU_get_error_msg(void);


typedef enum   CU_ErrorAction
/*****************************
- 遇到异常时,默认的行为是设置ERROR_CODE后继续执行,而有些测试情况下需要测试停止在框架错误处,或者测试应用程序直接退出。这个异常发生后的行为可由用户设置。
- 可设置为以下值
  CUEA_IGNORE(default)  程序继续运行
  CUEA_FAIL      程序运行停止
  CUEA_ABORT     程序退出
*****************************/
void           CU_set_error_action(CU_ErrorAction action);
CU_ErrorAction CU_get_error_action(void);

###官方实例解析
CUnit官方提供了一个简单的实例,下面这个解析对该实例中注释做了删减,加上了中文注释,如果想看原版,可直接访问以下链接:

http://cunit.sourceforge.net/example.html

#include <stdio.h>
#include <string.h>
#include "CUnit/Basic.h"

/* 新建FILE指针指向测试用的文件,暂时初始化为空,等待打开文件 */
static FILE* temp_file = NULL;

/*
- suite的初始化函数,根据suite初始化函数的要求,返回0则初始化成功,否则失败
- suite初始化的动作是打开 temp.txt 文件
*/
int init_suite1(void)
{
   if (NULL == (temp_file = fopen("temp.txt", "w+"))) {
      return -1;
   }
   else {
      return 0;
   }
}

/*
- suite的清除函数,与初始化函数相同的要求,返回0为成功,否则失败
- suite的清除则为关闭 temp.txt 文件
*/
int clean_suite1(void)
{
   if (0 != fclose(temp_file)) {
      return -1;
   }
   else {
      temp_file = NULL;
      return 0;
   }
}

/*
测试函数,测试fprintf返回的字节数是否正确
*/
void testFPRINTF(void)
{
   int i1 = 10;

   if (NULL != temp_file) {
      CU_ASSERT(0 == fprintf(temp_file, ""));
      CU_ASSERT(2 == fprintf(temp_file, "Q\n"));
      CU_ASSERT(7 == fprintf(temp_file, "i1 = %d", i1));
   }
}

/*
- 测试fread()
- 这个测试函数一定要在testFPRINTF()函数之后,否则测试就会失败
*/
void testFREAD(void)
{
   unsigned char buffer[20];
   
   if (NULL != temp_file) {
      rewind(temp_file);  //< 当前文件流的位置重新设置为文件开头
      CU_ASSERT(9 == fread(buffer, sizeof(unsigned char), 20, temp_file));
      CU_ASSERT(0 == strncmp(buffer, "Q\ni1 = 10", 9));
   }
}

/*
main 函数,执行测试的全过程
*/
int main()
{
   CU_pSuite pSuite = NULL;

   //< step1 测试注册表初始化
   if (CUE_SUCCESS != CU_initialize_registry())
      return CU_get_error();

   //< step2 给测试注册表添加一个套件(suite)
   //< 该函数返回添加的套件指针,若为空则创建失败,返回错误
   pSuite = CU_add_suite("Suite_1", init_suite1, clean_suite1);
   if (NULL == pSuite) {
      CU_cleanup_registry();
      return CU_get_error();
   }

   //< step3 给套件添加两个测试,且必须是先添加testFPRINTF,再添加testFREAD
   if ((NULL == CU_add_test(pSuite, "test of fprintf()", testFPRINTF)) ||
       (NULL == CU_add_test(pSuite, "test of fread()", testFREAD)))
   {
      CU_cleanup_registry();
      return CU_get_error();
   }

   //< step4 用基本模式执行所有的测试
   CU_basic_set_mode(CU_BRM_VERBOSE);  //< 设定了基本模式的输出模式(输出所有可能的细节)
   CU_basic_run_tests();
   
   //< step5 清除注册表(所有测试和套件)
   CU_cleanup_registry();
   
   return CU_get_error();
}

//< 测试结果如下
/*
 *       Suite: Suite_1
 *         Test: test of fprintf() ... passed
 *         Test: test of fread() ... passed
 *
 *       --Run Summary: Type      Total     Ran  Passed  Failed
 *                      suites        1       1     n/a       0
 *                      tests         2       2       2       0
 *                      asserts       5       5       5       0
 */
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值