程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> CPPUTest 單元測試框架(針對 C 單元測試的使用說明),cpputest單元測試

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

編輯:關於C語言

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


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 CPPUTEST
int 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;
}
#endif

void 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 cpputest

CPPUTEST_HOME = /home/wangyubin/Downloads/cpputest-3.6

CC      := gcc
CFLAGS    := -g -Wall
CFLAGS  += -std=c99
CFLAGS  += -D CPPUTEST            # 編譯測試文件時, 忽略sample.c的main函數, sample.c的代碼中用了宏CPPUTEST

# CPPUTest 是C++寫的, 所以用 g++ 來編譯 測試文件
CPP     := g++
CPPFLAGS  := -g -Wall
CPPFLAGS  += -I$(CPPUTEST_HOME)/include

LDFLAGS := -L$(CPPUTEST_HOME)/lib -lCppUTest


sample: sample.o

sample.o: sample.h sample.c
    $(CC) -c -o sample.o sample.c $(CFLAGS)

# 追加的測試程序編譯
test: test.o sample.o
    $(CPP) -o $@ test.o sample.o $(LDFLAGS)

test.o: sample.h test.c
    $(CPP) -c -o test.o test.c $(CPPFLAGS)


.PHONY: clean
clean:
    @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 ms
TEST(sample, ret_int_success) - 0 ms

Errors (1 failures, 2 tests, 2 ran, 2 checks, 0 ignored, 0 filtered out, 1 ms)

=================================================================================
$ ./test -r 2   <-- -r 指定測試執行的次數, 這裡把測試重復執行2遍
Test run 1 of 2

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, 0 ms)

Test run 2 of 2

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 -g sample    <-- -g 指定 TEST_GROUP, 本例其實只有一個 TEST_GROUP sample

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 -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 ms

OK (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 clean
clean...
rm -f test sample
rm -f sample.o test.o
$ make test
g++ -c -o test.o test.c -g -Wall -I/home/wangyubin/Downloads/cpputest-3.6/include
gcc -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 -v
TEST(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 ms
TEST(sample, ret_int_success)測試開始......
測試結束......
 - 0 ms

Errors (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 cpputest

CPPUTEST_HOME = /home/wangyubin/Downloads/cpputest-3.6

CC      := gcc
CFLAGS    := -g -Wall
CFLAGS  += -std=c99
CFLAGS  += -D CPPUTEST            # 編譯測試文件時, 忽略sample.c的main函數, sample.c的代碼中用了宏CPPUTEST

# CPPUTest 是C++寫的, 所以用 g++ 來編譯 測試文件
CPP     := g++
CPPFLAGS  := -g -Wall
CPPFLAGS  += -I$(CPPUTEST_HOME)/include

LDFLAGS := -L$(CPPUTEST_HOME)/lib -lCppUTest

# 內存洩露檢測
MEMFLAGS = -include $(CPPUTEST_HOME)/include/CppUTest/MemoryLeakDetectorMallocMacros.h

sample: sample.o

sample.o: sample.h sample.c
    $(CC) -c -o sample.o sample.c $(CFLAGS) $(MEMFLAGS)

# 追加的測試程序編譯
test: test.o sample.o
    $(CPP) -o $@ test.o sample.o $(LDFLAGS)

test.o: sample.h test.c
    $(CPP) -c -o test.o test.c $(CPPFLAGS)  $(MEMFLAGS)


.PHONY: clean
clean:
    @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 clean
clean...
rm -f test sample
rm -f sample.o test.o
$ make test
g++ -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.h
gcc -c -o sample.o sample.c -g -Wall -std=c99 -D CPPUTEST             -include /home/wangyubin/Downloads/cpputest-3.6/include/CppUTest/MemoryLeakDetectorMallocMacros.h
g++ -o test test.o sample.o -L/home/wangyubin/Downloads/cpputest-3.6/lib -lCppUTest
$ ./test -v -n init_student
TEST(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:  1
NOTE:
    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 ms

Errors (1 failures, 3 tests, 1 ran, 0 checks, 0 ignored, 2 filtered out, 0 ms)

急懂juint測試框架的同志,做一個很簡單的軟件的單元測試,只需要半天左右時間,做成150元

建議你自己來做,這個不是很難的……

JUnit是Java進行單元測試的一個框架, 需要下載junit, 3.8版本和後來的4.0以後版本編寫測試的方法略有不同,
在3.8.2中需要導入junit.framework.中的類, 進行測試的類必須繼承自TestCase類, 測試方法名稱中需要含test字樣, 可以在setup和teardown函數中管理一些每個測試函數都需要的資源比如數據庫連接等,在測試函數中使用assert開頭的函數來進行測試代碼開發.以下是從junit文檔中摘出的范例:
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
* Some simple tests.
*
*/
public class SimpleTest extends TestCase {
protected int fValue1;
protected int fValue2;

protected void setUp() {
fValue1= 2;
fValue2= 3;
}
public static Test suite() {

/*
* the type safe way
*
TestSuite suite= new TestSuite();
suite.addTest(
new SimpleTest("add") {
protected void runTest() { testAdd(); }
}
);

suite.addTest(
new SimpleTest("testDivideByZero") {
protected void runTest() { testDivideByZero(); }
}
);
return suite;
*/

/*
* the dynamic way
*/
return new TestSuite(SimpleTest.class);
}
public void testAdd() {
double result= fValue1 + fValue2;
// forced failure result == 5
assertTrue(result == 6);
}
public void testDivideByZero() {
int zero= 0;
int result= 8/zero;
result++; // avoid warning for not using result
}
public void testEquals() {
assertEquals(12, 12);
assertEquals(12L, 12L);
assertEquals(new Long(12), new Long(12));

assertEquals("Size", 12, 13);
assertEquals("Capacity", 12.0, 11.99, 0.0);
}
public static void......余下全文>>
 

我該怎寫單元測試?

?? 在我的團隊中,單元測試是較難推行的敏捷實踐之一,我思考後覺得有以下原因:1、主觀上覺得會加大工作量,影響進度2、從未接觸junit等單元測試框架,害怕接觸新事物3、團隊形式上要求、形式上開展,但是未能結合培訓、Code Review等方式持續推行???? 其實單元測試是個相當簡單的技術,當然,要做的完美也要花很多的心思。單元測試無非就是:AAA模式——Arrange(測試設置)、Act(調用測試裡的代碼)、Assert(測試通過的標准)。測試設置:測試環境的准備,例如構造mock對象,設置數據庫表數據等Act: ?????? 編寫測試方法,調用被測試代碼Assert: 利用斷言設置通過的標准?????? 很多開發人員不寫單元測試,但是他會寫個main方法去測試代碼,這樣做不好的地方在於測試的方法提交後一般要求會刪除掉,不能做沉澱。main方法也不能進行自動執行測試。我建議還未踏入門檻的程序員可以先把main方法要寫的測試代碼,使用單元測試的結構搬到單元測試中。踏出第一步,關鍵你已經出發,你要在路上!??
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved