CPPUTest 單元測試架構(針對 C 單元測試的使用說明)

來源:互聯網
上載者:User

標籤:

CPPUTest 雖然名稱上看起來是 C++ 的單元測試架構, 其實它也是支援測試 C 代碼的.

本文主要介紹用CPPUTest來測試 C 代碼. (C++沒用過, 平時主要用的是C) C++相關的內容都省略了.

本文基於 debian v7.6 x86_64.

 

1. CPPUTest 安裝

現在各個Linux的發行版的源都有豐富的軟體資源, 而且安裝方便.

但是如果想要在第一時間使用最新版本的開源軟體, 還是得從源碼安裝.

 

debian系統為了追求穩定性, apt源中的軟體一般都比較舊. 所以本文中的例子是基於最新源碼的CPPUTest.

 

1.1 apt-get 安裝
$ sudo apt-get install cpputest

 

1.2 源碼安裝

1. 下載源碼, 官網: http://cpputest.github.io/

2. 編譯源碼

$ tar zxvf cpputest-3.6.tar.gz$ cd cpputest-3.6/$ ./configure$ make

 

最後我沒有實際安裝, 而是直接使用編譯出的二進位。

 

2. CPPUTest 介紹 2.1 構造待測試代碼 (C語言)
/* file: sample.h */#include <stdio.h>#include <string.h>#include <stdlib.h>struct Student {    char* name;    int score;};void ret_void(void);int ret_int(int, int);double ret_double(double, double);char* ret_pchar(char*, char*);struct Student* init_student(struct Student* s, char* name, int score);

 

/* file: sample.c */#include "sample.h"#ifndef CPPUTESTint main(int argc, char *argv[]){    char* pa;    char* pb;    pa = (char*) malloc(sizeof(char) * 80);    pb = (char*) malloc(sizeof(char) * 20);    strcpy(pa, "abcdefg\0");    strcpy(pb, "hijklmn\0");            printf ("Sample Start......\n");        ret_void();    printf ("ret_int: %d\n", ret_int(100, 10));    printf ("ret_double: %.2f\n", ret_double(100.0, 10.0));    printf ("ret_pchar: %s\n", ret_pchar(pa, pb));    struct Student* s = (struct Student*) malloc(sizeof(struct Student));    s->name = (char*) malloc(sizeof(char) * 80);        init_student(s, "test cpputest", 100);    printf ("init_Student: name=%s, score=%d\n", s->name, s->score);    printf ("Sample End  ......\n");    free(pa);    free(pb);    free(s->name);    free(s);        return 0;}#endifvoid ret_void(){    printf ("Hello CPPUTest!\n");}/* ia + ib */int ret_int(int ia, int ib){    return ia + ib;}/* da / db */double ret_double(double da, double db){    return da / db;}/* pa = pa + pb */char* ret_pchar(char* pa, char* pb){    return strcat(pa, pb);}/* s->name = name, s->score = score */void init_student(struct Student* s, char* name, int score){    strcpy(s->name, name);    s->score = score;}

 

2.2 測試案例的組成, 寫法

CPPUTest 的測試案例非常簡單, 首先定義一個 TEST_GROUP, 然後定義屬於這個 TEST_GROUP 的 TEST.

需要注意的地方是:

1. 引用 CPPUTest 中的2個標頭檔

#include <CppUTest/CommandLineTestRunner.h>#include <CppUTest/TestHarness.h>

 

2. 引用 C 標頭檔時, 需要使用 extern "C" {}

