使用CUNIT测试

一:概述

CUnit是一个c语言的单元测试框架,它是以静态链接库的形式,连接到用户代码中的,主要的功能就是提供了语义丰富的断言和多种测试结果输出接口,可以方便地生成测试报告。

但是需要注意的地方是,由于Cunit和我们的代码是在同一个项目中,所以,需要注意将测试代码和程序代码进行区分管理,避免直接在程序代码中添加测试代码;为了达到这个目的,我们经常需要提供单独的头文件,在这个头文件中,可以将原有接口函数罗列进来,还可以将需要测试的内部使用的函数列入,这样,在测试用的.c文件中,就可以直接引用该头文件进行编译,连接,测试。

一个Registry可以认为是一个需要测试的独立的功能集合单元,我们可以使该单元处于active状态,以便进行运行测试,也可以使之处于inactive状态,这样,在一个运行中,我们可以指定要运行的Registry。

一个Suite可以若干运行条件类似的测试的集合,他需要注册到某一个Registry才可以被运行到。划分Suite的标准是多样的,例如,某些test运行之前需要进行特定的初始化动作,那么我们可以把凡是需要该类初始化动作的test放入到一个Suite中,因为以Suite为单位,可以有自己的初期化函数和清理函数。

一个test就可以认为是一个单元测试的函数了,由于Cunit是一个黑盒测试工具,也就是说,他的主要目的是根据输入参数和返回结果来从外部观察函数执行的是否正确,所以,通常的做法就是我们提供多种输入,然后使用Cunit提供的断言,来判断返回值,out形参数,和函数可能影响的全局变量的变化是否符合我们的设计。

Cunit提供的多种编程接口,通常是针对不同类型的程序需求。

Automated

Output
to xml file

Non-interactive

Basic

Flexible
programming interface

Non-interactive

Console

Console
interface (ansi C)

Interactive

Curses

Graphical
interface (Unix)

Interactive

如上边所示,后两种主要是interactive的接口,就是我们可以交互式地指定参数,然后观察结果。在我们具体的环境下,我们通常使用上面两种接口,第一种是将结果输出到XML文档中,便于我们生成报告。第二种仅仅是每一次运行结束之后,在standard
output中显示,不能保留测试结果数据。

我们可以将前两种输出结合起来,自己测试的时候,使用Basic模式,生成报告的时候,使用Automated模式。

二:详细介绍

1:测试函数的书写

下面介绍一下典型的测试程序的书写流程:

) 首先针对被测试的函数书写测试函数。

) 初始化一个Registry。

) 将特定的Suite加入到这个Registry中。可以为Suite指定初始化函数和清理函数。

) 将测试函数加入到一个Suite中,这样,如果该测试函数的运行需要一些初始化条件,那么可以可以将代码加入到Suite的初始化函数中,当然不要忘记最后还要做对应的清理操作。

) 使用相应的接口将测试结果输出。

) 最后,清理Registry。

如下

这个例子中,有两个Suite,同时自己定义了一个函数 来将test加入到Suite中,这样的目的是避免main函数太长。另外这个函数中同时使用了Automated接口和Basic接口。我们不必同时使用,可以根据需要的形式进行选择。需要注意的是,使用Automated接口是,需要调用CU_list_tests_to_file()函数。

2CUnit提供的断言

CUnit提供了大量的预定义的断言,针对几乎所有的C的标准类型,我们可以针对具体的需要检查的参数的类型进行选择。

提供的断言有:

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 andexpected 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 andexpected 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.

可以看到,每一种类型的断言都有FATAL和非FATAL两个函数,他们的区别是,非FATAL断言失败的时候,程序继续运行,而FATAL类型的断言失败之后,程序马上中止。通常我们使用非FATAL就可以了。

注意在我们的程序中,需要对返回值,out参数进行判断,对于是否是预想结果的判断的时候,一定要使用断言,而不要使用printf等等函数,一方面printf缺乏灵活性,另一方面,只有使用断言,结果报告中才会有对应的输出项。

