您好,登錄后才能下訂單哦!
這篇文章主要介紹了C/C++中gtest怎么用,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
Google C++ Testing Framework(簡稱gtest,http://code.google.com/p/googletest/)是Google公司發布的一個開源C/C++單元測試框架,已被應用于多個開源項目及Google內部項目中,知名的例子包括Chrome Web瀏覽器、LLVM編譯器架構、Protocol Buffers數據交換格式及工具等。
優秀的C/C++單元測試框架并不算少,相比之下gtest仍具有明顯優勢。與CppUnit比,gtest需要使用的頭文件和函數宏更集中,并支持測試用例的自動注冊。與CxxUnit比,gtest不要求Python等外部工具的存在。與Boost.Test比,gtest更簡潔容易上手,實用性也并不遜色。Wikipedia給出了各種編程語言的單元測試框架列表(http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks)。
一、基本用法
gtest當前的版本是1.5.0,如果使用Visual C++編譯,要求編譯器版本不低于7.1(Visual C++ 2003)。如下圖所示,它的msvc文件夾包含Visual C++工程和項目文件,samples文件夾包含10個使用范例。
一般情況下,我們的單元測試代碼只需要包含頭文件gtest.h。gtest中常用的所有結構體、類、函數、常量等,都通過命名空間testing訪問,不過gtest已經把最簡單常用的單元測試功能包裝成了一些帶參數宏,因此在簡單的測試中常常可以忽略命名空間的存在。
按照gtest的叫法,宏TEST為特定的測試用例(Test Case)定義了一個可執行的測試(Test)。它接受用戶指定的測試用例名(一般取被測對象名)和測試名作為參數,并劃出了一個作用域供填充測試宏語句和普通的C++代碼。一系列TEST的集合就構成一個簡單的測試程序。
常用的測試宏如下表所示。以ASSERT_開頭和以EXPECT_開頭的宏的區別是,前者在測試失敗時會給出報告并立即終止測試程序,后者在報告后繼續執行測試程序。
ASSERT宏 | EXPECT宏 | 功能 |
ASSERT_TRUE | EXPECT_TRUE | 判真 |
ASSERT_FALSE | EXPECT_FALSE | 判假 |
ASSERT_EQ | EXPECT_EQ | 相等 |
ASSERT_NE | EXPECT_NE | 不等 |
ASSERT_GT | EXPECT_GT | 大于 |
ASSERT_LT | EXPECT_LT | 小于 |
ASSERT_GE | EXPECT_GE | 大于或等于 |
ASSERT_LE | EXPECT_LE | 小于或等于 |
ASSERT_FLOAT_EQ | EXPECT_FLOAT_EQ | 單精度浮點值相等 |
ASSERT_DOUBLE_EQ | EXPECT_DOUBLE_EQ | 雙精度浮點值相等 |
ASSERT_NEAR | EXPECT_NEAR | 浮點值接近(第3個參數為誤差閾值) |
ASSERT_STREQ | EXPECT_STREQ | C字符串相等 |
ASSERT_STRNE | EXPECT_STRNE | C字符串不等 |
ASSERT_STRCASEEQ | EXPECT_STRCASEEQ | C字符串相等(忽略大小寫) |
ASSERT_STRCASENE | EXPECT_STRCASENE | C字符串不等(忽略大小寫) |
ASSERT_PRED1 | EXPECT_PRED1 | 自定義謂詞函數,(pred, arg1)(還有_PRED2, ..., _PRED5) |
寫個簡單的測試試一下。假設我們實現了一個加法函數:
// add.h #pragma once inline int Add(int i, int j) { return i+j; }
對應的單元測試程序可以這樣寫:
// add_unittest.cpp #include "add.h" #include <gtest/gtest.h> TEST(Add, 負數) { EXPECT_EQ(Add(-1,-2), -3); EXPECT_GT(Add(-4,-5), -6); // 故意的 } TEST(Add, 正數) { EXPECT_EQ(Add(1,2), 3); EXPECT_GT(Add(4,5), 6); }
代碼中,測試用例Add包含兩個測試,正數和負數(這里利用了Visual C++ 2005以上允許標識符包含Unicode字符的特性)。編譯運行效果如下:
在控制臺界面中,通過的測試用綠色表示,失敗的測試用紅色表示。雙橫線分隔了不同的測試用例,其中包含的每個測試的啟動與結果用單橫線和RUN ... OK或RUN ... FAILED標出。失敗的測試會打印出代碼行和原因,測試程序***為所有用例和測試顯示統計結果。建議讀者試一下換成ASSERT_宏的不同之處。
每個測試宏還可以使用<<運算符在測試失敗時輸出自定義信息,如:
ASSERT_EQ(M[i], N[j]) << "i = " << i << ", j = " << j;
編譯命令行中,gtest_mt.lib和gtest_main_mt.lib就是前面使用VC項目文件生成的靜態庫。有意思的是,測試代碼不需要注冊測試用例,也不需要定義main函數,這是gtest通過后一個靜態庫自動完成的,它的實現代碼如下:
// gtest-main.cc int main(int argc, char **argv) { std::cout << "Running main() from gtest_main.cc\n"; testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
其中,函數InitGoogleTest負責注冊需要運行的所有測試用例,宏RUN_ALL_TEST負責執行所有測試,如果全部成功則返回0,否則返回1。當然,我們也可以僅鏈接gtest_mt.lib,自己提供main函數。
二、測試固件
很多時候,我們想在不同的測試執行前創建相同的配置環境,在測試執行結束后執行相應的清理工作,測試固件(Test Fixture)為這種需求提供了方便。“Fixture”是一個漢語中不易直接對應的詞,《美國傳統詞典》對它的解釋是“(作為附屬物的)固定裝置;被固定的狀態”。在單元測試中,Fixture的作用是為測試創建輔助性的上下文環境,實現測試的初始化和終結與測試過程本身的分離,便于不同測試使用相同代碼來搭建固定的配置環境。用體操比賽的說法,測試過程體現了特定測試的自選動作,測試固件則體現了對一系列測試(在開始和結束時)的規定動作。有些講單元測試的書籍直接把測試固件稱為Scaffolding(腳手架)。
使用測試固件比單純調用TEST宏稍微麻煩一些:
1. 從gtest的testing::Test類派生一個類,用public或protected定義以下所有成員。
2. (可選)建立環境:使用默認構造函數,或定義一個虛成員函數virtual void SetUp()。
3. (可選)銷毀環境:使用析構函數,或定義一個虛成員函數virtual void TearDown()。
4. 用TEST_F定義測試,寫法與TEST相同,但測試用例名必須為上面定義的類名。
每個帶固件的測試的執行順序是:
1. 調用默認構造函數創建一個新的帶固件對象。
2. 立即調用SetUp函數。
3. 運行TEST_F體。
4. 立即調用TearDown函數。
5. 調用析構函數銷毀類對象。
從gtest的實現代碼可以看到,TEST_F又從用戶定義的類自動派生了一個類,因此要求public或protected的訪問權限;大括號里的內容被擴展成一個名為TestBody的虛成員函數的函數體,因此可以在其中直接訪問成員變量和成員函數。其實TEST也采用了相同的實現機制,只是它直接從gtest的testing::Test自動派生類,所以可以指定任意用例名。testing::Test類的SetUp和TearDown都是空函數,所以它只執行測試步驟,沒有環境的創建和銷毀。
借用上面Add函數寫個固件測試的例子:
// add_unittest2.cpp #include "add.h" #include <stdio.h> #include <gtest/gtest.h> class AddTest: public testing::Test { public: virtual void SetUp() { puts("SetUp()"); } virtual void TearDown() { puts("TearDown()"); } }; TEST_F(AddTest, 正數) { ASSERT_GT(Add(1,2), 3); // 故意的 ASSERT_EQ(Add(4,5), 6); // 也是故意的 }
編譯運行效果如下:
必須強調,每個TEST_F開始都創建了一個新的帶固件對象,因此每個測試都使用獨立的完全相同的初始環境,各測試可以按任意順序執行(參見--gtest_shuffle命令行選項)。但在某些情況下,我們可能需要在各個測試間共享一個相同的環境來保存和傳遞狀態,或者環境的狀態是只讀的,可以只初始化一次,再或者創建環境的過程開銷很高,要求只初始化一次。共享某個固件環境的所有測試合稱為一個“測試套件”(Test Suite),gtest中利用靜態成員變量和靜態成員函數實現這個概念:
1. (可選)在testing::Test的派生類中,定義若干靜態成員變量來維護套件的狀態。
2. (可選)建立共享環境:定義一個靜態成員函數static void SetUpTestCase()。
3. (可選)銷毀共享環境:定義一個靜態成員函數static void TearDownCase()。
另外,還可以使用gtest的Environment類來建立和銷毀所有測試共用的全局環境(對應于上圖顯示的“Global test environment set-up”和“Global test environment tear-down”):
class Environment { public: virtual ~Environment() {} virtual void SetUp() {} virtual void TearDown() {} };
gtest文檔建議測試程序自己定義main函數并在其中創建和注冊全局環境對象:
Environment* AddGlobalTestEnvironment(Environment* env);
三、異常測試
C程序中要返回出錯信息,可以利用特定的函數返回值、函數的輸出(outbound)參數、或者設置全局變量(如C標準庫定義的errno,Windows API中的“上次錯誤”(last error)代碼,Winsock中與每個socket相關聯的錯誤代碼)。C++程序常用異常(exception)來返回出錯信息,gtest為異常測試提供了專用的測試宏:
ASSERT宏 | EXPECT宏 | 功能 |
ASSERT_NO_THROW | EXPECT_NO_THROW | 不拋出異常,參數為(statement) |
ASSERT_ANY_THROW | EXPECT_ANY_THROW | 拋出異常,參數為(statement) |
ASSERT_THROW | EXPECT_THROW | 拋出特定類型的異常,參數為(statement, type) |
需要注意,這些測試宏都接受C/C++語句作為參數,所以既可以像前面那樣傳遞表達式,也可以傳遞用大括號包起來的代碼塊。
借助下面的被測函數:
// divide.h #pragma once #include <stdexcept> int divide(int dividend, int divisor) { if(!divisor) { throw std::length_error("can't be divided by 0"); // 故意的 } return dividend / divisor; }
測試程序如下:
// divide-unittest.cpp #include <gtest/gtest.h> #include "./divide.h" TEST(Divide, ByZero) { EXPECT_NO_THROW(divide(-1, 2)); EXPECT_ANY_THROW({ int k = 0; divide(k, k); }); EXPECT_THROW(divide(100000, 0), std::invalid_argument); }
編譯運行效果如下
容易想到,gtest的這些異常測試宏是用C++的try ... catch語句來實現的:
try { statement; } catch(type const&) { // throw } catch(...) { // any throw } // no throw
如果把上圖中Visual C++的編譯選項/EHsc換成/EHa,try ... catch就可以同時支持C++風格的異常和Windows系統的結構化異常(SEH)。這樣,即使刪掉divide函數里的if判斷,測試代碼的EXPECT_ANY_THROW宏也會成功捕獲異常。
遺憾的是,目前僅使用這些測試宏無法得到獲得被拋出異常的詳細信息(如divide函數中的報錯文本),這和gtest自身不愿意使用C++異常有關。
四、值參數化測試
有些時候,我們需要對代碼實現的功能使用不同的參數進行測試,比如使用大量隨機值來檢驗算法實現的正確性,或者比較同一個接口的不同實現之間的差別。gtest把“集中輸入測試參數”的需求抽象出來提供支持,稱為值參數化測試(Value Parameterized Test)。
值參數化測試包括4個步驟:
1. 從gtest的TestWithParam模板類派生一個類(記為C),模板參數為需要輸入的測試參數的類型。由于TestWithParam本身是從Test派生的,所以C就成了一個測試固件類。
2. 在C中,可以實現諸如SetUp、TearDown等方法。特別地,測試參數由TestWithParam實現的GetParam()方法依次返回。
3. 使用TEST_P(而不是TEST_F)定義測試。
4. 使用INSTANTIATE_TEST_CASE_P宏集中輸入測試參數,它接受3個參數:任意的文本前綴,測試類名(這里即為C),以及測試參數值序列。gtest框架依次使用這些參數值生成測試固件類實例,并執行用戶定義的測試。
gtest提供了專門的模板函數來生成參數值序列,如下表所示:
參數值序列生成函數 | 含義 |
Bool() | 生成序列{false, true} |
Range(begin, end[, step]) | 生成序列{begin, begin+step, begin+2*step, ...} (不含end),step默認為1 |
Values(v1, v2, ..., vN) | 生成序列{v1, v2, ..., vN} |
ValuesIn(container), ValuesIn(iter1, iter2) | 枚舉STL container,或枚舉迭代器范圍[iter1, iter2) |
Combine(g1, g2, ..., gN) | 生成g1, g2, ..., gN的笛卡爾積,其中g1, g2, ..., gN均為參數值序列生成函數(要求C++0x的<tr1/tuple>) |
寫個小程序試一下。假設我們實現了一種快速累加算法,希望使用另一種直觀算法進行正確性校驗。算法實現和測試代碼如下
// addupto.h #pragma once inline unsigned NaiveAddUpTo(unsigned n) { unsigned sum = 0; for(unsigned i = 1; i <= n; ++i) sum += i; return sum; } inline unsigned FastAddUpTo(unsigned n) { return n*(n+1)/2; }
測試程序如下:
// addupto_test.cpp #include <gtest/gtest.h> #include "addupto.h" class AddUpToTest : public testing::TestWithParam<unsigned> { public: AddUpToTest() { n_ = GetParam(); } protected: unsigned n_; }; TEST_P(AddUpToTest, Calibration) { EXPECT_EQ(NaiveAddUpTo(n_), FastAddUpTo(n_)); } INSTANTIATE_TEST_CASE_P( NaiveAndFast, // prefix AddUpToTest, // test case name testing::Range(1u, 1000u) // parameters );
注意TestWithParam的模板參數設置為unsigned類型,而在代碼倒數第2行,兩個常量值都加了u后綴來指定為unsigned類型。熟悉C++的讀者應該知道,模板函數在進行類型推斷(deduction)時匹配相當嚴格,不像普通函數那樣允許類型提升(promotion)。如果上面省略u后綴,就會造成編譯錯誤。當然還可以顯式指定模板參數:testing::Range<unsigned>(1, 1000)。
運行效果如下,這里省略了開頭的大部分輸出(命令行窗口設置的緩沖區高度為3000行)。
感謝你能夠認真閱讀完這篇文章,希望小編分享的“C/C++中gtest怎么用”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。