C开发中的单元测试:CUNIT 的使用

1.CUNIT 介绍

CUnit是一种C语言单元测试框架 ,继Junit CppUnit的成功后, c语言环境下也出现了开发源码的白盒测试用例CUnit。CUnit以静态库的形式提供给用户使用,用户编写程序的时候直接链接此静态库就可以了。它提供了一个简单的单元测试框架,并且为常用的数据类型提供了丰富的断言语句支持。

2.CUNIT的下载和安装(linux)

2.1、首先在

站点1:http://www.hnlyyk.icoc.cc/col.jsp?id=103,下载cunit源码包:cunit-2.0-1.tar

站点2:http://download.csdn.net/download/u010193457/9193477 ,下载最新版本的CUnit源码包
2.2、将CUnit源码包(cunit-2.0-1.tar)复制到Linux的目标目录下,比如我在这里放到了/usr/unittest目录下。
2.3、CUnit源码包的解压。打开[System Tools]-〉[Terminal],进入到/usr/unittest目录下,
输入如下命令:
#tar xvf cunit-2.0-1.tar
执行结束后,将会在当前目录下生成一个解压后的文件夹(cunit-2.0-1)。
2.4、解压结束后,开始进行编译和安装。
#cd cunit-2.0-1
#aclocal 
#autoconf 
#automake 
#chmod u+x configure
#./configure --prefix 
(对上一句进行解释,这个位置,需要你输入要安装的目录,目录的格式举例如下:/usr/unittest/)
#make
#make install
这里需要一段时间...
#cd /usr/unittest/lib
#ldconfig
到此处为止,CUnit的安装基本上就到一段落了。

3.CUNIT结构框架

CUnit的测试是单线程启动,只能注册一个Test Registry, 一次测试(Test Registry)可以运行多个测试包(Test Suite),而每个测试包可以包括多个测试用例(Test Case),每个测试用例又包含一个或者多个断言类的语句。具体到程序的结构上,一次测试下辖多个Test Suite,它对应于程序中各个独立模块;一个Suite管理多个Test Case,它对应于模块内部函数实现。每个Suite可以含有setup和teardown函数,分别在执行suite的前后调用。
注册一个测试用例(如果已经注册了你可以cleanup掉然后重新注册使用)然后CU_add_suite增加你的模块然后CU_add_test再在你的模块下挂载你的模块内的测试函数。所有的挂载完毕后,调用你想使用的界面进行测试。

4.CUNIT测试模式

下面是四种测试模式:
1 Automated Output to xml file Non-interactive
2 Basic Flexible programming interface Non-interactive
3 Console Console interface (ansi C) Interactive
4 Curses Graphical interface (Unix) Interact
注意1,2是没有交互功能的,4的Unix下的我是windows用户, 选择第3个介绍console而且console是可以人机交互的。

5.CUNIT测试流程

使用CUnit进行测试的基本流程如下所示:
⒈书写代测试的函数(如果必要,需要写suite的init/cleanup函数)
⒉初始化Test Registry - CU_initialize_registry()
⒊把测试包(Test Suites)加入到Test Registry - CU_add_suite()
⒋加入测试用例(Test Case)到测试包当中 - CU_add_test()
⒌使用适当的接口来运行测试测试程序,例如 CU_console_run_tests()
⒍清除Test Registry - CU_cleanup_registry()
⒈4 TestCase的构成
一个CUnit TestCase的构成有以下文件:test.c、testcase.c、Main.c 与 Makefile构成。即一个测试范例由①被测函数,②测试函数(定义测试用例和测试包),③运行测试函数及④Makefile四部分构成。

6.CUNIT构成

CUnit TestCase的构成
一个CUnit TestCase的构成有以下文件:test.c、testcase.c、Main.c 与 Makefile构成。
主要构成函数说明
以下以一个简单的testcase为例说明。
我要测试的是整数求最大值的函数maxi,我使用如下文件组织结构:
⒈test.c:被测函数(定义maxi()函数)
⒉testcase.c:测试函数(定义测试用例和测试包)
⒊Main.c:运行测试函数(调用CUnit的Automated接口运行测试)

7.CUNIT函数介绍

1.      registry初始化

CU_ErrorCode    CU_initialize_registry(void);
//用户在调用任何其他CUnit函数之前调用本函数,如果不这样做可能会导致系统崩溃。
返回值为:
CUE_SUCCESS初始化成功。
CUE_NOMEMORY内存分配失败。