extern "C"{#include "sample.h"}

 

下面的例子是測試 sample.c 中 ret_int 的代碼.

構造了一個測試成功, 一個測試失敗的例子

/* file: test.c */#include <CppUTest/CommandLineTestRunner.h>#include <CppUTest/TestHarness.h>extern "C"{#include "sample.h"}/* 定義個 TEST_GROUP, 名稱為 sample */TEST_GROUP(sample){};/* 定義一個屬於 TEST_GROUP 的 TEST, 名稱為 ret_int_success */TEST(sample, ret_int_success){    int sum = ret_int(1, 2);    CHECK_EQUAL(sum, 3);}/* 定義一個屬於 TEST_GROUP 的 TEST, 名稱為 ret_int_failed */TEST(sample, ret_int_failed){    int sum = ret_int(1, 2);    CHECK_EQUAL(sum, 4);}int main(int argc, char *argv[]){    CommandLineTestRunner::RunAllTests(argc, argv);    return 0;}

 

2.3 測試案例結果判斷 ( fail, 各種assert等等)

測試完成後, 可以用 CPPUTest 提供的宏來判斷測試結果是否和預期一致.

CPPUTest 提供的用於判斷的宏如下: (上面的測試代碼就使用了 CHECK_EQUAL)

Assertion 宏

含義

CHECK(boolean condition) condition==True則成功; 反之失敗
CHECK_TEXT(boolean condition, text) condition==True則成功; 反之失敗, 並且失敗時輸出 text資訊
CHECK_EQUAL(expected, actual) expected==actual則成功; 反之失敗
CHECK_THROWS(expected_exception, expression) 拋出的異常 expected_exception==exception則成功; 反之失敗
STRCMP_EQUAL(expected, actual) 字串 expected==actual則成功; 反之失敗
LONGS_EQUAL(expected, actual) 數字 expected==actual則成功; 反之失敗
BYTES_EQUAL(expected, actual) 數字 expected==actual則成功; 反之失敗 (數字是 8bit 寬)
POINTERS_EQUAL(expected, actual) 指標 expected==actual則成功; 反之失敗
DOUBLES_EQUAL(expected, actual, tolerance) double型 expected和actual在誤差範圍內(tolerance)相等則成功; 反之失敗
FAIL(text) 總是失敗, 並輸出 text 資訊

 

2.4 運行測試案例時的編譯選項配置 (主要是C語言相關的)

這一步是最關鍵的, 也就是編譯出單元測試檔案. 下面是 makefile 的寫法, 關鍵位置加了注釋.

# makefile for sample cpputestCPPUTEST_HOME = /home/wangyubin/Downloads/cpputest-3.6CC      := gccCFLAGS    := -g -WallCFLAGS  += -std=c99CFLAGS  += -D CPPUTEST            # 編譯測試檔案時, 忽略sample.c的main函數, sample.c的代碼中用了宏CPPUTEST# CPPUTest 是C++寫的, 所以用 g++ 來編譯 測試檔案CPP     := g++CPPFLAGS  := -g -WallCPPFLAGS  += -I$(CPPUTEST_HOME)/includeLDFLAGS := -L$(CPPUTEST_HOME)/lib -lCppUTestsample: sample.osample.o: sample.h sample.c    $(CC) -c -o sample.o sample.c $(CFLAGS)# 追加的測試程式編譯test: test.o sample.o    $(CPP) -o [email protected] test.o sample.o $(LDFLAGS)test.o: sample.h test.c    $(CPP) -c -o test.o test.c $(CPPFLAGS).PHONY: cleanclean:    @echo "clean..."    rm -f test sample    rm -f sample.o test.o

 

編譯測試檔案

make test  <-- 會產生一個檔案名稱為 test 可執行檔

編譯sample程式時, 需要把 "CFLAGS  += -D CPPUTEST" 這句注釋掉, 否則沒有main函數.

 

2.5 運行測試案例, 查看結果的方法

運行可執行檔 test 就可以實施測試.

$ ./test    <-- 預設執行, 沒有參數test.c:34: error: Failure in TEST(sample, ret_int_failed)    expected <3>    but was  <4>    difference starts at position 0 at: <          4         >                                                   ^..Errors (1 failures, 2 tests, 2 ran, 2 checks, 0 ignored, 0 filtered out, 1 ms)=================================================================================$ ./test -c   <-- -c 執行結果加上顏色 (成功綠色, 失敗紅色)test.c:34: error: Failure in TEST(sample, ret_int_failed)    expected <3>    but was  <4>    difference starts at position 0 at: <          4         >                                                   ^..Errors (1 failures, 2 tests, 2 ran, 2 checks, 0 ignored, 0 filtered out, 1 ms) <-- bash中顯示紅色=================================================================================$ ./test -v  <-- -v 顯示更為詳細的資訊TEST(sample, ret_int_failed)test.c:34: error: Failure in TEST(sample, ret_int_failed)    expected <3>    but was  <4>    difference starts at position 0 at: <          4         >                                                   ^ - 1 msTEST(sample, ret_int_success) - 0 msErrors (1 failures, 2 tests, 2 ran, 2 checks, 0 ignored, 0 filtered out, 1 ms)=================================================================================$ ./test -r 2   <-- -r 指定測試執行的次數, 這裡把測試重複執行2遍Test run 1 of 2test.c:34: error: Failure in TEST(sample, ret_int_failed)    expected <3>    but was  <4>    difference starts at position 0 at: <          4         >                                                   ^..Errors (1 failures, 2 tests, 2 ran, 2 checks, 0 ignored, 0 filtered out, 0 ms)Test run 2 of 2test.c:34: error: Failure in TEST(sample, ret_int_failed)    expected <3>    but was  <4>    difference starts at position 0 at: <          4         >                                                   ^..Errors (1 failures, 2 tests, 2 ran, 2 checks, 0 ignored, 0 filtered out, 1 ms)=================================================================================$ ./test -g sample    <-- -g 指定 TEST_GROUP, 本例其實只有一個 TEST_GROUP sampletest.c:34: error: Failure in TEST(sample, ret_int_failed)    expected <3>    but was  <4>    difference starts at position 0 at: <          4         >                                                   ^..Errors (1 failures, 2 tests, 2 ran, 2 checks, 0 ignored, 0 filtered out, 1 ms)=================================================================================$ ./test -n ret_int_success    <-- -s 指定執行其中一個 TEST, 名稱為 ret_int_success.OK (2 tests, 1 ran, 1 checks, 0 ignored, 1 filtered out, 0 ms)=================================================================================$ ./test -v -n ret_int_success  <-- 參數也可以搭配使用TEST(sample, ret_int_success) - 0 msOK (2 tests, 1 ran, 1 checks, 0 ignored, 1 filtered out, 0 ms)

 

2.6 補充: setup and teardown

上面 test.c 檔案中 TEST_GROUP(sample) 中的代碼是空的, 其實 CPPUTest 中內建了 2 個調用 setup 和 teardown.

在 TEST_GROUP 中實現這2個函數之後, 每個屬於這個 TEST_GROUP 的 TEST 在執行之前都會調用 setup, 執行之後會調用 teardown.

修改 test.c 中的 TEST_GROUP 如下:

/* 定義個 TEST_GROUP, 名稱為 sample */TEST_GROUP(sample){    void setup()    {        printf ("測試開始......\n");    }    void teardown()    {        printf ("測試結束......\n");    }};

 

重新執行測試: (每個測試之前, 之後都多了上面的列印資訊)

$ make cleanclean...rm -f test samplerm -f sample.o test.o$ make testg++ -c -o test.o test.c -g -Wall -I/home/wangyubin/Downloads/cpputest-3.6/includegcc -c -o sample.o sample.c -g -Wall -std=c99 -D CPPUTEST            g++ -o test test.o sample.o -L/home/wangyubin/Downloads/cpputest-3.6/lib -lCppUTest$ ./test -vTEST(sample, ret_int_failed)測試開始......test.c:44: error: Failure in TEST(sample, ret_int_failed)    expected <3>    but was  <4>    difference starts at position 0 at: <          4         >                                                   ^測試結束...... - 0 msTEST(sample, ret_int_success)測試開始......測試結束...... - 0 msErrors (1 failures, 2 tests, 2 ran, 2 checks, 0 ignored, 0 filtered out, 0 ms)

 

2.7 記憶體流失檢測外掛程式

記憶體流失一直是C/C++代碼中令人頭疼的問題, 還好, CPPUTest 中提供了檢測記憶體流失的外掛程式, 使用這個外掛程式, 可使我們的代碼更加健壯.

 

使用記憶體檢測外掛程式時, 測試代碼 和 待測代碼 在編譯時間都要引用.

-include $(CPPUTEST_HOME)/include/CppUTest/MemoryLeakDetectorMallocMacros.h

 

makefile 修改如下:

# makefile for sample cpputestCPPUTEST_HOME = /home/wangyubin/Downloads/cpputest-3.6CC      := gccCFLAGS    := -g -WallCFLAGS  += -std=c99CFLAGS  += -D CPPUTEST            # 編譯測試檔案時, 忽略sample.c的main函數, sample.c的代碼中用了宏CPPUTEST# CPPUTest 是C++寫的, 所以用 g++ 來編譯 測試檔案CPP     := g++CPPFLAGS  := -g -WallCPPFLAGS  += -I$(CPPUTEST_HOME)/includeLDFLAGS := -L$(CPPUTEST_HOME)/lib -lCppUTest# 記憶體泄露檢測MEMFLAGS = -include $(CPPUTEST_HOME)/include/CppUTest/MemoryLeakDetectorMallocMacros.hsample: sample.osample.o: sample.h sample.c    $(CC) -c -o sample.o sample.c $(CFLAGS) $(MEMFLAGS)# 追加的測試程式編譯test: test.o sample.o    $(CPP) -o [email protected] test.o sample.o $(LDFLAGS)test.o: sample.h test.c    $(CPP) -c -o test.o test.c $(CPPFLAGS)  $(MEMFLAGS).PHONY: cleanclean:    @echo "clean..."    rm -f test sample    rm -f sample.o test.o

 

修改 sample.c 中的 init_student 函數, 構造一個記憶體流失的例子.

/* s->name = name, s->score = score */void init_student(struct Student* s, char* name, int score){    char* name2 = NULL;    name2 = (char*) malloc(sizeof(char) * 80); /* 這裡申請的記憶體, 最後沒有釋放 */    strcpy(s->name, name2);    strcpy(s->name, name);    s->score = score;}

 

修改 test.c 追加一個測試 init_student 函數的測試案例

TEST(sample, init_student){    struct Student *stu = NULL;    stu = (struct Student*) malloc(sizeof(struct Student));    char name[80] = {‘t‘, ‘e‘, ‘s‘, ‘t‘, ‘\0‘};        init_student(stu, name, 100);    free(stu);}

 

執行測試, 可以發現測試結果中提示 sample.c 72 行有記憶體流失風險,

這一行正是 init_student 函數中用 malloc 申請記憶體的那一行.

$ make cleanclean...rm -f test samplerm -f sample.o test.o$ make testg++ -c -o test.o test.c -g -Wall -I/home/wangyubin/Downloads/cpputest-3.6/include  -include /home/wangyubin/Downloads/cpputest-3.6/include/CppUTest/MemoryLeakDetectorMallocMacros.hgcc -c -o sample.o sample.c -g -Wall -std=c99 -D CPPUTEST             -include /home/wangyubin/Downloads/cpputest-3.6/include/CppUTest/MemoryLeakDetectorMallocMacros.hg++ -o test test.o sample.o -L/home/wangyubin/Downloads/cpputest-3.6/lib -lCppUTest$ ./test -v -n init_studentTEST(sample, init_student)測試開始......測試結束......test.c:47: error: Failure in TEST(sample, init_student)    Memory leak(s) found.Alloc num (4) Leak size: 80 Allocated at: sample.c and line: 72. Type: "malloc"     Memory: <0x120c5f0> Content: ""Total number of leaks:  1NOTE:    Memory leak reports about malloc and free can be caused by allocating using the cpputest version of malloc,    but deallocate using the standard free.    If this is the case, check whether your malloc/free replacements are working (#define malloc cpputest_malloc etc). - 0 msErrors (1 failures, 3 tests, 1 ran, 0 checks, 0 ignored, 2 filtered out, 0 ms)

CPPUTest 單元測試架構(針對 C 單元測試的使用說明)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.