CU_PASSCU_FAIL这两个断言比较特殊,它们仅仅表示测试程序运行到了这个地方。比如,在某些测试函数中,被测试的函数没有任何返回值等,我们为了证明这个函数已经被运行到了,我们使用以上两个函数。它们仅仅打印出一条消息,代表执行过。还有就是,它们也被输出。

给出了一个被测函数和一个测试函数,注意断言的使用:

测试用例是否完备,需要测试人员自己保证。

3:注册所要进行的测试

。CU_add_suite函数的参数。

显示如何将tests加入Suite,以及测试函数本身

其中,testWRITE_GROUP是一个函数名。函数如下:

其中,write_group就是被测函数。

4:运行测试和测试报告的生成

完成了以上步骤之后,剩下的就是生成测试报告了:以下是Basic模式下的典型的输出:

表中显示了一共有多少个Suite,多少个tests,还有一共执行了多少个断言.(当然,以上这么多断言是因为有循环。)

如果有失败的断言,那么会是如下的样子,在程序中,我们将write_group进行调整,让断言失败。

以上可以看出,报告会指出断言失败的tests数目,还可以指出,断言失败的位置和函数。

关于Automated模式的,如下所示:表一是测试结果的报告,显示该XML文档,需要相关的XSL文档和DTD文档。test_report_multiuser-Results

Running
Suite Suite(need not init multiuser data)

Running
test test of get_key() ...

Passed

Running
test test of original crypt and decrypt ...

Passed

Running
test test of read_group() ...

Passed

Running
test test of crypt and decrypt ...

Passed

Running
test test of mu_load_multiuser_files() ...

Passed

Running
test test of test parameter check functions ...

Passed

Running
test test of free_list() ...

Passed

