程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 從Google開源RE2庫學習到的C++測試方案

從Google開源RE2庫學習到的C++測試方案

編輯:關於C++

最近因為科研需求,一直在研究Google的開源RE2庫(正則表達式識別庫),庫源碼體積龐大,用C++寫的,對於我這個以前專供Java的人來說真的是一件很痛苦的事,每天只能啃一點點。今天研究了下裡面用到的測試方法,感覺挺好的,拿來跟大家分享下!(哈~C++大牛勿噴)

對於我這個C++菜鳥中的菜鳥而言,平時寫幾個函數想要測試一般都是在main中一個一個的測試,因為沒用C++寫過項目,沒有N多方法所以在main中一個個測試也不費勁。但是對於一個項目而言,或多或少都有N多方法,如果在main中一個個測試的話,不僅效率低而且還容易出錯遺漏什麼的。那麼該怎麼進行測試呢?貌似現在有很多C++自動化測試的工具,反正我是一個沒用過,也沒法評價。我就說下Google在RE2庫裡是怎麼測試的吧。

先用一個超級簡單的例子來做講解:測試兩個方法getAsciiNum()和getNonAsciiNum(),分別求flow中ASCII碼字符的數目和非ASCII碼字符的數目。

第一步:寫個頭文件,定義測試所用類和測試方法。

// test.h  
#define TEST(x, y)   \
    void x##y(void);   \
    TestRegisterer r##x##y(x##y, # x "." # y);   \
    void x##y(void)  
      
void RegisterTest(void (*)(void), const char*);  
      
class TestRegisterer {  
public:  
    TestRegisterer(void (*fn)(void), const char *s) {  
        RegisterTest(fn, s);  
    }  
};

解析:首先看定義的類TestRegisterer,有個構造方法,兩個參數:

1. 一個函數指針:void (*fn)(void),指向我們具體要編寫的測試方法名;

2. 一個字符串:constchar *s,屬於該測試方法的描述信息。

這個構造函數調用了另一個函數RegisterTest(),具體實現見下面。

然後看最上面定義的宏TEST(x, y),主要將其替換為TestRegisterer r##x##y(x##y, # x"." # y);其中x##y作為方法名,# x"." # y作為描述信息。這裡可能有些和我一樣入門級別的人沒怎麼看懂這個宏,因為不知道前後加void x##y(void);這個是干嘛用的?一開始我也沒想明白,因為不加的話就會報錯,後來通過gcc的-E選項激活宏編譯,看了下編譯期間展開成啥模樣了。這裡以一個簡單的例子作為說明:假設x為test,y為flow,如果不加前後那個,那麼展開後為TestRegisterer rtestflow(testflow, "test.flow"); 這明顯是個函數聲明,有兩個參數,第二個是字符串,那麼第一個是什麼?編譯器會認為是個函數名(實際上也是的),但這個函數前面明顯未定義,就會報找不到此函數聲明的錯誤,所以就需要在之前加上void x##y(void);聲明函數,當然光聲明不實現在鏈接時同樣報錯,所以就需要在之後加上void x##y(void)進行具體實現了,注意這裡沒有逗號,也沒有具體實現的{},因為這只是宏,Google的所有測試函數是這樣寫的:

TEST(x, y) {   
    ....      // 具體實現   
}

