Cunit单元测试

48 篇文章 1 订阅


转自:http://www.cnblogs.com/onlytiancai/p/3847524.html

安装CUnit

复制代码
wget http://sourceforge.net/projects/cunit/files/latest/download
tar xf CUnit-2.1-3.tar.bz2
cd CUnit-2.1-3
./bootstrap
./configure
make
make install
复制代码

了解下单元测试的概念: 一次测试(registry)可以分成多个suit,一个suit里可以有多个 test case, 每个suit有个setup和teardown函数,分别在执行suit之前或之后调用。

下面的代码是一个单元测试的架子,这里测试的是库函数strlen,这里面只有一个suit, 就是testSuite1,testSuit1里里有一特test case,就是testcase,testcase里有一个 测试,就是test_string_length。

整体上就是这么一个架子,suit,test case, test都可以往里扩展。

复制代码
#include <assert.h> 
#include <stdlib.h> 
#include <string.h> 

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

// 测试库函数strlen功能是否正常
void test_string_lenth(void){
    char* test = "Hello";
    int len = strlen(test);
    CU_ASSERT_EQUAL(len,5);
}

// 创建一特test case,里面可以有多个测试 
CU_TestInfo testcase[] = {
    { "test_for_lenth:", test_string_lenth },
    CU_TEST_INFO_NULL
};

// suite初始化,
int suite_success_init(void) {
    return 0;
}

// suite 清理
int suite_success_clean(void) {
    return 0;
}

// 定义suite集, 里面可以加多个suit
CU_SuiteInfo suites[] = {
    // 以前的版本没有那两个NULL参数,新版需要加上,否则就coredump
    //{"testSuite1", suite_success_init, suite_success_clean, testcase },
    {"testSuite1", suite_success_init, suite_success_clean, NULL, NULL, testcase },
    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();

        // 第一种:直接输出测试结果
        CU_basic_set_mode(CU_BRM_VERBOSE);
        CU_basic_run_tests();

        // 第二种:交互式的输出测试结果
        // CU_console_run_tests();

        // 第三种:自动生成xml,xlst等文件
        //CU_set_output_filename("TestMax");
        //CU_list_tests_to_file();
        //CU_automated_run_tests();

        CU_cleanup_registry();

        return CU_get_error();

    }

}

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

然后Makefile里增加如下代码

复制代码
INC=-I /usr/local/include/CUnit
LIB=-L /usr/local/lib/

test: testcase.c
    gcc -o test.o $(INC) $(LIB) -g  $^ -l cunit
    ./test.o

.PHONY: test
复制代码

再执行make test就可以执行单元测试了,结果大约如下

复制代码
gcc -o test.o -I /usr/local/include/CUnit -L /usr/local/lib/ -g  testcase.c -l cunit
./test.o


     CUnit - A unit testing framework for C - Version 2.1-3
     http://cunit.sourceforge.net/


Suite: testSuite1
  Test: test_for_lenth: ...passed

Run Summary:    Type  Total    Ran Passed Failed Inactive
              suites      1      1    n/a      0        0
               tests      1      1      1      0        0
             asserts      1      1      1      0      n/a

Elapsed time =    0.000 seconds
复制代码

可以看到testSuite1下面的test_for_lenth通过测试了。 注意一下,安装完新的动态库后记得ldconfig,否则-l cunit可能会报错 如果还是不行就要 /etc/ld.so.conf 看看有没有 /usr/local/lib , cunit默认把库都放这里了。

调试coredump

就上面的单元测试, 如果使用注释掉那行,执行make test时就会产生coredump。如下

// 定义suite集, 里面可以加多个suit
CU_SuiteInfo suites[] = {
    {"testSuite1", suite_success_init, suite_success_clean, testcase },
    //{"testSuite1", suite_success_init, suite_success_clean, NULL, NULL, testcase },
    CU_SUITE_INFO_NULL
};

但默认coredump不会保存在磁盘上,需要执ulimit -c unlimited才可以,然后要 指定一下coredump的路径和格式:

echo "/tmp/core-%e-%p" > /proc/sys/kernel/core_pattern

其中%e是可执行文件名,%p是进程id。然后编译这段代码的时候要加上-g的选项,意思 是编译出调试版本的可执行文件,在调试的时候可以看到行号。

gcc -o test.o -I /usr/local/include/CUnit -L /usr/local/lib/ -g  testcase.c -l cunit

在执行./test.o后就会产生一个coredump了,比如是/tmp/core-test.o-16793, 这时候 用gdb去调试该coredump,第一个参数是可执行文件,第二个参数是coredump文件

gdb test.o /tmp/core-test.o-16793