2.    registry释放
void CU_cleanup_registry(void);
//当测试完成后,用户应该调用这个函数进行清理和释放的框架所使用的内存。
//其他一些关于注册的内部函数,主要用于内部和测试的目的,较少使用
CU_pTestRegistry CU_get_registry(void);
CU_pTestRegistry CU_set_registry(CU_pTestRegistry pTestRegistry);
CU_pTestRegistry CU_create_new_registry(void);
void CU_destroy_existing_registry(CU_pTestRegistry* ppRegistry);


3.     向registry中增加suites
CU_pSuite CU_add_suite(const char* strName, CU_InitializeFunc pInit, CU_CleanupFunc pClean);
//创建一个新的测试集,具有指定的名称、初始化函数、清除函数。
返回值为:
l  CUE_SUCCESS                    suite创建成功。
l  CUE_NOREGISTRY            registry尚未初始化。
l  CUE_NO_SUITENAME     strName为NULL。
l  CUE_DUP_SUITE               suite的名称已经存在。
l  CUE_NOMEMORY            内存分配失败。

4.     向registry中增加test
CU_pTest CU_add_test(CU_pSuite pSuite, const char* strName, CU_TestFunc pTestFunc)
//创建一个新的具有指定名称和测试功能的test,并在指定的suite中注册。该suite必须是已经使用CU_add_suite()创建的。
返回值为:
l  CUE_SUCCESS                    suite创建成功。
l  CUE_NOSUITE                   指定的suite是Null或无效。
l  CUE_NO_TESTNAME       strName为NULL。
l  CUE_NO_TEST                  pTestFunc为null或无效。
l  CUE_DUP_TEST                 test的名称已存在。
l  CUE_NOMEMORY            内存分配失败。
5.     运行Tests
1)     Automated Mode 自动输出到XML文件
非交互式
void CU_automated_run_tests(void);
//运行在所有suites中注册的所有tests
CU_ErrorCode CU_list_tests_to_file(void);
//列出已注册的suites和与其相关联的tests
void CU_set_output_filename(const char* szFilenameRoot);
//设置-Results.xml和-Listing.xml的名字
2)     Basic Mode   基本扩展编程方式
非交互式
CU_ErrorCode CU_basic_run_tests(void);
//运行在所有suites中注册的所有tests
CU_ErrorCode CU_basic_run_suite(CU_pSuite pSuite);
//在一个指定的suite中运行其下所有的tests
CU_ErrorCode CU_basic_run_test(CU_pSuite pSuite, CU_pTest pTest);
//在一个指定的suite中运行指定的test
void CU_basic_set_mode(CU_BasicRunMode mode);
//设置运行的基本模式,控制着tests的输出,主要有下面几种
l  CU_BRM_NORMAL                   失败和运行概况打印。
l  CU_BRM_SILENT              只打印错误消息。
l  CU_BRM_VERBOSE                   Maximum output of run details。
CU_BasicRunMode CU_basic_get_mode(void);
//检测当前的运行模式
void CU_basic_show_failures(CU_pFailureRecord pFailure);
//将所有的失败信息打印到stdout,不依赖于运行模式
3)     Interactive Console Mode  控制台方式
交互式
void CU_console_run_tests(void);
//启动控制台方式
4)         Interactive Curses Mode    Curses图形接口
交互式,只适用于Unix/Linux
void CU_curses_run_tests(void);
//启动Curses方式,需要应用程序中支持ncurses库支持
6.    获取接口目前测试运行的结果
用户代码有时可能需要这些结果。主要包含以下函数:
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)
const CU_pRunSummary CU_get_run_summary(void)
const CU_pFailureRecord CU_get_failure_list(void)
unsigned int CU_get_number_of_failure_records(void)
7.       错误处理
CU_ErrorCode CU_get_error(void)
const char* CU_get_error_msg(void)
//第一个函数返回错误代码本身,而第二个函数返回一条消息,描述错误信息
void CU_set_error_action(CU_ErrorAction action)
CU_ErrorAction CU_get_error_action(void)

8.CUNIT案例演示

下面我们结合一个小实例,来体验一下Cunit的便利吧(学编程,最有效的方式还是自己动手)
先编写一个具体两个简单功能的函数,然后写Testcase来测试它。
文件主要有:
1) strformat.h :字符串功能函数的接口文件
2)strformat.c :字符串功能函数的实现
3)testcase.c : 测试用例及Cunit运行环境
4)makefile :

代码:strformat.h