Running
Suite Mysuite(need load multiuser data

Running
test test of get_admin_info() ...

Passed

Running
test test of mu_get_all_user_info() ...

Passed

Running
test test of find_user() ...

Passed

Running
test test of write_group() ...

Passed

Running
test test of mu_login() ...

Passed

Running
test test of mu_logout() ...

Passed

Running
test test of mu_islogin() ...

Passed

Running
test test of mu_user_authentication() ...

Passed

Running
test test of mu_modify_password() ...

Passed

Running
test test of mu_modify_settings() ...

Passed

Running
test test of mu_get_user_settings() ...

Passed

Running
test test of mu_register() ...

Passed

Running
test test of mu_pop_is_busy() ...

Passed

Running
test test of add_to_list and remove_from_list ...

Passed

Cumulative
Summary for Run

Type

Total

Run

Succeeded

Failed

Suites

-
NA -

Test
Cases

Assertions

File
Generated By CUnit at Mon May 22 11:35:57 2006

表二是关于测试用例的报告:test_report_multiuser-Listing

Total
Number of Suites

Total
Number of Test Cases

Suite

Suite(need
not init multiuser data)

Initialize
Function?

Yes

Cleanup
Function?

Yes

Test
Count

Test
Cases

test
of get_key() 
test of original crypt and decrypt 
test
of read_group() 
test of crypt and decrypt 
test
of mu_load_multiuser_files() 
test of test parameter
check functions 
test of free_list()

Suite

Mysuite(need
load multiuser data

Initialize
Function?

Yes

Cleanup
Function?

Yes

Test
Count

Test
Cases

test
of get_admin_info() 
test of mu_get_all_user_info() 
test
of find_user() 
test of write_group() 
test of
mu_login() 
test of mu_logout() 
test of
mu_islogin() 
test of mu_user_authentication() 
test
of mu_modify_password() 
test of
mu_modify_settings() 
test of
mu_get_user_settings() 
test of mu_register() 
test
of mu_pop_is_busy() 
test of add_to_list and
remove_from_list

File
Generated By CUnit at Mon May 22 11:35:57 2006

5:错误处理

对于Cunit本身运行时的错误有可能使我们误认为是被测程序的错误,这样不利于错误的定位,因此Cunit提供了本身的错误处理函数,主要是以下两个:

CU_ErrorCode CU_get_error(void)
const
char* 
CU_get_error_msg(void)

几乎所有的Cunit函数在发生错误的时候,都会设置相应的错误码,我们可以诊断出到底发生了什么:错误码如下:

Error
Value

Description

CUE_SUCCESS

No
error condition.

CUE_NOMEMORY

Memory
allocation failed.

CUE_NOREGISTRY

Test
registry not initialized.

CUE_REGISTRY_EXISTS

Attempt
to CU_set_registry() without CU_cleanup_registry().

CUE_NOSUITE

A
required CU_pSuite pointer was NULL.

CUE_NO_SUITENAME

Required
CU_Suite name not provided.

CUE_SINIT_FAILED

Suite
initialization failed.

CUE_SCLEAN_FAILED

Suite
cleanup failed.

CUE_DUP_SUITE

Duplicate
suite name not allowed.

CUE_NOTEST

A
required CU_pTest pointer was NULL.

CUE_NO_TESTNAME

Required
CU_Test name not provided.

CUE_DUP_TEST

Duplicate
test case name not allowed.

CUE_TEST_NOT_IN_SUITE

Test
is not registered in the specified suite.

CUE_FOPEN_FAILED

An
error occurred opening a file.

CUE_FCLOSE_FAILED

An
error occurred closing a file.

CUE_BAD_FILENAME

A
bad filename was requested (NULL, empty, nonexistent, etc.).

CUE_WRITE_ERROR

An
error occurred during a write to a file.

三:注意

:我们应该重视测试程序代码本身的书写。

:注意测试代码本身也需要进行相应的错误处理和资源回收,否则在后续的测试工程中,使用资源检查工具诊断出来的泄漏通常是测试代码的问题。

:一定要使用断言,不要使用自定义函数。

:主要保持测试代码和被测代码的相互独立,尽量不要改动被测试代码。

:注意单元测试的测试用例的完备性,对于参数的不同情况,需要考虑是否已经将所有的可能全部覆盖。

:注意测试代码的覆盖性,Cunit仅仅运行测试人员写下的程序,如果你不为被测函数写测试代码,它就不会被运行到。

:注意保持测试代码的自动化,保证每一次都可以正确运行。比如一个函数设置一个全局变量,仅仅在某些状态下才可以正确执行,那么设置完成,并断言成功之后,需要将这个全局变量恢复到初始值,一方面,可以保证下次执行还是正确的,另一方面,可以减少对其他测试函数的影响。(当然不能一概而论,可以根据具体的情况适用该条款)

:Cunit用于维持自动的测试环境和测试用例,以便在进行了修改之后,继续检查接口是否发生了变动,以及接口是否正确。他不是一个调试工具,不要将使用debug才可以运行的代码加入进来。

使用CUNIT测试的更多相关文章

  1. 执行CUnit测试出错

    est/test_fifo.test: error while loading shared libraries: libcunit.so.1: cannot open shared object f ...

  2. Gradle 1.12用户指南翻译——第二十二章. 标准的 Gradle 插件

    其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...

  3. C语言单元测试

    转自http://blog.csdn.net/colin719/article/details/1420583 对于敏捷开发来说,单元测试必不可少,对于Java开发来说,JUnit非常好,对于C++开 ...

  4. CUnit的用法

    转自:http://blog.csdn.net/scucj/article/details/4385630/ CUnit下载地址: http://sourceforge.net/projects/cu ...

  5. μCUnit,微控制器的单元测试框架

    在MCU on Eclipse网站上看到Erich Styger在8月26日发布的博文,一篇关于微控制器单元测试的文章,有很高的参考价值,特将其翻译过来以备学习.原文网址:https://mcuone ...

  6. linux C单元测试工具CUnit的编译安装及使用

    1 下载CUnit安装包CUnit-2.1-3.tar.bz2保存至/home/用户/ (安装包版本为文章做成时2016-05-25的最新版本) https://sourceforge.NET/pro ...

  7. (转)使用CUnit进行单元测试和覆盖率统计

    CUnit安装 如果能联网的话,直接 yum install CUnit-devel.x86_64 就完成安装了,注意要安装devel版本,这样才能找到头文件. 编写单元测试代码 CUnit的测试是单 ...

  8. SignalR系列续集[系列8:SignalR的性能监测与服务器的负载测试]

    目录 SignalR系列目录 前言 也是好久没写博客了,近期确实很忙,嗯..几个项目..头要炸..今天忙里偷闲.继续我们的小系列.. 先谢谢大家的支持.. 我们来聊聊SignalR的性能监测与服务器的 ...

  9. Apache Ignite之集群应用测试

    集群发现机制 在Ignite中的集群号称是无中心的,而且支持命令行启动和嵌入应用启动,所以按理说很简单.而且集群有自动发现机制感觉对于懒人开发来说太好了,抱着试一试的心态测试一下吧. 在Apache ...

随机推荐

  1. 已知的CPropertysheet bug: 切换焦点导致无响应

    当一个页面内容比较多时我们首先可能考虑用Tab Control,但如果有很多页面内容需要动态加载则用CPropertySheet比较好点~ CPropertySheet有两种不同的显示模式.一种就是向 ...

  2. WPF-22:WPF绘制五角星改进版(增加半个五角星的绘制)-修改bug

    之前用坐标画多边形的方法,绘制五角星.今天调试时发现当时写的时候有bug,修改一下. 原文: http://blog.csdn.net/yysyangyangyangshan/article/deta ...

  3. MySQL分组数据

    分组 理解分组能够看例如以下一个样例,首先我们打印出products表例如以下 从上面的表中能够看出.每一个vendor都有若干个产品,那么怎么一次统计每一个vendor有多少个产品呢? 这里就能够使 ...

  4. c# winform 弹出确认消息框判断是否删除?

    if (MessageBox.Show("确认删除?", "此删除不可恢复", MessageBoxButtons.YesNo) == DialogResult ...

  5. Windows VS下搭建cocos2d-x环境搭建

    VS2010以上版本(eg:VS2012/VS2013,这里本人用VS2013) 1.环境.安装包准备 2.python安装 3.cocos2d-x安装包解压安装 4.环境变量配置 5.执行setup ...

  6. mysql事务和锁InnoDB(转)

    背景 MySQL/InnoDB的加锁分析,一直是一个比较困难的话题.我在工作过程中,经常会有同事咨询这方面的问题.同时,微博上也经常会收到MySQL锁相关的私信,让我帮助解决一些死锁的问题.本文,准备 ...

  7. pytesser图片文本识别

    python图片文本识别使用的工具是PIL和pytesser.因为他们使用到很多的python库文件,为了避免一个个工具的安装,建议使用pythonxy,这个工具的介绍可参考baidu. pytess ...

  8. CCIE路由实验(6) -- 组播Multicasting

    1.组播IGMP的各种情况2.PIM Dense-Mode3.PIM Sparse-Mode4.PIM双向树和SSM5.动态RP之auto-rp6.动态RP之BSR7.Anycast RP8.域间组播 ...

  9. JQuery 事件及案例

    JQuery事件与JavaScript事件相似,只是把其中的on去掉 1.click,dblclick事件 案例1:点击缩略图换背景 <html xmlns="http://www.w ...

  10. Eclipse用法和技巧二十:一个快速打印技巧

    调试的时候经常用到打印语句,当需要添加的说明字符串和需要打印的数值混淆到一起的时候,需要先写字符串如,"the string here is",接着再输入变量的值.这样一来一去还是 ...