那麼上面例子TEST(test, flow){ ...  // 具體實現 },整體展開後就是這樣:

void testflow(void);  
TestRegisterer rtestflow(testflow, "test.flow");  
void testflow(void) {   
    ....      // 具體實現   
}

第二步:N多個具體的測試實現。

01.#include <string>   
02.#include <vector>
03.#include "test.h"
04.
05.#define arraysize(array) (sizeof(array)/sizeof((array)[0]))
06.#define CHECK_EQ(x, y) if((x) != (y)) { printf("test failed!\n"); system("pause"); exit(0); }
07.
08.struct TestFlow {
09. const char* flow;
10. const int num;
11.};
12.
13.static struct TestFlow tests1[] = {
14. {"\x02\x97\xa4\xe6\xfe\x0c", 2},
15. {"\x05\x97\x35\xe6\xfe\xac\x04", 3},
16. {"\xb2\x97\xa5\xe6\x9c\x1c\x58\xaa\x97\x03", 3},
17. {"\x32\x97\xa5\x05\x9c\xac\xe8\xaa\x57", 3},
18. {"\x42\x01\xa5\x86\x0c\x56\xe8\xaa\x97\x03", 5},
19.};
20.
21.static struct TestFlow tests2[] = {
22. {"\x02\x97\xa4\xe6\xfe\x0c", 4},
23. {"\x05\x97\x35\xe6\xfe\xac\x04", 4},
24. {"\xb2\x97\xa5\xe6\x9c\x1c\x58\xaa\x97\x03", 7},
25. {"\x32\x97\xa5\x05\x9c\xac\xe8\xaa\x57", 6},
26. {"\x42\x01\xa5\x86\x0c\x56\xe8\xaa\x97\x03", 5},
27.};
28.
29.int getAsciiNum(const char*);
30.int getNonAsciiNum(const char*);
31.
32.TEST(TestAsciiNum, Simple) {
33. int failed = 0;
34. for (int i = 0; i < arraysize(tests1); i++) {
35. const TestFlow& t = tests1[i];
36. int num = getAsciiNum(t.flow);
37. if (num != t.num) {
38. failed++;
39. }
40. }
41. CHECK_EQ(failed, 0);
42.}
43.
44.TEST(TestNonAsciiNum, Simple) {
45. int failed = 0;
46. for (int i = 0; i < arraysize(tests2); i++) {
47. const TestFlow& t = tests2[i];
48. int num = getNonAsciiNum(t.flow);
49. if (num != t.num) {
50. failed++;
51. }
52. }
53. CHECK_EQ(failed, 0);
54.}
55.
56.int getAsciiNum(const char* flow) {
57. // we assume that there's no \x00 in flow otherwise we cannot use strlen()
58. int num = 0, i;
59.
60. for(i = 0; i < strlen(flow); i++) {
61. // ASCII: 0 ~ 127
62. if(flow[i] >= 0 && flow[i] < 128)
63. num++;
64. }
65.
66. return num;
67.}
68.
69.int getNonAsciiNum(const char* flow) {
70. // we assume that there's no \x00 in flow otherwise we cannot use strlen()
71. int num = 0, i;
72. for(i = 0; i < strlen(flow); i++) {
73. // ASCII: 0 ~ 127
74. if(flow[i] < 0 || flow[i] >= 128)
75. num++;
76. }
77.
78. return num;
79.}

看上去一目了然,TEST(TestAsciiNum, Simple)和TEST(TestNonAsciiNum, Simple)就是兩個具體的測試實現了,這個例子很簡單,僅僅是為了說明問題。

第三步:具體的測試方案。

// test.cpp  
#include <stdio.h>  
#include <stdlib.h>  
#include "test.h"  
      
struct Test {  
    void (*fn)(void);  
    const char *name;  
};  
      
static Test tests[10000];  
static int ntests;  
      
void RegisterTest(void (*fn)(void), const char *name) {  
    tests[ntests].fn = fn;  
    tests[ntests++].name = name;  
}  
      
int main(int argc, char **argv) {  
    for (int i = 0; i < ntests; i++) {  
        printf("%s\n", tests[i].name);  
        tests[i].fn();  
    }  
    printf("PASS\n");  
    system("pause");  
      
    return 0;  
}

解析:

1. 結構體Test存儲具體的測試實現,定義最多能有10000個不同的方法測試,也就是能同時測試10000個方法。

2. ntests代表實際所測試的方法數,我這裡就是2了。

3. RegisterTest()具體的實現也比較簡單,就是將實際所要測試的方法名和描述信息存儲到Test結構體數組tests中。

4. 最後就是在main中進行統一測試了,首先輸出測試方法描述信息,以便知道當前測試了哪些方法及如果有測試失敗時能及時進行排查。然後就是具體的執行測試函數了。

本例的測試結果如下:

思考:下面看下具體是如何執行的:

大家可能覺得main寫的太簡潔,一開始什麼都沒調用,直接來個for循環,ntests的值初始不是0嗎?在main一開始也沒顯式的調用RegisterTest()將測試方法加進去啊,怎麼一進入main,ntests就變成2了?

大家要記住:所有的測試具體實現都是在TEST這個宏裡面,而宏是在編譯期間就開始展開了。以 TEST(TestAsciiNum, Simple){ ... }為例,具體的執行過程如下:

運行期間:

從main開始,執行for循環,先後執行了具體的測試實現方法TestAsciiNumSimple()和TestAsciiNonNumSimple()從而完成測試。

用一個圖來說明更加清晰(圖畫的不太好,望見諒~~~)

作者:csdn博客 lanxuezaipiao

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