/* strformat.h --- 
 * 
 * Filename: strformat.h
 * Description: 字符串操作头文件
 * Author: hnlyyk
 * Maintainer: 
 * Created: 一  8月 20 22:57:19 2012 (+0800)
 * Version: 
 * Compatibility: 
 * 
 */
/* Commentary: 
 * 为的是体验Cunit而临时写的几项功能函数,没有多大实际意义,仅仅是为了写测试类
 * 
 * 
 */

/* Change Log:
 * 
 * 
 */

/* Code: */

#ifndef _strformat_h
#define _strformat_h

typedef char * string;

/*************************************************************************
*功能描述:返回字符串的长度
*参数列表:
*返回类型:
**************************************************************************/    
int string_lenth(string word);
/*************************************************************************
*功能描述:返回字符串的大写形式
*参数列表:
*返回类型:
**************************************************************************/
string to_Upper(string word);
/*************************************************************************
*功能描述:连接字符串
*参数列表:
*返回类型:
**************************************************************************/
string add_str(string word1 ,string word2);



#endif


/* strformat.h ends here */
代码:strformat.c

/* strformat.c --- 
 * 
 * Filename: strformat.c
 * Description: 字符串操作
 * Author: hnlyyk
 * Maintainer: 
 * Keywords: 
 * Compatibility: 
 * 
 */

/* Commentary: 
 * 此代码仅为体验Cunit而临时撰写。
 * 
 * 
 */

/* Change Log:
 * 
 * 
 */

/* Code: */
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include "strformat.h"


/**************************************************************************
函数名称:字符串相加
功能描述:
输入参数:
返   回:
**************************************************************************/
string add_str(string word1 ,string word2){
    return (strcat(word1, word2));
}

/**************************************************************************
函数名称:将字符串转换成大写格式
功能描述:
输入参数:
返   回:
**************************************************************************/
string to_Upper(string word){
    int i;
    for(i = 0;word[i] !='\0' ;i++){
        if(word[i]<'z' && word[i]>'a'){
            word[i] -= 32;
        }
    }
    return word;
    
}

/**************************************************************************
函数名称:字符串长度
功能描述:
输入参数:
返   回:
**************************************************************************/
int string_lenth(string word){
    int i;
    for(i = 0 ;word[i] != '\0';i++){
        
    }
    return i;
}

/* strformat.c ends here */
测试代码: testcase.c
/* Commentary:
 * 当前文件用来定义测试方法,suite,及registry信息,若测试方法有变化,只需要修改当前文件即可。
 * 第一步:书写测试函数的代码,建议以"test_"为前缀。
 * 第二步:将测试方法归类,即将相似功能的测试方法放到一个数组里,以便把它们指定给一个suite
 * 第三步:创建suite,可按功能或模块,生成多个test suite,
 * 第四步:书写测试方法的总调用方法,AddTests(),用来统一启动测试方法。
 */

/* Change Log:
 * 
 * 
 */

/* Code: */
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>

#include <CUnit/Basic.h>
#include <CUnit/Console.h>
#include <CUnit/CUnit.h>
#include <CUnit/TestDB.h>
#include "strformat.h"

/**************************************************************************
函数名称:测试string_lenth()方法
功能描述:
输入参数:
返   回:
**************************************************************************/
void test_string_lenth(void){
    string test = "Hello";
    int len = string_lenth(test);
    CU_ASSERT_EQUAL(len,5);
}

/**************************************************************************
函数名称:测试方法to_Upper()
功能描述:
输入参数:
返   回:
**************************************************************************/

void test_to_Upper(void){
    char test[] = "Hello";
    CU_ASSERT_STRING_EQUAL(to_Upper(test),"HELLO");
    
}

/**************************************************************************
函数名称:测试方法 add_str()
功能描述:
输入参数:
返   回:
**************************************************************************/
void test_add_str(void){
    char test1[] = "Hello!";
    char test2[] = "MGC";
    CU_ASSERT_STRING_EQUAL(add_str(test1,test2),"Hello!MGC");
    
}

/**************************************************************************
数组名称:将多个测试方法打包成组,以供指定给一个Suite
功能描述:每个suite可以有一个或多个测试方法,以CU_TestInfo数组形式指定
**************************************************************************/
// CU_TestInfo是Cunit内置的一个结构体,它包括测试方法及描述信息
CU_TestInfo testcase[] = {
    {"test_for_lenth:",test_string_lenth    },
    {"test_for_add:",test_add_str    },
    CU_TEST_INFO_NULL
};

