從Google開源RE2庫學習到的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*); classTestRegisterer { public: TestRegisterer(void (*fn)(void), const char *s) { RegisterTest(fn, s); } };
解析:首先看定義的類TestRegisterer,有個構造方法,兩個引數:
1. 一個函式指標:void (*fn)(void),指向我們具體要編寫的測試方法名;
2. 一個字串:const char *s,屬於該測試方法的描述資訊。
這個建構函式呼叫了另一個函式RegisterTest(),具體實現見下面。
然後看最上面定義的巨集TEST(x, y),主要將其替換為TestRegisterer r##x##y(x##y, # x
TEST(x, y) { .... // 具體實現 }
那麼上面例子TEST(test, flow){ ... // 具體實現 },整體展開後就是這樣:
void testflow(void); TestRegisterer rtestflow(testflow, "test.flow"); void testflow(void) { .... // 具體實現 }
第二步:N多個具體的測試實現。
#include <string> #include <vector> #include "test.h" #define arraysize(array) (sizeof(array)/sizeof((array)[0])) #define CHECK_EQ(x, y) if((x) != (y)) { printf("test failed!\n"); system("pause"); exit(0); } struct TestFlow { const char* flow; const int num; }; static struct TestFlow tests1[] = { {"\x02\x97\xa4\xe6\xfe\x0c", 2}, {"\x05\x97\x35\xe6\xfe\xac\x04", 3}, {"\xb2\x97\xa5\xe6\x9c\x1c\x58\xaa\x97\x03", 3}, {"\x32\x97\xa5\x05\x9c\xac\xe8\xaa\x57", 3}, {"\x42\x01\xa5\x86\x0c\x56\xe8\xaa\x97\x03", 5}, }; static struct TestFlow tests2[] = { {"\x02\x97\xa4\xe6\xfe\x0c", 4}, {"\x05\x97\x35\xe6\xfe\xac\x04", 4}, {"\xb2\x97\xa5\xe6\x9c\x1c\x58\xaa\x97\x03", 7}, {"\x32\x97\xa5\x05\x9c\xac\xe8\xaa\x57", 6}, {"\x42\x01\xa5\x86\x0c\x56\xe8\xaa\x97\x03", 5}, }; int getAsciiNum(const char*); int getNonAsciiNum(const char*); TEST(TestAsciiNum, Simple) { int failed = 0; for (int i = 0; i < arraysize(tests1); i++) { const TestFlow& t = tests1[i]; int num = getAsciiNum(t.flow); if (num != t.num) { failed++; } } CHECK_EQ(failed, 0); } TEST(TestNonAsciiNum, Simple) { int failed = 0; for (int i = 0; i < arraysize(tests2); i++) { const TestFlow& t = tests2[i]; int num = getNonAsciiNum(t.flow); if (num != t.num) { failed++; } } CHECK_EQ(failed, 0); } int getAsciiNum(const char* flow) { // we assume that there's no \x00 in flow otherwise we cannot use strlen() int num = 0, i; for(i = 0; i < strlen(flow); i++) { // ASCII: 0 ~ 127 if(flow[i] >= 0 && flow[i] < 128) num++; } return num; } int getNonAsciiNum(const char* flow) { // we assume that there's no \x00 in flow otherwise we cannot use strlen() int num = 0, i; for(i = 0; i < strlen(flow); i++) { // ASCII: 0 ~ 127 if(flow[i] < 0 || flow[i] >= 128) num++; } return num; }
看上去一目瞭然,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){ ... }為例,具體的執行過程如下:
編譯期間:
TEST(TestAsciiNum, Simple)展開為:
void TestAsciiNumSimple(void); TestRegisterer rTestAsciiNumSimple(TestAsciiNumSimple, "TestAsciiNum.Simple"); void TestAsciiNumSimple(void) { int failed = 0; for (int i = 0; i < arraysize(tests1); i++) { const TestFlow& t = tests1[i]; int num = getAsciiNum(t.flow); if (num != t.num) { failed++; } } CHECK_EQ(failed, 0); }
然後就觸發呼叫了TestRegisterer的構造方法從而開始執行RegisterTest(TestAsciiNumSimple, "TestAsciiNum.Simple")方法,將TestAsciiNumSimple方法名和描述資訊"TestAsciiNum.Simple"加入到結構體陣列tests中,這時ntests增為1,同理另一個巨集TEST(TestNonAsciiNum, Simple)展開後也將TestAsciiNonNumSimple方法名和描述資訊"TestNonAsciiNum.Simple"加入到結構體陣列tests中,這時ntests增為2,這是編譯期間做的事。
執行期間:
從main開始,執行for迴圈,先後執行了具體的測試實現方法TestAsciiNumSimple()和TestAsciiNonNumSimple()從而完成測試。
用一個圖來說明更加清晰(圖畫的不太好,望見諒~~~)
相關推薦
從Google開源RE2庫學習到的C++測試方案
最近因為科研需求,一直在研究Google的開源RE2庫(正則表示式識別庫),庫原始碼體積龐大,用C++寫的,對於我這個以前專供Java的人來說真的是一件很痛苦的事,每天只能啃一點點。今天研究了下里面用到的測試方法,感覺挺好的,拿來跟大家分享下!(哈~C++大牛勿噴) 對於我這個C++
從預設解構函式學習c++,new,delete,記憶體洩漏,野指標
預設解構函式:當系統沒有顯式定義解構函式,編譯器同樣會為物件定義一個預設解構函式,預設的解構函式只能釋放普通資料成員所佔用的空間,無法通過釋放通過new和malloc進行申請的空間,因此避免記憶體洩漏,我們要顯式的解構函式對申請的空間釋放。 記憶體洩漏(Memory Leak)是指程式中己動態分配的堆記憶體
數據庫性能測試方案示例
blog 數據庫 alt http mage tail post src class 原文地址:dhttp://blog.csdn.net/qq_23101033/article/details/76825559 數據庫性能測試方案示例
Google開源C++ 單元測試框架Google Test系列(gtest)之初始gtest
下載 最新原始碼地址:https://github.com/google/googletest V1.3和V1.
玩轉Google開源C++單元測試框架Google Test系列(gtest)(總)
前段時間學習和了解了下Google的開源C++單元測試框架Google Test,簡稱gtest,非常的不錯。 我們原來使用的是自己實現的一套單元測試框架,在使用過程中,發現越來越多使用不便之處,而這樣不便之處,gtest恰恰很好的解決了。 其實gtest本身的實現並不複雜
Google開源C++單元測試框架Google Test系列(gtest)之斷言
gtest中,斷言的巨集可以理解為分為兩類,一類是ASSERT系列,一類是EXPECT系列。一個直觀的解釋就是: ASSERT_* 系列的斷言,當檢查點失敗時,退出當前函式(注意:並非退出當前案例)。 EXPECT_* 系列的斷言,當檢查點失敗時,繼續往下
Google開源C++單元測試框架Google Test系列(gtest)之引數化
在設計測試案例時,經常需要考慮給被測函式傳入不同的值的情況。我們之前的做法通常是寫一個通用方法,然後編寫在測試案例呼叫它。即使使用了通用方法,這樣的工作也是有很多重複性的,程式設計師都懶,都希望能夠少寫程式碼,多複用程式碼。 Google的程式設計師也
Google開源C++單元測試框架Google Test系列(gtest)之- 事件機制
gtest提供了多種事件機制,非常方便我們在案例之前或之後做一些操作。總結一下gtest的事件一共有3種: 全域性的,所有案例執行前後。 TestSuite級別的,在某一批案例中第一個案例前,最後一個案例執行後 TestCase級別的,每個TestCase前後。 全域
Gtest:死亡測試 玩轉Google開源C++單元測試框架Google Test系列(gtest)之五 - 死亡測試
轉自:玩轉Google開源C++單元測試框架Google Test系列(gtest)之五 - 死亡測試 一、前言 “死亡測試”名字比較恐怖,這裡的“死亡”指的的是程式的崩潰。通常在測試過程中,我們需要考慮各種各樣的輸入,有的輸入可能直接導致程式崩潰,這時我們就需要檢查程式是否按照預期的方式掛掉,這也就是所
Gtest:引數化 玩轉Google開源C++單元測試框架Google Test系列(gtest)之四 - 引數化
轉自:玩轉Google開源C++單元測試框架Google Test系列(gtest)之四 - 引數化 一、前言 在設計測試案例時,經常需要考慮給被測函式傳入不同的值的情況。我們之前的做法通常是寫一個通用方法,然後編寫在測試案例呼叫它。即使使用了通用方法,這樣的工作也是有很多重複性的,程式設計師都懶,都希望
【1.1】Eigen C++ 矩陣開源庫學習之稠密矩陣和陣列操作——矩陣類
稠密矩陣和陣列操作 http://eigen.tuxfamily.org/dox-devel/group__DenseMatrixManipulation__chapter.html 包含模組: 1.矩陣類 2.矩陣和向量的運算
玩轉Google開源C++單元測試框架Google Test系列(gtest)之一
一、前言本篇將介紹一些gtest的基本使用,包括下載,安裝,編譯,建立我們第一個測試Demo工程,以及編寫一個最簡單的測試案例。 二、下載如果不記得網址, 直接在google裡搜gtest,第一個就是。目前gtest的最新版本為1.3.0,從下列地址可以下載到該最新版本:三、
玩轉Google開源C++單元測試框架Google Test系列(gtest)之六
一、前言使用gtest編寫的測試案例通常本身就是一個可執行檔案,因此執行起來非常方便。同時,gtest也為我們提供了一系列的執行引數(環境變數、命令列引數或程式碼裡指定),使得我們可以對案例的執行進行一些有效的控制。二、基本介紹前面提到,對於執行引數,gtest提供了三種設定
玩轉Google開源C++單元測試框架Google Test系列(gtest)之五
一、前言 “死亡測試”名字比較恐怖,這裡的“死亡”指的的是程式的崩潰。通常在測試過程中,我們需要考慮各種各樣的輸入,有的輸入可能直接導致程式崩潰,這時我們就需要檢查程式是否按照預期的方式掛掉,這也就是所謂的“死亡測試”。gtest的死亡測試能做到在一個安全的環境下執行崩潰的
玩轉Google開源C++單元測試框架Google Test系列(gtest)之四
一、前言在設計測試案例時,經常需要考慮給被測函式傳入不同的值的情況。我們之前的做法通常是寫一個通用方法,然後編寫在測試案例呼叫它。即使使用了通用方法,這樣的工作也是有很多重複性的,程式設計師都懶,都希望能夠少寫程式碼,多複用程式碼。Google的程式設計師也一樣,他們考慮到了
c語言庫函數測試
print 異常終止 不存在 eache 函數名 mode .com src 參數 1.函數名: abort功 能: 異常終止一個進程用 法: void abort(void);程序例: 1 #include <stdio.h> 2 #include &l
Linux下經常使用的C/C++開源Socket庫
bsd 面向對象 sql數據庫 高速 com telnet ade ftp 版本 1. Linux Socket Programming In C++ : http://tldp.org/LDP/LG/issue74/tougher.html2. A
優秀開源軟件學習系列(一)——從零學習Spring4以及學習方法分享
文檔 軟件 準備 相關性 培訓 獎勵 在哪裏 方式 列表 一、目的1.掌握Spring4怎樣使用,以便將這個框架作為自己的一項技能。2.掌握Spring官網是怎樣介紹其產品的,在心中對Spring有最官方的、最直觀的了解。在Spring的相關領域,能夠知道怎麽下載Sprin
【JMeter4.0學習(十一)】之JMeter對(Mysql、Oracle)數據庫性能測試腳本開發
conn 遇到的問題 mys .cn SQ 數據庫性能測試 pos rac 問題總結 一、MySQL數據庫鏈接: 註:下面所產生的問題一律參考詳見:《【JMeter4.0】之遇到的問題總結(持續更新)》(包括Mysql、Orcale) 準備:引包,包路徑一定要放對位置,
機器學習開源演算法庫
C++計算機視覺 CCV —基於C語言/提供快取/核心的機器視覺庫,新穎的機器視覺庫 OpenCV—它提供C++, C, Python, Java 以及 MATLAB介面,並支援Windows, Linux, Android and Mac OS作業系統。