目录
0 引言
在 C/C++ 编程中,单元测试(Unit Testing)是指对软件中最小可测试部分(通常是函数或方法)进行测试的过程。单元测试的目的是验证每个单元的功能是否按照预期正常工作。通过单元测试,开发者可以在开发过程中尽早发现并修复代码中的错误,从而提高代码质量和可维护性。
单元测试是 C/C++ 开发中非常重要的一部分,可以帮助开发者确保代码的正确性和稳定性。通过使用单元测试框架,开发者可以更轻松地编写和执行测试,从而提高代码质量。下面将会介绍一款适用于C语言的单元测试框架——CUnit。
1 CUnit简介
CUnit是一个以C语言为基础的无关平台的单元测试框架,其通常被编译成静态库或动态库,以提供用户单元测试代码的链接。其不仅提供一组丰富的断言用以测试,还提供了4种不同的接口用来运行测试和报告测试结果。
以下头文件对用户通常会用到的数据类型和函数做了声明:
Header File | Description |
---|---|
#include <CUnit/CUnit.h> | 提供断言宏,以及测试框架必要的声明 |
#include <CUnit/CUError.h> | 提供错误数据类型的定义和错误处理函数的声明(被CUnit.h文件自动包含) |
#include <CUnit/TestDB.h> | 提供数据类型的定义和测试套件、测试用例的注册功能接口(被CUnit.h文件自动包含) |
#include <CUnit/TestRun.h> | 提供数据类型的定义和运行测试、检索结果的功能接口(被CUnit.h文件自动包含) |
#include <CUnit/Automated.h> | 提供自动XML输出接口 |
#include <CUnit/Basic.h> | 提供非交互式的标准输出接口 |
#include <CUnit/Console.h> | 提供交互式的控制台输出接口 |
#include <CUnit/CUCurses.h> | 提供交互式的控制台输出接口(Unix平台) |
#include <CUnit/Win.h> | 提供Windows界面(尚未实施) |
接口函数的详细介绍与用法应参阅CUnit提供的公共头文件
2 测试框架结构
CUnit测试框架的核心为测试注册表,其为测试套件和测试用例提供了基本支持。CUnit的测试框架与传统的单元测试框架类似:
Test Registry | ------------------------------ | | Suite '1' . . . . Suite 'N' | | --------------- --------------- | | | | Test '11' ... Test '1M' Test 'N1' ... Test 'NM'
每个单独的测试用例(Test)被集成到对应的套件(Suite)中,这些套件又被注册到测试注册表(Test Registry)里。通过调用一个函数就可以执行测试注册表里的全部测试用例,当然也可以有选择性地执行部分测试用例。
3 通常用法
使用CUnit框架的典型步骤是:
- 编写待测函数
- 编写测试函数
- 初始化测试注册表 –
CU_initialize_registry()
- 将套件(Suite)添加到测试注册表 –
CU_add_suite()
- 将测试用例(Test)加到套件里 –
CU_add_test()
- 调用合适的接口函数执行测试 –
CU_console_run_tests()
- 清除测试注册表 –
CU_cleanup_registry()
4 测试用例
CUnit测试用例应该是一个C函数,其函数签名(语法格式)为:
void test_func(void)
5 断言
CUnit提供了一组用于测试逻辑条件的断言。这些断言的结果会被框架跟踪,当测试执行结束后可以对跟踪的结果进行查看和报告。
每个断言测试一个单一的逻辑,如果计算结果为CU_FALSE则失败。断言失败后,测试函数将继续执行,除非用户选择 xxx_FATAL 版本的断言。这种情况下,测试函数将被中止执行,并立即返回。FATAL版本的断言应谨慎使用,一旦 FATAL 版本断言失败,测试执行的主函数就没有机会执行自身的清理工作了。但是,普通测试套件的清理函数不受影响,两个版本的断言都会执行套件的清理函数。
还有一些特殊的断言,用于在不执行逻辑测试的情况下,向框架注册通过或失败。这对于控制测试流或其他不需要逻辑测试的情况都非常有用。
在被已注册的测试函数中调用的其他函数里,仍然可以自由地使用CUnit断言,这些断言结果都将被计算到调用它们的测试函数中。同样也可以使用FATAL版本的断言,如果断言失败将中止原始测试函数和整个调用链。
CUnit断言定义:
断言 | 描述 |
---|---|
CU_ASSERT(int expression) CU_ASSERT_FATAL(int expression) CU_TEST(int expression) CU_TEST_FATAL(int expression) | Assert that expression is TRUE (non-zero) |
CU_ASSERT_TRUE(value) CU_ASSERT_TRUE_FATAL(value) | Assert that value is TRUE (non-zero) |
CU_ASSERT_FALSE(value) CU_ASSERT_FALSE_FATAL(value) | Assert that value is FALSE (zero) |
CU_ASSERT_EQUAL(actual, expected) CU_ASSERT_EQUAL_FATAL(actual, expected) | Assert that actual = = expected |
CU_ASSERT_NOT_EQUAL(actual, expected) CU_ASSERT_NOT_EQUAL_FATAL(actual, expected) | Assert that actual != expected |
CU_ASSERT_PTR_EQUAL(actual, expected) CU_ASSERT_PTR_EQUAL_FATAL(actual, expected) | Assert that pointers actual = = expected |
CU_ASSERT_PTR_NOT_EQUAL(actual, expected) CU_ASSERT_PTR_NOT_EQUAL_FATAL(actual, expected) | Assert that pointers actual != expected |
CU_ASSERT_PTR_NULL(value) CU_ASSERT_PTR_NULL_FATAL(value) | Assert that pointer value == NULL |
CU_ASSERT_PTR_NOT_NULL(value) CU_ASSERT_PTR_NOT_NULL_FATAL(value) | Assert that pointer value != NULL |
CU_ASSERT_STRING_EQUAL(actual, expected) CU_ASSERT_STRING_EQUAL_FATAL(actual, expected) | Assert that strings actual and expected are equivalent |
CU_ASSERT_STRING_NOT_EQUAL(actual, expected) CU_ASSERT_STRING_NOT_EQUAL_FATAL(actual, expected) | Assert that strings actual and expected differ |
CU_ASSERT_NSTRING_EQUAL(actual, expected, count) CU_ASSERT_NSTRING_EQUAL_FATAL(actual, expected, count) | Assert that 1st count chars of actual and expected are the same |
CU_ASSERT_NSTRING_NOT_EQUAL(actual, expected, count) CU_ASSERT_NSTRING_NOT_EQUAL_FATAL(actual, expected, count) | Assert that 1st count chars of actual and expected differ |
CU_ASSERT_DOUBLE_EQUAL(actual, expected, granularity) CU_ASSERT_DOUBLE_EQUAL_FATAL(actual, expected, granularity) | Assert that (actual – expected) <= (granularity) Math library must be linked in for this assertion. |
CU_ASSERT_DOUBLE_NOT_EQUAL(actual, expected, granularity) CU_ASSERT_DOUBLE_NOT_EQUAL_FATAL(actual, expected, granularity) | Assert that (actual – expected) > (granularity) Math library must be linked in for this assertion. |
CU_PASS(message) | Register a passing assertion with the specified message. No logical test is performed. |
CU_FAIL(message) CU_FAIL_FATAL(message) | Register a failed assertion with the specified message. No logical test is performed. |
6 测试注册表
CUnit中测试注册表可以看作是管理测试套件和测试用例的仓库。CUnit维护这个测试注册表,当用户添加套件或测试用例时会更新该注册表。注册表中的套件就是用户选择运行所有测试用例时运行的测试套件。
CUnit测试注册表的数据结构CU_TestRegistry如下所示,其包括存储在注册表中的套件和测试用例的总数,以及一个指向已注册的套件链表的头指针。
typedef struct CU_TestRegistry { unsigned int uiNumberOfSuites; /**< Number of registered suites in the registry. */ unsigned int uiNumberOfTests; /**< Total number of registered tests in the registry. */ CU_pSuite pSuite; /**< Pointer to the 1st suite in the test registry. */ } CU_TestRegistry; typedef CU_TestRegistry* CU_pTestRegistry; /**< Pointer to a CUnit test registry. */
用户通常只需要在使用前初始化注册表,使用后进行清理。但是,在必要时也可以使用其他函数操作注册表。
CU_initialize_registry
/** * @brief CUnit测试注册表初始化函数 * @return CUE_SUCCESS:初始化成功;CUE_NOMEMORY:初始化失败 * @note CUnit测试注册表在使用之前必须先初始化。用户在调用任何其他CUnit函数之前应先调用CU_initialize_registry()。不这样做可能会导致系统崩溃。 * 如果重复调用此函数,则会将已存在的注册表先清除(即销毁),然后再创建一个新的注册表。在执行测试期间,不应该调用此函数。 */ CU_ErrorCode CU_initialize_registry(void)
CU_registry_initialized
/** * @brief 用于检查注册表是否已初始化 * @return CU_TRUE:注册表初始化成功;CU_FALSE:注册表初始化失败 */ CU_BOOL CU_registry_initialized(void)
CU_cleanup_registry
/** * @brief 清除注册表 * @return * @note 当测试完成后,用户应调用此函数来清理和释放框架使用的内存。这应该是最后一个调用的CUnit函数。 * 除非后面使用CU_initialize_registry()或CU_set_registry()恢复测试注册表 * 未调用 CU_cleanup_registry() 将导致内存泄漏。它可以被多次调用,且不会报错。注意,这个函数会销毁注册表中的所有套件(以及相关联的测试用例)。 * 清理注册表之后,指向已注册的套件和测试用例的指针不应该再被引用。在执行测试期间,不应该调用此函数。 * 调用 CU_cleanup_registry() 只会影响CUnit框架维护的内部CU_TestRegistry。那些隶属于用户的的测试注册表,用户有责任自己销毁。 * 可以显式地通过调用CU_destroy_existing_registry()来完成。 * 也可以隐式地通过先调用CU_set_registry()激活注册表,再调用CU_cleanup_registry()来完成。 */ void CU_cleanup_registry(void)
CU_get_registry
/** * @brief 获取当前测试注册表 * @return 一个指向活动测试注册表的指针 * @note 框架维护注册表的所有权,因此当调用CU_cleanup_registry()或CU_initialize_registry()函数之后,该函数返回的指针将会失效。 */ CU_pTestRegistry CU_get_registry(void)
CU_set_registry
/** * @brief 指定某注册表替换当前活动注册表 * @param pTestRegistry 指向用于替换的注册表指针 * @return 指向被替换的注册表的指针 * @note 调用者有责任销毁旧的注册表,这可以通过为返回的指针调用CU_destroy_existing_registry()来显式地完成。 * 也可以先使用CU_set_registry()激活注册表,再调用CU_cleanup_registry()隐式地销毁注册表。 * 应注意不要显式地销毁被激活的注册表,这会导致同一个内存块被多次释放,很可能会引起崩溃。 */ CU_pTestRegistry CU_set_registry(CU_pTestRegistry pTestRegistry)
CU_create_new_registry
/** * @brief 创建一个新的注册表并返回一个指向它的指针 * @return 一个指向新的注册表的指针 * @note 新的注册表不包含任何的套件或测试用例。调用者有责任通过前面描述的机制之一销毁新的注册表。 */ CU_pTestRegistry CU_create_new_registry(void)
CU_destroy_existing_registry
/** * @brief 销毁并释放指定测试注册表的所有内存,包括所有已注册的套件和测试用例 * @param ppRegistry 指向欲被销毁的注册表的指针的地址 * @return * @note 对于被设置为激活状态的注册表(例如,CU_get_registry()返回的CU_pTestRegistry指针),不应调用此函数。 * 这会导致在调用CU_cleanup_registry()时多次释放相同的内存。 * ppRegistry可能不为NULL,但指针内容可以为空。在这种情况下,该函数不起作用。请注意,本函数返回时会将 *ppRegistry 设置为NULL。 */ void CU_destroy_existing_registry(CU_pTestRegistry* ppRegistry)
7 测试用例与测试套件
为了让CUnit运行一个测试用例,必须将其添加到已在测试注册表注册的测试集(套件)中。
7.1 测试套件
测试套件又称测试用例集,它具有制定的名称,初始化函数(或称为构造函数)和清理函数(或称为析构函数)。
一个新的测试套件应注册在(隶属于)测试注册表,因此在添加套件之前,必须先对注册表进行初始化。
测试注册表中的每个套件的名称应该是唯一的,这有助于按名称查找套件。测试套件的初始化和清理函数是可选的,并作为指针传递给这些函数,即运行套件中的测试用例之前和之后要调用的函数。这使得套件可以 setUp 和 tearDown 临时 fixtures(固定环境),以支持运行测试用例。这些函数没有参数,如果他们执行成功应返回零(否则返回非零值)。如果套件不需要这些函数中的一个或两个,可以传递NULL给CU_add_suite()。
Test Fixture 是指一个测试运行所需的固定环境,准确的定义:The test fixture is everything we need to have in place to exercise the SUT. 在进行测试时,我们通常需要把环境设置成已知状态(如创建对象、获取资源等)来创建测试,每次测试开始时都处于一个固定的初始状态(通过setUp实现);测试结果后需要将测试状态还原(通过tearDown实现)。setUp用于测试用例执行前的初始化工作,tearDown与之对应,用于测试用例执行后的善后工作。
CU_add_suite
/** * @brief 创建一个新的测试用例集(套件),并将其注册到测试注册表 * @param strName Name for the new test suite (non-NULL). * @param pInit Initialization function to call before running suite. * @param pClean Cleanup function to call after running suite. * @return 一个指向新套件的指针,利用该指针可以向套件添加测试用例,若创建失败,则返回NULL * @note 在执行测试期间,不应该调用此函数 * @note 在调用此函数之前,测试注册表必须初始化 */ CU_pSuite CU_add_suite(const char* strName, CU_InitializeFunc pInit, CU_CleanupFunc pClean)
7.2 测试用例
为了使CUnit可以运行测试用例,需要将测试函数添加到某个测试套件中,该测试套件必须是通过调用CU_add_suite()创建好的。若添加成功,则可以将该测试函数视为一个测试用例。
同一个套件中的每个测试用例的名称应该是唯一的,这有助于按名称查找测试用例。测试函数不能为空,必须指向一个运行测试时被调用的函数。测试函数既没有参数,也没有返回值。
CU_add_test
/** * @brief 创建一个具有指定名称和测试功能的新测试用例,并将其注册到指定的套件中 * @param pSuite Test suite to which to add new test (non-NULL). * @param strName Name for the new test case (non-NULL). * @param pTest Function to call when running the test (non-NULL). * @return 一个指向新测试用例的指针,若创建失败,则返回NULL * @note 在执行测试期间,不应该调用此函数 * @note 指定的套件必须是通过CU_add_suite()创建的 */ CU_pTest CU_add_test(CU_pSuite pSuite, const char* strName, CU_TestFunc pTestFunc)
CU_ADD_TEST
/** * @brief 根据测试函数名自动生成唯一的测试用例名称,并将其添加到指定的套件中。用户应检查返回值以验证是否成功。 */ #define CU_ADD_TEST(suite, test) (CU_add_test(suite, #test, (CU_TestFunc)test))
7.3 管理测试用例
对于有着许多测试用例和套件的大型测试结构,管理测试用例和套件之间的关系以及注册工作是繁琐且容易出错的。CUnit提供了一个特殊的注册系统来帮助管理套件和测试用例。它的主要优点是可以统一的对套件及其关联的测试用例进行注册,并最大程度地减少用户需要编写的错误检查代码量。
typedef struct CU_TestInfo { const char *pName; /**< Test name. */ CU_TestFunc pTestFunc; /**< Test function. */ } CU_TestInfo;
typedef struct CU_SuiteInfo { const char *pName; /**< Suite name. */ CU_InitializeFunc pInitFunc; /**< Suite initialization function. */ CU_CleanupFunc pCleanupFunc; /**< Suite cleanup function */ CU_SetUpFunc pSetUpFunc; /**< Pointer to the test SetUp function. */ CU_TearDownFunc pTearDownFunc; /**< Pointer to the test TearDown function. */ CU_TestInfo *pTests; /**< Test case array - must be NULL terminated. */ } CU_SuiteInfo;
/* Test Func */ void test_func1() { CU_ASSERT_EQUAL(max(1, 2), 2); } void test_func2() { CU_ASSERT_EQUAL(max(2, 1), 2); } void test_func3() { CU_ASSERT_EQUAL(max(2, 2), 2); } /* 写好测试函数之后,可以通过构建CU_TestInfo类型的数组管理测试用例(Test) */ /* 每个数组元素包含一个Test的唯一名称和Test。该数组的结束元素必须为NULL,宏CU_TEST_INFO_NULL很方便地定义了该元素 */ /* 每个CU_TestInfo类型的数组都可以视为一个测试集(Suite),其中包含了一些不同的Test */ CU_TestInfo SuiteName[] = { {"TestName1", test_func1}, {"TestName2", test_func2}, {"TestName3", test_func3}, CU_TEST_INFO_NULL }; /* 同样,可以通过构建CU_SuiteInfo类型的数组管理测试套件(Suite) */ /* 每个数组元素包含一个Suite的唯一名称、Suite初始化函数、Suite清理函数和CU_TestInfo类型的测试集Suite数组 */ /* 若给定的Suite不需要初始化或清除函数,可以设置为NULL。该数组必须以全NULL的元素作为结束,可以使用宏CU_SUITE_INFO_NULL */ CU_SuiteInfo suites[] = { { "SuiteName1", suite1_init-func, suite1_cleanup_func, SuiteName1}, { "SuiteName2", suite2_init-func, suite2_cleanup_func, SuiteName1}, CU_SUITE_INFO_NULL }; /* 在构建好CU_SuiteInfo类型的数组后,其里面的每个Suite元素,都可以通过调用一个函数注册到测试注册表中 */ CU_ErrorCode CU_register_suites(CU_SuiteInfo suite_info[])
CU_register_suites
/** * @brief 将单个CU_SuiteInfo类型的数组中包含的所有Suite全部注册到测试注册表中 * @param suite_info NULL-terminated array of CU_SuiteInfo items to register. * @return A CU_ErrorCode indicating the error status. */ CU_ErrorCode CU_register_suites(CU_SuiteInfo suite_info[])
CU_register_nsuites
/** * @brief 将suite_count个CU_SuiteInfo类型的数组中包含的所有Suite全部注册到测试注册表中 * @param suite_count 要注册到测试注册表中的CU_SuiteInfo类型的数组个数 * @param ... suite_count number of CU_SuiteInfo* arguments. NULLs are ignored. * @return A CU_ErrorCode indicating the error status. */ CU_ErrorCode CU_register_nsuites(int suite_count, ...)
7.4 激活/禁用Suite和Test
所有套件和测试用例在创建时默认是激活的。当前的激活状态分别保存在 pSuite->fActive 和 pTest->fActive 数据结构成员中。如果为激活状态,该标志为CU_TRUE,否则为CU_FALSE。除非套件或测试用例处于激活状态,否则其在执行测试期间将不会被执行。
为了能够动态地选择要执行的suite和test,可以通过CU_set_suite_active和CU_set_test_active来控制相应的suite和test状态。
CU_set_suite_active
/** * @brief 激活/禁用指定的suite * @param pSuite suite * @param fNewActive suite激活状态 * @return CUE_SUCCESS:执行成功;CUE_NOSUITE:相应的suite为空 */ CU_ErrorCode CU_set_suite_active(CU_pSuite pSuite, CU_BOOL fNewActive)
CU_set_test_active
/** * @brief 激活/禁用指定的test * @param pTest test * @param fNewActive test激活状态 * @return CUE_SUCCESS:执行成功;CUE_NOSUITE:相应的test为空 */ CU_ErrorCode CU_set_test_active(CU_pTest pTest, CU_BOOL fNewActive)
7.5 修改Suite和Test的属性
通常,套件和测试用例的属性是在创建时就设置好的。在某些情况下,用户希望可以通过操纵这些属性动态地修改测试结构,为此CUnit提供了以下函数。这些函数如果执行成功都会返回CUE_SUCCESS,失败则返回指示的错误代码。
CU_set_suite_name
/** * @brief 用于更改已注册suite的名称 * @param pSuite 指向要更改名字的suite * @param strNewName suite的新名称 * @return */ CU_ErrorCode CU_set_suite_name(CU_pSuite pSuite, const char *strNewName)
CU_set_test_name
/** * @brief 用于更改已注册test的名称 * @param pTest 指向要更改名字的test * @param strNewName test的新名称 * @return */ CU_ErrorCode CU_set_test_name(CU_pTest pTest, const char *strNewName)
CU_set_suite_initfunc
/** * @brief 用于更改已注册suite的初始化函数 * @param pSuite 指向要更改初始化函数的suite * @param pNewInit 新的初始化函数 * @return */ CU_ErrorCode CU_set_suite_initfunc(CU_pSuite pSuite, CU_InitializeFunc pNewInit)
CU_set_suite_cleanupfunc
/** * @brief 用于更改已注册suite的清理函数 * @param pSuite 指向要更改清理函数的suite * @param pNewInit 新的清理函数 * @return */ CU_ErrorCode CU_set_suite_cleanupfunc(CU_pSuite pSuite, CU_CleanupFunc pNewClean)
CU_set_test_func
/** * @brief 用于更改已注册test的测试函数 * @param pTest 指向要更改测试函数的test * @param pNewFunc 新的测试函数 * @return */ CU_ErrorCode CU_set_test_func(CU_pTest pTest, CU_TestFunc pNewFunc)
7.6 查找单个Suite和Test
在大多数情况下,用户将通过 CU_add_suite() 和 CU_add_test() 返回的指针来引用已注册的套件和测试用例。有时,用户可能需要重新获取套件和测试用例的引用。如果用户拥有关于实体的一些信息(注册名称或顺序),下面的函数将协助用户完成此操作。
CU_get_suite
/** * @brief 根据名称查找已注册的suite * @param strName suite名称 * @return CU_pSuite 指向查找到的suite */ CU_pSuite CU_get_suite(const char* strName)
CU_get_suite_at_pos
/** * @brief 根据位置查找已注册的suite * @param pos suite位置 * @return CU_pSuite 指向查找到的suite * @note 查找位置范围:[1,CU_get_registry()->uiNumberOfSuites] */ CU_pSuite CU_get_suite_at_pos(unsigned int pos)
CU_get_suite_pos
/** * @brief 根据已注册的suite查找suite位置 * @param pSuite 指向要查找位置的suite * @return 查找到的suite位置 */ unsigned int CU_get_suite_pos(CU_pSuite pSuite)
CU_get_suite_pos_by_name
/** * @brief 根据已注册的suite名称查找suite位置 * @param strName 要查找位置的suite名称 * @return 查找到的suite位置 */ unsigned int CU_get_suite_pos_by_name(const char* strName)
CU_get_test
/** * @brief 根据名称查找已注册的test * @param strName test名称 * @return CU_pSuite 指向查找到的test */ CU_pTest CU_get_test(CU_pSuite pSuite, const char *strName)
CU_get_test_at_pos
/** * @brief 根据位置查找已注册的test * @param pos test位置 * @return CU_pTest 指向查找到的test * @note 查找位置范围:[1,pSuite->uiNumberOfSuites] */ CU_pTest CU_get_test_at_pos(CU_pSuite pSuite, unsigned int pos)
CU_get_test_pos
/** * @brief 根据已注册的test查找test位置 * @param pSuite 指向包含该test的suite * @param pTest 指向要查找位置的test * @return 查找到的test位置 */ unsigned int CU_get_test_pos(CU_pSuite pSuite, CU_pTest pTest)
CU_get_test_pos_by_name
/** * @brief 根据已注册的test名称查找test位置 * @param pSuite 指向包含该test的suite * @param strName 要查找位置的test名称 * @return 查找到的test位置 */ unsigned int CU_get_test_pos_by_name(CU_pSuite pSuite, const char *strName)
8 执行测试
CUnit只支持执行在注册表中已注册的测试suite,可以选择执行全部的测试suite,也可以选择执行某些特定的测试suite。在每次测试任务执行期间,CUnit框架都会跟踪并记录参与本次测试任务的suite和test的执行情况。注意,每次执行新的测试任务之前都会清除上一次的测试结果。
用户可以自由选择将单个suite或test激活还是禁用,但是,若禁用一个suite或test之后,又明确请求执行它将会产生框架错误。
CUnit为用户执行测试任务提供了4个接口,这些接口负责处理与框架交互的细节,并且输出详细的测试信息和测试结果。
Interface | Platform | Description |
---|---|---|
Automated | all | 非交互式,输出到xml文件 |
Basic | all | 非交互式,输出到stdout |
Console | all | 交互式,控制台模式 |
Curses | Linux/Unix | 交互式,curses模式 |
如果这些接口仍然无法满足用户的需求,那么还可以使用<CUnit/TestRun.h>中定义的原始框架API函数。有关信息示例,请参见原始框架API的源代码。
8.1 Automated
Automated即自动模式,其接口是非交互式的。用户以该模式执行测试后,已注册的suite和test列表和测试结果会自动输出到XML文件。该模式的API由以下函数组成:
CU_automated_run_tests
/** * @brief 运行所有已注册的且为激活状态的suite * @param * @return 测试结果输出到一个文件名为ROOT-Results.xml的文件中 * @note 可以使用CU_set_output_filename()设置文件名ROOT * 结果文件支持两种类型,文件类型定义文件(CUnit Run.dtd)和XSL样式表(CUnit Run.xsl) */ void CU_automated_run_tests(void)
CU_list_tests_to_file
/** * @brief 列举出已注册suite和test到文件中 * @param * @return 测试suite和test列表输出到一个文件名为ROOT-Listing.xml的文件中 * @note 可以使用CU_set_output_filename()设置文件名ROOT * 结果文件支持两种类型,文件类型定义文件(CUnit Run.dtd)和XSL样式表(CUnit Run.xsl) * 列表文件不是由CU_automated_run_tests()自动生成的,当用户需要列表信息时,用户必须调用本接口作出明确请求 */ CU_ErrorCode CU_list_tests_to_file(void)
CU_set_output_filename
/** * @brief 设置结果和列表文件的输出文件名 * @param szFilenameRoot 结果和列表文件的输出文件名前缀 * @return 将szFilenameRoot分别添加到-Results.xml和-Listing.xml的前缀以构成文件名 */ void CU_set_output_filename(const char* szFilenameRoot)
8.2 Basic
Basic即基本模式,其接口是非交互式的。用户以该模式执行测试后,测试结果输出到标准输出stdout。此接口支持运行单独的套件或测试用例,并允许用户代码控制每次运行期间显示的输出类型。该模式的API由以下函数组成:
CU_basic_run_tests
/** * @brief 运行所有已注册的且为激活状态的suite * @param * @return 返回测试运行期间发生的第一个错误码 * @note 如果遇到非激活的suite,不会将其视为错误,而是跳过 * 输出类型由当前的运行模式控制,该模式可以使用CU_basic_set_mode()进行设置 */ CU_ErrorCode CU_basic_run_tests(void)
CU_basic_run_suite
/** * @brief 运行指定单一suite中的所有test * @param * @return 返回测试运行期间发生的第一个错误码 * @note 如果遇到非激活的test,不会将其视为错误,而是跳过 * 输出类型由当前的运行模式控制,该模式可以使用CU_basic_set_mode()进行设置 */ CU_ErrorCode CU_basic_run_suite(CU_pSuite pSuite)
CU_basic_run_test
/** * @brief 运行指定单一suite中的一个test * @param * @return 返回测试运行期间发生的第一个错误码 * @note 输出类型由当前的运行模式控制,该模式可以使用CU_basic_set_mode()进行设置 */ CU_ErrorCode CU_basic_run_test(CU_pSuite pSuite, CU_pTest pTest)
CU_basic_set_mode
/** * @brief 设置运行模式,该模式在测试运行期间控制输出 * @param mode 测试任务运行模式 * @return * @note CU_BRM_NORMAL 打印故障和运行结果 * CU_BRM_SILENT 除错误消息外不打印输出 * CU_BRM_VERBOSE 最大程度地输出运行的详细信息 */ void CU_basic_set_mode(CU_BasicRunMode mode)
CU_basic_get_mode
/** * @brief 获取当前的运行模式 * @param * @return 返回当前的运行模式 */ CU_BasicRunMode CU_basic_get_mode(void)
CU_basic_show_failures
/** * @brief 将所有失败汇总打印到stdout * @param pFailure List of CU_pFailureRecord's to output * @return * @note 不依赖于运行模式 */ void CU_basic_show_failures(CU_pFailureRecord pFailure)
8.3 Console
Console即控制台模式,其接口是交互式的。用户利用此接口可以启动控制台会话,以交互方式控制测试运行。这些操作包括选择和运行suite/test,以及查看测试结果。该模式的API由以下函数组成:
CU_console_run_tests
/** * @brief 运行所有已注册的且为激活状态的suite * @param * @return */ void CU_console_run_tests(void)
8.4 Curses
Curses是在Linux/Unix平台下的控制台模式,其接口是交互式的。使用此接口需要将ncurses库链接到应用程序中。用户利用此接口可以启动控制台会话,以交互方式控制测试运行。这些操作包括选择和运行suite/test,以及查看测试结果。该模式的API由以下函数组成:
CU_curses_run_tests
/** * @brief 运行所有已注册的且为激活状态的suite * @param * @return */ void CU_curses_run_tests(void)
9 控制执行测试行为
以下API允许用户在测试运行期间修改框架的行为:
CU_set_fail_on_inactive
/** * @brief 设置当前框架对禁用状态下的suite和test的测试行为 * @param new_inactive = CU_TRUE,将禁用状态下的suite和test报告为失败 = CU_FALSE,忽略禁用状态下的suite和test * @return * @note 框架默认将非激活的套件和测试用例报告为失败,以便用户知道测试结构已有部分被停用 */ void CU_set_fail_on_inactive(CU_BOOL new_inactive)
CU_get_fail_on_inactive
/** * @brief 获取当前框架对禁用状态下的suite和test的测试行为 * @param * @return CU_FALSE:框架忽略禁用状态下的suite和test CU_TRUE: 框架视禁用状态下的suite和test为失败 */ CU_BOOL CU_get_fail_on_inactive(void)
10 获取测试结果
执行测试的四种模式都会自动呈现测试结果,但有时用户代码需要直接访问结果。这些结果包括各种运行计数,以及详细的故障记录链接列表信息。访问测试结果的API如下:
CU_get_number_of_suites_run
/** * @brief Retrieves the number of suites completed during the previous run (reset each run) */ unsigned int CU_get_number_of_suites_run(void)
CU_get_number_of_suites_failed
/** * @brief Retrieves the number of suites which failed to initialize during the previous run (reset each run) */ unsigned int CU_get_number_of_suites_failed(void)
CU_get_number_of_tests_run
/** * @brief Retrieves the number of tests completed during the previous run (reset each run) */ CU_get_number_of_tests_run
CU_get_number_of_tests_failed
/** * @brief Retrieves the number of tests containing failed assertions during the previous run (reset each run) */ unsigned int CU_get_number_of_tests_failed(void)
CU_get_number_of_asserts
/** * @brief Retrieves the number of assertions processed during the last run (reset each run) */ unsigned int CU_get_number_of_asserts(void)
CU_get_number_of_successes
/** * @brief Retrieves the number of successful assertions during the last run (reset each run) */ unsigned int CU_get_number_of_successes(void)
CU_get_number_of_failures
/** * @brief Retrieves the number of failed assertions during the last run (reset each run) */ unsigned int CU_get_number_of_failures(void)
CU_get_run_summary
/** * @brief 一次性获取所有测试结果 * @return 一个指向包含计数结果值的存储结构的指针 * @note 返回的指针指向的结构体变量为框架所拥有,因此用户不应该释放或以其他方式更改它。注意,一旦启动另一个测试运行,指针可能失效。 */ const CU_pRunSummary CU_get_run_summary(void)
CU_get_failure_list
/** * @brief 一次性获取测试运行期间发生的所有失败 * @return 一个指向包含失败记录的存储结构的指针,每个失败记录都包含有关失败位置和性质的信息 * @note 返回的指针指向的结构体变量为框架所拥有,因此用户不应该释放或以其他方式更改它。注意,一旦启动另一个测试运行,指针可能失效。 */ const CU_pFailureRecord CU_get_failure_list(void)
CU_get_number_of_failure_records
/** * @brief 获取由CU_get_failure_list()返回的失败链表中CU_FailureRecord结点的个数 * @note 这个数值可能比失败的断言数量多,因为套件的初始化和清除的失败也计算在内 */ unsigned int CU_get_number_of_failure_records(void)
11 框架错误
11.1 获取错误
CUnit大多数函数都会设置错误代码来表示框架的错误状态。有些函数直接返回错误代码,而有些函数只是设置错误代码,并返回一些其他值。以下两个API用于检查框架错误状态:
CU_get_error
/** * @brief 返回框架的错误状态代码 * @note 错误代码是在<CUnit/CUError.h>文件中定义的CU_ErrorCode枚举型 */ CU_ErrorCode CU_get_error(void)
CU_get_error_msg
/** * @brief 返回框架的错误状态代码描述信息 * @note 错误代码是在<CUnit/CUError.h>文件中定义的CU_ErrorCode枚举类型 */ const char* CU_get_error_msg(void)
11.2 处理错误
CUnit遇到一个错误发生时的默认行为是设置错误代码并继续执行。在这种情况下,失败的断言不被认为是框架错误。其他错误条件还包括套件初始化或者清理失败,非激活套件或测试用例被显示地执行等等。有时,用户可能更希望执行测试时停在框架发生错误的地方,甚至让测试程序退出。这种行为可以由用户设置,提供以下接口API:
CU_set_error_action
/** * @brief 设置框架对发生错误时的处理行为 * @param action CUEA_IGNORE 运行时发生错误,继续运行(默认) CUEA_FAIL 运行时发生错误,停止 CUEA_ABORT 运行时发生错误,退出应用程序 */ void CU_set_error_action(CU_ErrorAction action)
CU_get_error_action
/** * @brief 获取框架对发生错误时的处理行为 * @return CUEA_IGNORE 运行时发生错误,继续运行(默认) CUEA_FAIL 运行时发生错误,停止 CUEA_ABORT 运行时发生错误,退出应用程序 */ CU_ErrorAction CU_get_error_action(void)