CU_TestInfo testcase2[] = {
    {"test for Upper :",test_to_Upper    },
    CU_TEST_INFO_NULL
};

/**************************************************************************
函数名称:suite初始化过程
功能描述:
输入参数:
返   回:
**************************************************************************/
int suite_success_init(void){
    return 0;
    
}

/**************************************************************************
函数名称:suite清理过程,以便恢复原状,使结果不影响到下次运行
功能描述:
输入参数:
返   回:
**************************************************************************/
int suite_success_clean(void){
    return 0;
}

//定义suite数组,包括多个suite,每个suite又会包括若干个测试方法。
CU_SuiteInfo suites[] = {
    {"testSuite1",suite_success_init,suite_success_clean,testcase},
    {"testsuite2",suite_success_init,suite_success_clean,testcase2},
    CU_SUITE_INFO_NULL
};

/**************************************************************************
函数名称:测试类的调用总接口
功能描述:
输入参数:
返   回:
**************************************************************************/
void AddTests(){
    assert(NULL != CU_get_registry());
    assert(!CU_is_test_running());

    if(CUE_SUCCESS != CU_register_suites(suites)){
        exit(EXIT_FAILURE);

    }
}
/*************************************************************************
*功能描述:运行测试入口
*参数列表:
*返回类型:
**************************************************************************/

int RunTest(){
        if(CU_initialize_registry()){
                fprintf(stderr, " Initialization of Test Registry failed. ");
                exit(EXIT_FAILURE);
        }else{
                AddTests();
                /**** Automated Mode *****************
                CU_set_output_filename("TestMax");
                CU_list_tests_to_file();
                CU_automated_run_tests();
                //************************************/
                
                /***** Basice Mode *******************
                CU_basic_set_mode(CU_BRM_VERBOSE);
                CU_basic_run_tests();
                //************************************/

                /*****Console Mode ********************
                CU_console_run_tests();
                //************************************/

                CU_cleanup_registry();
                
                return CU_get_error();
                
        }

}
/*************************************************************************
*功能描述:测试类主方法
*参数列表:
*返回类型:
**************************************************************************/

int main(int argc, char * argv[])
{
   return  RunTest();
    
}

/* testcase.c ends here */
注:
1)注意结合上面Cunit的组织结构图,理解Cunit中几个角色的关系(CU_TestInfo,CU_SuiteInfo各以数组的形式,将多个Test和Suite组织起来)。
2)Cunit有几种运行模式,如automated,basic,console,有的是可以交互的,有的是没有交互,直接出结果的。

代码:makefile

IINC=-I/usr/local/include/CUnit
LIB=-L/usr/local/lib/

all:  strformat.c testcase.c
    gcc -o test $(INC) $(LIB)  $^ -lcunit -static

下面我们欣赏一下Cunit的常见几种运行模式
1)Automated Mode
先将testcase.c中156~159代码放开注释,此时便是以automated模式运行,此模块没有交互能力,直接生成XML格式的报表,先make,然后运行后,在当前目录下生成两个报表
TestMax-Listing.xml和TestMax-Results.xml(前者是测试用例的列表,后者是测试用例的测试结果) ,但这两个文件是不能直接观看的,要查看这两个文件,需要使用如下xsl和dtd文件:CUnit-List.dtd和CUnit-List.xsl用于解析列表文件, CUnit-Run.dtd和CUnit-Run.xsl用于解析结果文件。这四个文件在CUnit包里面有提供,安装之后在$(PREFIX) /share/CUnit目录下,默认安装的话在/home/lirui/local/share/CUnit目录下。在查看结果之前,需要把这六 个文件:TestMax-Listing.xml, TestMax-Results.xml, CUnit-List.dtd, CUnit-List.xsl, CUnit-Run.dtd, CUnit-Run.xsl拷贝到一个目录下,然后用浏览器打开两个结果的xml文件就可以了。

2) Console Mode
在testcase.c中将其它模式代码注释掉,放开168行代码,便转换成Console模式了。console模式支持交互,如支持运行,查看错误等操作。输入R来运行,输入F来查看错误用例,输入Q来退出。
这种模式在实际中是很实用的。

3)Basic Mode
在testcase.c中将其它模式的代码注释掉,放到163~164行代码,便转换成Basic模式,这种是不支持交互的,直接打印出运行结果。






你可能感兴趣的:(框架,JUnit,单元测试,白盒测试,cunit)