挂上去后默认会有一些输出,其中有如下

Program terminated with signal 11, Segmentation fault.

说明程序遇到了段错误,崩溃了,一般段错误都是因为内存访问引起的, 我们想知道 引起错误的调用栈, 输入bt回车,会看到类似如下的显示

(gdb) bt
#0  0x00007fe1b0b22cb2 in CU_register_nsuites () from /usr/local/lib/libcunit.so.1
#1  0x00007fe1b0b22d28 in CU_register_suites () from /usr/local/lib/libcunit.so.1
#2  0x0000000000400a8a in AddTests () at testcase.c:46
#3  0x0000000000400adf in RunTest () at testcase.c:56
#4  0x0000000000400b13 in main (argc=1, argv=0x7fff4fa51928) at testcase.c:79

这样大概知道是咋回事了,报错在testcase.c的46行上,再往里就是cunit的调用栈了, 我们看不到行号,好像得有那个so的调试信息才可以,目前还不会在gdb里动态挂符号文件 ,所以就先不管了,输入q退出调试器,其它命令用输入help学习下。

if(CUE_SUCCESS != CU_register_suites(suites)){

就调用了一个CU_register_suites函数,函数本身应该没有错误,可能是传给他从参数 有问题,就是那个suites,该参数构建的代码如下:

CU_SuiteInfo suites[] = {
    {"testSuite1", suite_success_init, suite_success_clean, testcase },
    CU_SUITE_INFO_NULL
};

是个CU_SuiteInfo的数组,就感觉是构建这个类型没构建对,然后就看他在哪儿定义 的

# grep -n "CU_SuiteInfo" /usr/local/include/CUnit/*
/usr/local/include/CUnit/TestDB.h:696:typedef struct CU_SuiteInfo {

在/usr/local/include/CUnit/TestDB.h的696行,具体如下

复制代码
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;
复制代码

 

可以看到,该结构有6个成员,但我们定义的时候只有4个成员,没有设置pSetUpFunc和 pTearDownFunc的,所以做如下修改就能修复该问题了。

-    {"testSuite1", suite_success_init, suite_success_clean, testcase },
+    {"testSuite1", suite_success_init, suite_success_clean, NULL, NULL, testcase },

 

对了,gdb用yum安装就行了。

性能剖析

好些时候我们要去分析一个程序的性能,比如哪个函数调用了多少次,被谁调用了, 平均每次调用花费多少时间等。这时候要用gprof,gprof是分析profile输出的。 要想执行时输出profile文件编译时要加-pg选项,

gcc -o helloworld.o -pg -g helloworld.c
./helloworld.o

执行上面语句后会在当前目录下生成gmon.out文件, 然后用gprof去读取并显示出来, 因为可能显示的比较长,所以可以先重定向到一个文件prof_info.txt里

gprof -b -A -p -q helloworld.o gmon.out >prof_info.txt 

 

参数的含义先这么用,具体可以搜,最后查看prof_info.txt里会有需要的信息, 大概 能看懂,具体可以搜。

复制代码
Flat profile:

Each sample counts as 0.01 seconds.
 no time accumulated

  %   cumulative   self              self     total           
 time   seconds   seconds    calls  Ts/call  Ts/call  name    
  0.00      0.00     0.00       15     0.00     0.00  cmp_default
  0.00      0.00     0.00       15     0.00     0.00  cmp_reverse
  0.00      0.00     0.00        4     0.00     0.00  w_strlen
  0.00      0.00     0.00        2     0.00     0.00  sort
  0.00      0.00     0.00        1     0.00     0.00  change_str_test
  0.00      0.00     0.00        1     0.00     0.00  concat_test
  0.00      0.00     0.00        1     0.00     0.00  customer_manager
  0.00      0.00     0.00        1     0.00     0.00  hello_world
  0.00      0.00     0.00        1     0.00     0.00  n_hello_world
  0.00      0.00     0.00        1     0.00     0.00  reverse
  0.00      0.00     0.00        1     0.00     0.00  sort_test

            Call graph


granularity: each sample hit covers 2 byte(s) no time propagated

index % time    self  children    called     name
                0.00    0.00      15/15          sort [4]
[1]      0.0    0.00    0.00      15         cmp_default [1]
-----------------------------------------------
                0.00    0.00      15/15          sort [4]
[2]      0.0    0.00    0.00      15         cmp_reverse [2]
-----------------------------------------------
                0.00    0.00       1/4           reverse [10]
                0.00    0.00       1/4           main [16]
                0.00    0.00       2/4           concat_test [6]
[3]      0.0    0.00    0.00       4         w_strlen [3]
-----------------------------------------------
复制代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值