使用GTEST編寫C++測試用例進(jìn)階教程(GTEST advanced中文譯文)

使用GTEST編寫C++測試用例進(jìn)階教程

[TOC]

更多的斷言

這章覆蓋了一些使用頻率較少但是仍然很重要的斷言

明確的成功和失敗

下面三個斷言沒有確切的測試一個值或者表達(dá)式,而是直接生成一個成功或者失敗。就像實際執(zhí)行測試的宏一樣橱健,你可以將定制的失敗消息傳遞進(jìn)去叙甸。

SUCCEED();

產(chǎn)生一個成功。這并不意味著整個測試都是成功捅位,只有當(dāng)測試的斷言在執(zhí)行過程中沒有一個失敗,測試才被認(rèn)為是成功的。

NOTE:SUCCEED()純粹是紀(jì)實的并且現(xiàn)階段不產(chǎn)生任何用戶可見的輸出昌粤。

FAIL();

ADD_FAILURE();

ADD_FAILURE_AT("file_path", line_number);

FAIL()產(chǎn)生一個致命的失敗既绕,然而ADD_FAILURE()ADD_FAILURE_AT()產(chǎn)生一個非致命的錯誤。他們可以在控制流時決定測試的成功或者失敗涮坐。例如:

switch(expression) {

  case 1:

     ... some checks ...

  case 2:

     ... some other checks ...

  default:

     FAIL() << "We shouldn't get here.";

}

NOTE:只能在返回值是void的函數(shù)里才能使用FAIL()

異常的斷言

這些用于驗證一段代碼是否拋出(或不拋出)給定類型的異常凄贩。

Fatal assertion Nonfatal assertion Verifies
ASSERT_THROW(statement, exception_type); EXPECT_THROW(statement, exception_type); statement throws an exception of the given type
ASSERT_ANY_THROW(statement); EXPECT_ANY_THROW(statement); statement throws an exception of any type
ASSERT_NO_THROW(statement); EXPECT_NO_THROW(statement); statement doesn't throw any exception

例如:

ASSERT_THROW(Foo(5), bar_exception);


EXPECT_NO_THROW({

  int n = 5;

  Bar(&n);

});

為了更好地錯誤信息而使用謂詞斷言

盡管GTEST有一套豐富的斷言,但是卻永遠(yuǎn)不夠袱讹,因為不可能預(yù)測用戶可能遇到的所有場景疲扎。有些時候缺乏更好地宏,用戶只能使用EXPECT_TRUE()來檢查復(fù)雜的表達(dá)式捷雕。但是不能向用戶展示表達(dá)式各個部分的值椒丧,會讓哪里出錯變得難以理解。作為一種變通方法救巷,一些用戶選擇自己構(gòu)建失敗消息壶熏,將其流式傳輸?shù)?code>EXPECT_TRUE()中,當(dāng)一個表達(dá)式有副作用或者評估起來很昂貴時浦译,這將是十分困難的棒假。

GTEST為了解決這個問題,提出了三個選項:

使用一個存在的布爾函數(shù)

如果已經(jīng)存在一個返回bool型的函數(shù)(或者是可以隱式轉(zhuǎn)換成bool)管怠,可以在謂詞斷言中使用它來免費打印函數(shù)的參數(shù):

Fatal assertion Nonfatal assertion Verifies
ASSERT_PRED1(pred1, val1) EXPECT_PRED1(pred1, val1) pred1(val1) is true
ASSERT_PRED2(pred2, val1, val2) EXPECT_PRED2(pred2, val1, val2) pred2(val1, val2) is true
... ... ...

上述中淆衷,predn是一個n個參數(shù)的謂詞函數(shù),val1,val2,...,和valn是函數(shù)的參數(shù)渤弛。當(dāng)謂詞應(yīng)用于給定的參數(shù)時返回值為true祝拯,那么斷言為成功,否則為失敗她肯。當(dāng)斷言失敗時佳头,他會打印每個參數(shù)的值。在這兩種情況下晴氨,參數(shù)只被計算一次康嘉。

例子如下:

// Returns true if m and n have no common divisors except 1.

bool MutuallyPrime(int m, int n) { ... }




const int a = 3;

const int b = 4;

const int c = 10;

斷言如下時,將會成功:

EXPECT_PRED2(MutuallyPrime, a, b);

斷言如下時籽前,將會失敗

EXPECT_PRED2(MutuallyPrime, b, c);

失敗信息如下

MutuallyPrime(b, c) is false, where

b is 4

c is 10

使用一個返回斷言結(jié)果的函數(shù)

盡管EXPECT_PRED*和類似的宏對于快速工作非常方便亭珍,但是語法并不令人滿意。對不不同的參數(shù)數(shù)量需要使用不同的宏枝哄,這更像Lisp而不是C++肄梨,::testing::AssertionResult類解決了這個問題。

一個AssertionResult對象代表了一個斷言的結(jié)果(無論是成功還是失敗挠锥,或是相關(guān)的信息)

namespace testing {

// Returns an AssertionResult object to indicate that an assertion has
// succeeded.
AssertionResult AssertionSuccess();

// Returns an AssertionResult object to indicate that an assertion has
// failed.
AssertionResult AssertionFailure();

可以使用<<操作符將信息傳輸?shù)?code>AssertionResult對象众羡。

返回值用AssertionResult對象代替bool值能夠在斷言中提供更多可讀的信息。例如將IsEven()定義如下:

::testing::AssertionResult IsEven(int n) {
  if ((n % 2) == 0)
     return ::testing::AssertionSuccess();
  else
     return ::testing::AssertionFailure() << n << " is odd";
}

替代

bool IsEven(int n) {
  return (n % 2) == 0;
}

那么蓖租,錯誤的斷言EXPECT_TRUE(IsEven(Fib(4)))將會打印

Value of: IsEven(Fib(4))
  Actual: false (3 is odd)
Expected: true

替代原來模糊的結(jié)果

Value of: IsEven(Fib(4))
  Actual: false
Expected: true

使用謂詞格式器

當(dāng)由(ASSERT | EXPECT)_PRED_*(ASSERT | EXPECT)_(TRUE | FALSE)生成的默認(rèn)消息不令人滿意粱侣,或者謂詞的某些參數(shù)不支持將數(shù)據(jù)流傳輸?shù)給stream羊壹,就可以使用下面的謂詞格式器來定制信息的格式。

Fatal assertion Nonfatal assertion Verifies
ASSERT_PRED_FORMAT1(pred_format1, val1); EXPECT_PRED_FORMAT1(pred_format1, val1); pred_format1(val1) is successful
ASSERT_PRED_FORMAT2(pred_format2, val1, val2); EXPECT_PRED_FORMAT2(pred_format2, val1, val2); pred_format2(val1, val2) is successful
... ... ...

與上一組宏之間的區(qū)別在于齐婴,(ASSERT | EXPECT)_PRED_FORMAT *使用謂詞格式化(pred_formatn)來代替謂詞油猫,前者是具有簽名的函數(shù):

::testing::AssertionResult PredicateFormattern(const char* expr1,
                                               const char* expr2,
                                               ...
                                               const char* exprn,
                                               T1 val1,
                                               T2 val2,
                                               ...
                                               Tn valn);

其中val1val2尔店,...和valn是謂詞參數(shù)的值眨攘,而expr1expr2嚣州,...和exprn是它們在源代碼中出現(xiàn)的相應(yīng)變量名。 類型T1共螺,T2该肴,...和Tn可以是值類型或引用類型。 例如藐不,如果參數(shù)的類型為Foo匀哄,則可以將其聲明為Fooconst Foo&

例如雏蛮,改進(jìn)使用EXPECT_PRED2()來測試MutuallyPrime()中的失敗消息:

// Returns the smallest prime common divisor of m and n,
// or 1 when m and n are mutually prime.
int SmallestPrimeCommonDivisor(int m, int n) { ... }

// A predicate-formatter for asserting that two integers are mutually prime.
::testing::AssertionResult AssertMutuallyPrime(const char* m_expr,
                                               const char* n_expr,
                                               int m,
                                               int n) {
  if (MutuallyPrime(m, n)) return ::testing::AssertionSuccess();

  return ::testing::AssertionFailure() << m_expr << " and " << n_expr
      << " (" << m << " and " << n << ") are not mutually prime, "
      << "as they have a common divisor " << SmallestPrimeCommonDivisor(m, n);
}

使用EXPECT_PRED_FORMAT2:

EXPECT_PRED_FORMAT2(AssertMutuallyPrime, b, c);

將會獲得以下信息:

b and c (4 and 10) are not mutually prime, as they have a common divisor 2.

前面介紹的許多內(nèi)置斷言是(EXPECT | ASSERT)_PRED_FORMAT *的特殊情況涎嚼。 實際上,大多數(shù)確實是使用(EXPECT | ASSERT)_PRED_FORMAT *定義的挑秉。

浮點數(shù)比較

比較浮點數(shù)很棘手法梯。由于舍入誤差,兩個浮點將很難完全匹配犀概。因此ASSERT_EQ的比較通常不起作用立哑。由于浮點具有十分廣泛的取值范圍,沒有單個固定的錯誤界限能夠一直有效姻灶。最好以固定的相對誤差范圍進(jìn)行比較铛绰,除了那些接近0的值,因為浮點數(shù)在0附近會丟失精度产喉。

通常捂掰,為了使浮點的比較有意義,用戶需要仔細(xì)選擇誤差范圍曾沈。如果他們不希望或不在乎这嚣,則比較“最后一位”(Units in Last Place)是一個很好的默認(rèn)值,并且googletest提供了斷言來做到這一點晦譬。

浮點宏

Fatal assertion Nonfatal assertion Verifies
ASSERT_FLOAT_EQ(val1, val2); EXPECT_FLOAT_EQ(val1, val2); the two float values are almost equal
ASSERT_DOUBLE_EQ(val1, val2); EXPECT_DOUBLE_EQ(val1, val2); the two double values are almost equal

“幾乎相等”是指這些值彼此之間在4個ULP之內(nèi)疤苹。

以下斷言允許選擇可接受的錯誤范圍:

Fatal assertion Nonfatal assertion Verifies
ASSERT_NEAR(val1, val2, abs_error); EXPECT_NEAR(val1, val2, abs_error); the difference between val1 and val2 doesn't exceed the given absolute error

浮點類型謂詞格式函數(shù)

一些浮點運算很有用,但并不常用敛腌。為了避免出現(xiàn)新的宏卧土,我們將它們作為謂語格式函數(shù)提供惫皱,可在謂詞斷言宏中使用

EXPECT_PRED_FORMAT2(::testing::FloatLE, val1, val2);
EXPECT_PRED_FORMAT2(::testing::DoubleLE, val1, val2);

驗證val1是否小于等于val2。也可以將上表中的EXPECT_PRED_FORMAT2替換為ASSERT_PRED_FORMAT2尤莺。

使用gMock匹配器進(jìn)行斷言

gMock帶有一個匹配器庫旅敷,用于驗證傳遞給模擬對象的參數(shù)。gMock匹配器是知道如何描述自己的基本謂詞颤霎∠彼可以在這些斷言宏中使用gMock匹配器。

Fatal assertion Nonfatal assertion Verifies
ASSERT_THAT(value, matcher); EXPECT_THAT(value, matcher); value matches matcher

例如友酱,StartsWith(prefix)是一個匹配器晴音,它匹配以prefix開頭的字符串,例如:

using ::testing::StartsWith;
...
    // Verifies that Foo() returns a string starting with "Hello".
    EXPECT_THAT(Foo(), StartsWith("Hello"));

gMock具有豐富的匹配器集缔杉。 您可以做很多googletest無法獨自完成的事情锤躁。 有關(guān)gMock提供的匹配器列表,請閱讀此內(nèi)容或详。

gMock與googletest捆綁在一起系羞,因此無需添加任何構(gòu)建依賴項即可利用此優(yōu)勢。只需添加“ testing / base / public / gmock.h”霸琴,就可以開始使用椒振。

更多的字符串?dāng)嘌?/h3>

您可以使用帶有EXPECT THAT()ASSERT THAT()的gMock字符串匹配器來執(zhí)行更多的字符串比較技巧(子字符串、前綴、后綴、正則表達(dá)式等)砰粹。例如:

using ::testing::HasSubstr;
using ::testing::MatchesRegex;
...
  ASSERT_THAT(foo_string, HasSubstr("needle"));
  EXPECT_THAT(bar_string, MatchesRegex("\\w*\\d+"));

如果該字符串包含格式正確的HTML或XML文檔,則可以檢查其DOM樹是否與XPath表達(dá)式匹配:

// Currently still in //template/prototemplate/testing:xpath_matcher
#include "template/prototemplate/testing/xpath_matcher.h"
using prototemplate::testing::MatchesXPath;
EXPECT_THAT(html_string, MatchesXPath("http://a[text()='click here']"));

Windows HRESULT斷言

這些斷言測試HRESULT成功或者失敗

Fatal assertion Nonfatal assertion Verifies
ASSERT_HRESULT_SUCCEEDED(expression) EXPECT_HRESULT_SUCCEEDED(expression) expression is a success HRESULT
ASSERT_HRESULT_FAILED(expression) EXPECT_HRESULT_FAILED(expression) expression is a failure HRESULT

生成的輸出包含與表達(dá)式返回的HRESULT代碼相關(guān)的可讀錯誤消息嗡善。

例如可以這樣使用:

CComPtr<IShellDispatch2> shell;
ASSERT_HRESULT_SUCCEEDED(shell.CoCreateInstance(L"Shell.Application"));
CComVariant empty;
ASSERT_HRESULT_SUCCEEDED(shell->ShellExecute(CComBSTR(url), empty, empty, empty, empty));

類型斷言

可以調(diào)用以下函數(shù):

::testing::StaticAssertTypeEq<T1, T2>();

來判斷T1T2的值是否相同。如果斷言得到滿足学歧,該函數(shù)將不執(zhí)行任何操作罩引。如果類型不同,則函數(shù)調(diào)用將編譯失敗枝笨,編譯器錯誤消息將指出T1和T2不是同一類型袁铐,并且很可能(取決于編譯器)顯示T1T2的實際值。 這主要使用在模板代碼中横浑。

警告:在類模板或函數(shù)模板的成員函數(shù)中使用時剔桨,StaticAssertTypeEq <T1,T2>()僅在實例化函數(shù)時才有效徙融。 例如洒缀,給定:

template <typename T> class Foo {
 public:
  void Bar() { ::testing::StaticAssertTypeEq<int, T>(); }
};

代碼

void Test1() { Foo<bool> foo; }

不會導(dǎo)致編譯錯誤,因為Foo<bool>::Bar()沒有被實例化。相反树绩,以下代碼會導(dǎo)致編譯錯誤:

void Test2() { 
    Foo<bool> foo; 
    foo.Bar();
    }

斷言的位置

可以在任何C ++函數(shù)中使用斷言萨脑,它不一定是測試類中的的方法。但是有一個約束饺饭,生成致命故障的斷言(FAIL*和ASSERT_ *)只能在返回void的函數(shù)中使用渤早。如果將其放在返回值費控的函數(shù)中,將得到一個令人困惑的編譯錯誤瘫俊,例如:"error: void value not ignored as it ought to be"或者"cannot initialize return object of type 'bool' with an rvalue of type 'void'"或者"error: no viable conversion from 'void' to 'string'"鹊杖。

如果需要在一個返回值不是void的函數(shù)中使用致命斷言,可以讓函數(shù)在參數(shù)中返回需要返回的值扛芽。例如骂蓖,將函數(shù)T2 Foo(T1 x)重寫成void Foo(T1 x, T2* result)。這需要確保* result包含一些合理的值胸哥,即使函數(shù)過早的返回也需要確保涯竟。由于函數(shù)現(xiàn)在返回void,因此可以在其中使用任何斷言空厌。

如果不能選擇更改函數(shù)的類型,則應(yīng)僅使用會產(chǎn)生非致命故障的斷言银酬,例如ADD_FAILURE *EXPECT_ *嘲更。

注意:根據(jù)c++語言規(guī)范,構(gòu)造函數(shù)和析構(gòu)函數(shù)不被認(rèn)為是返回值為void的函數(shù)揩瞪,因此不能在它們中使用致命的斷言;如果嘗試赋朦,將得到編譯錯誤。相反李破,要么調(diào)用abort并崩潰整個測試可執(zhí)行文件宠哄,要么將致命的斷言放在SetUp/TearDown函數(shù)中;

警告:當(dāng)構(gòu)造函數(shù)或析構(gòu)函數(shù)調(diào)用一個包含致命斷言的helper函數(shù)(私有的void-returning方法)時,不會終止當(dāng)前的測試嗤攻。它僅僅會過早地從構(gòu)造函數(shù)或析構(gòu)函數(shù)中返回毛嫉,并且可能讓對象處在在一個部分構(gòu)造或部分析構(gòu)的狀態(tài)。所以應(yīng)該使用abort或者SetUp/TearDown函數(shù)代替它妇菱。

教GTEST如何打印你的值

當(dāng)測試斷言(例如EXPECT_EQ)失敗時承粤,googletest會輸出參數(shù)值以幫助進(jìn)行調(diào)試。它使用用戶可擴(kuò)展值打印器執(zhí)行此操作闯团。

該打印器知道如何打印內(nèi)置的C++類型辛臊、原生數(shù)組、STL容器以及任何支持<<操作符的類型房交。對于其他類型彻舰,它將在值中打印原始字節(jié),并希望用戶可以理解它。

如前所述刃唤,打印器是可擴(kuò)展的隔心。這意味著可以教它在打印特定類型方面做的更好,而不是僅打印轉(zhuǎn)儲字節(jié)透揣。為此济炎,需要為你定義的類型定義<<操作符:

#include <ostream>

namespace foo {

class Bar {  // We want googletest to be able to print instances of this.
...
  // Create a free inline friend function.
  friend std::ostream& operator<<(std::ostream& os, const Bar& bar) {
    return os << bar.DebugString();  // whatever needed to print bar to os
  }
};

// If you can't declare the function in the class it's important that the
// << operator is defined in the SAME namespace that defines Bar.  C++'s look-up
// rules rely on that.
std::ostream& operator<<(std::ostream& os, const Bar& bar) {
  return os << bar.DebugString();  // whatever needed to print bar to os
}

}  // namespace foo

有時團(tuán)隊可能會認(rèn)為為Bar設(shè)置<<操作符可能是不好的風(fēng)格,或者Bar可能已經(jīng)具有<<操作符但沒有做想要的事情(并且無法更改它)辐真。如果是這樣须尚,可以改為定義一個PrintTo()函數(shù):

#include <ostream>

namespace foo {

class Bar {
  ...
  friend void PrintTo(const Bar& bar, std::ostream* os) {
    *os << bar.DebugString();  // whatever needed to print bar to os
  }
};

// If you can't declare the function in the class it's important that PrintTo()
// is defined in the SAME namespace that defines Bar.  C++'s look-up rules rely
// on that.
void PrintTo(const Bar& bar, std::ostream* os) {
  *os << bar.DebugString();  // whatever needed to print bar to os
}

}  // namespace foo

如果同時定義了<<PrintTo(),則在使用googletest時將使用后者侍咱。這樣就可以自定義待打印值在googletest輸出中的顯示方式耐床,而不會影響依賴于<<操作符的行為的代碼。

如果您想使用googletest的值打印器自己打印值x楔脯,只需調(diào)用::testing::PrintToString(x)撩轰,它會返回一個std::string

vector<pair<Bar, int> > bar_ints = GetBarIntVector();

EXPECT_TRUE(IsCorrectBarIntVector(bar_ints))
    << "bar_ints = " << ::testing::PrintToString(bar_ints);

死亡測試

在許多應(yīng)用程序中,有一些斷言會在不滿足條件的情況下導(dǎo)致應(yīng)用程序失敗昧廷。這些健全性檢查可確保程序處于已知的良好狀態(tài)堪嫂,并在某些程序狀態(tài)損壞后的最早可能的時間點失敗。如果斷言檢查了錯誤的條件木柬,則程序可能會以錯誤的狀態(tài)運行皆串,這可能導(dǎo)致內(nèi)存損壞,安全漏洞或更糟眉枕。因此恶复,測試此類斷言語句是否按預(yù)期工作至關(guān)重要。

由于這些前提條件檢查會導(dǎo)致進(jìn)程終止速挑,因此我們將此類測試稱為死亡測試谤牡。 更一般而言,任何以預(yù)期方式檢查程序是否終止(除非引發(fā)異常)的測試也都是死亡測試姥宝。

請注意翅萤,如果一段代碼拋出異常,則出于死亡測試的目的伶授,我們不會將其視為“死亡”断序,因為代碼的調(diào)用者可以捕獲該異常并避免崩潰。 如果要驗證代碼引發(fā)的異常糜烹,請參見異常斷言违诗。

如何編寫死亡測試

GTEST提供以下的宏來進(jìn)行死亡測試

Fatal assertion Nonfatal assertion Verifies
ASSERT_DEATH(statement, matcher); EXPECT_DEATH(statement, matcher); statement crashes with the given error
ASSERT_DEATH_IF_SUPPORTED(statement, matcher); EXPECT_DEATH_IF_SUPPORTED(statement, matcher); if death tests are supported, verifies that statement crashes with the given error; otherwise verifies nothing
ASSERT_DEBUG_DEATH(statement, matcher); EXPECT_DEBUG_DEATH(statement, matcher); statement crashes with the given error in debug mode. When not in debug (i.e. NDEBUG is defined), this just executes statement
ASSERT_EXIT(statement, predicate, matcher); EXPECT_EXIT(statement, predicate, matcher); statement exits with the given error, and its exit code matches predicate

其中,statement是預(yù)期會導(dǎo)致進(jìn)程終止的語句疮蹦,predicate是評估整數(shù)退出狀態(tài)的函數(shù)或函數(shù)對象诸迟,matcher是與const std :: string&匹配的gMock marcher或(Perl)正則表達(dá)式 ,兩者都與statementstderr輸出匹配。由于遺留原因阵苇,裸字符串(即沒有匹配器)被解釋為ContainsRegex(str)壁公,而不是Eq(str)。 請注意绅项,statement可以是任何有效的語句(包括復(fù)合語句)紊册,而不必是表達(dá)式。

注意:在宏的描述中使用崩潰(crash)代表進(jìn)程是以一個非零的狀態(tài)碼終結(jié)的快耿。這有兩種可能:一是進(jìn)程調(diào)用了非零值得exit()或者_exit()囊陡,二是被一個信號量殺死了。
這表明了如果statement終結(jié)了一個進(jìn)程并返回0掀亥,EXPECT_DEATH()不認(rèn)為這是一個崩潰撞反。在這種情況下應(yīng)該使用EXPECT_EXIT代替,或者更精確地限制退出代碼搪花。

這里的謂詞(predicate)必須接受一個int返回一個bool遏片。僅僅當(dāng)謂詞返回true時,代表死亡測試成功撮竿。GTEST定義了一些謂詞能夠處理大部分的情況:

::testing::ExitedWithCode(exit_code)

如果程序使用給定的退出碼正常退出吮便,那么將返回true

::testing::KilledBySignal(signal_number)  // Not available on Windows.

如果程序被給定的信號量殺死,就會返回true

* _DEATH宏是* _EXIT的便捷包裝器幢踏,它們使用謂詞來驗證進(jìn)程的退出代碼是否為非零线衫。

注意一個死亡測試只關(guān)注三件事:

  1. statement有沒有終止或者退出進(jìn)程
  2. 對于ASSERT_EXITEXPECT_EXIT退出狀態(tài)是否滿足謂詞? 或者在ASSERT_DEATHEXPECT_DEATH的情況下退出狀態(tài)是否為非零
  3. stderr的輸出是否符合marcher

特別是惑折,如果語句生成ASSERT_ *EXPECT_ *錯誤,則不會導(dǎo)致死亡測試失敗枯跑,因為googletest斷言不會終止該進(jìn)程惨驶。

死亡測試的例子:

TEST(MyDeathTest, Foo) {
  // This death test uses a compound statement.
  ASSERT_DEATH({
    int n = 5;
    Foo(&n);
  }, "Error on line .* of Foo()");
}

TEST(MyDeathTest, NormalExit) {
  EXPECT_EXIT(NormalExit(), ::testing::ExitedWithCode(0), "Success");
}

TEST(MyDeathTest, KillMyself) {
  EXPECT_EXIT(KillMyself(), ::testing::KilledBySignal(SIGKILL),
              "Sending myself unblockable signal");
}

這些測試驗證了:

  • 調(diào)用Foo(5)導(dǎo)致進(jìn)程使用給定的錯誤信息結(jié)束
  • 調(diào)用NormalExit()導(dǎo)致進(jìn)程在stderr上打印Success并且退出碼為0
  • 調(diào)用KillMyself()會用信號量SIGKILL殺死進(jìn)程

死亡測試命名

重要說明:當(dāng)測試包含死亡測試時,如上面的示例所示敛助。強(qiáng)烈建議遵循命名test suite(而非測試)加* DeathTest的約定粗卜, 下面的“死亡測試和線程”部分解釋了原因。

如果一個test fixture類被普通測試和死亡測試所共享纳击,可以使用using或typedef為Fixture類引入別名续扔,并避免重復(fù)其代碼

class FooTest : public ::testing::Test { ... };

using FooDeathTest = FooTest;

TEST_F(FooTest, DoesThis) {
  // normal test
}

TEST_F(FooDeathTest, DoesThat) {
  // death test
}

死亡測試如何工作的

在后臺,ASSERT_EXIT()產(chǎn)生一個新進(jìn)程并在該進(jìn)程中執(zhí)行死亡測試語句焕数。具體發(fā)生情況的詳細(xì)信息取決于平臺和變量::testing::GTEST_FLAG(death_test_style)纱昧。

  • 在POSIX系統(tǒng)上,使用fork()(或Linux上的clone())來生成子子進(jìn)程堡赔,然后
    • 如果變量的值為“ fast”识脆,則將立即執(zhí)行死亡測試語句。
    • 如果變量的值是“threadsafe”,則子進(jìn)程將像第一次調(diào)用一樣重新執(zhí)行單元測試二進(jìn)制文件灼捂,但是會帶有一些額外的標(biāo)志導(dǎo)致只運行這一個死亡測試
  • 在Windows上离例,使用CreateProcess()API生成子對象,然后重新執(zhí)行二進(jìn)制文件并且僅運行該單個死亡測試-就像POSIX上的線程安全模式一樣悉稠。

變量的其他值是非法的宫蛆,將導(dǎo)致死亡測試失敗。 當(dāng)前的猛,該標(biāo)志的默認(rèn)值為“fast”

  1. 子進(jìn)程的退出碼符合謂詞
  2. 子進(jìn)程的stderr輸出符合正則表達(dá)式

如果死亡測試語句運行到完成而沒有死亡耀盗,則子進(jìn)程將終止,并且斷言失敗衰絮。

死亡測試和線程

有兩種死亡測試類型的原因與線程安全性有關(guān)袍冷。由于fork線程時存在的眾所周知的問題,死亡測試應(yīng)該在單線程上下文中運行猫牡。但是胡诗,有時布置這樣的環(huán)境是不可行的。例如淌友,靜態(tài)初始化的模塊可能在到達(dá)main之前啟動線程煌恢。線程一旦創(chuàng)建后,可能很難或無法清理它們震庭。

googletest具有三個功能瑰抵,旨在提高人們對線程問題的認(rèn)識:

  1. 運行死亡測試時,如果有多個線程正在運行器联,則會發(fā)出警告二汛。
  2. 名稱以“ DeathTest”結(jié)尾的Test suites將在所有其他測試之前運行。
  3. 使用clone()而不是fork()來在Linux上生成子進(jìn)程(在Cygwin和Mac上不支持clone())拨拓,因為在父進(jìn)程具有多個線程時肴颊,fork()更有可能導(dǎo)致子進(jìn)程掛起。

在死亡測試語句中創(chuàng)建線程是完全可行的渣磷。它們在單獨的進(jìn)程中執(zhí)行婿着,不會影響父進(jìn)程。

死亡測試類型

引入了“線程安全”死亡測試類型醋界,以幫助減輕可能在多線程環(huán)境中進(jìn)行測試的風(fēng)險竟宋。它以增加測試執(zhí)行時間(可能會如此)為代價,以提高線程安全性形纺。

自動測試框架未設(shè)置樣式標(biāo)志丘侠。可以通過設(shè)置標(biāo)志來選擇一種特定的死亡測試類型:

testing::FLAGS_gtest_death_test_style="threadsafe"

可以在main()中執(zhí)行此操作挡篓,以設(shè)置二進(jìn)制文件或單個測試中所有死亡測試的樣式婉陷。 在每個測試前會保存標(biāo)志并在運行完成后恢復(fù)它帚称,例如:

int main(int argc, char** argv) {
  InitGoogle(argv[0], &argc, &argv, true);
  ::testing::FLAGS_gtest_death_test_style = "fast";
  return RUN_ALL_TESTS();
}

TEST(MyDeathTest, TestOne) {
  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
  // This test is run in the "threadsafe" style:
  ASSERT_DEATH(ThisShouldDie(), "");
}

TEST(MyDeathTest, TestTwo) {
  // This test is run in the "fast" style:
  ASSERT_DEATH(ThisShouldDie(), "");
}

警告

ASSERT_EXIT()statement參數(shù)可以是任何有效的C++語句。如果它通過return語句或引發(fā)異常離開當(dāng)前函數(shù)秽澳,則將死亡測試視為失敗闯睹。某些googletest宏可能會從當(dāng)前函數(shù)中返回(例如ASSERT_TRUE()),因此請確保在statement中避免使用它們担神。

由于statement在子進(jìn)程中運行楼吃,因此它引起的任何內(nèi)存中副作用(例如,修改變量妄讯,釋放內(nèi)存等)在父進(jìn)程中都是無法觀察到的孩锡。特別是,如果在死亡測試中釋放內(nèi)存亥贸,則程序?qū)o法通過堆檢查躬窜,因為父進(jìn)程將永遠(yuǎn)不會看到回收的內(nèi)存。 為了解決這個問題炕置,可以:

  1. 在死亡測試中不釋放內(nèi)存
  2. 在父進(jìn)程中再釋放一遍內(nèi)存
  3. 在程序中不實用堆檢查

由于實現(xiàn)細(xì)節(jié)荣挨,不能在同一行上放置多個死亡測試斷言,否則編譯將失敗朴摊,并顯示明顯的錯誤消息默垄。

盡管“線程安全”類型的死亡測試提供了提高的線程安全性,但是在向pthread_atfork(3)注冊的處理程序存在的情況下甚纲,諸如死鎖之類的線程問題仍然可能出現(xiàn)口锭。

在子例程中使用斷言

向斷言添加跟蹤

如果從多個地方調(diào)用了一個測試子例程,則當(dāng)其中的一個斷言失敗時介杆,很難確定該失敗來自哪個子例程調(diào)用鹃操。可以使用額外的日志記錄或自定義失敗消息來緩解此問題春哨,但這通常會使測試混亂组民。更好的解決方案是使用SCOPED_TRACE宏或ScopedTrace工具:

SCOPED_TRACE(message);
ScopedTrace trace("file_path", line_number, message);

message可以是可流式傳輸?shù)?code>std::ostream的任何內(nèi)容。SCOPED_TRACE宏將使當(dāng)前文件名悲靴,行號和給定的消息被添加到每個失敗消息中。ScopedTrace在參數(shù)中接受顯式的文件名和行號莫其,這對于編寫測試非常有用癞尚。當(dāng)控件離開當(dāng)前詞法范圍時,效果將被撤消乱陡。
例如:

void Sub1(int n) {
  EXPECT_EQ(Bar(n), 1);
  EXPECT_EQ(Bar(n + 1), 2);
}
TEST(FooTest, Bar) {
  {
     SCOPED_TRACE("A");  // This trace point will be included in
                                      // every failure in this scope.
     Sub1(1);
   }
   // Now it won't.
   Sub1(9);
  }

結(jié)果信息如下:

path/to/foo_test.cc:11: Failure
Value of: Bar(n)
Expected: 1
  Actual: 2
Google Test trace:
path/to/foo_test.cc:17: A

path/to/foo_test.cc:12: Failure
Value of: Bar(n + 1)
Expected: 2
  Actual: 3

如果沒有跟蹤浇揩,將很難知道這兩個失敗分別來自Sub1()的調(diào)用。

使用SCOPED_TRACE的一些技巧:

  1. 使用適當(dāng)?shù)?code>message憨颠,并且在子例程的開頭而不是在每個調(diào)用的地方使用SCOPED_TRACE胳徽。
  2. 在循環(huán)內(nèi)調(diào)用子例程時积锅,請在SCOPED_TRACE中將循環(huán)迭代器作為消息的一部分,以便您可以知道失敗來自哪個迭代养盗。
  3. 有時跟蹤點的行號足以標(biāo)識子例程的特定調(diào)用缚陷。在這種情況下,不必為SCOPED_TRACE選擇唯一的消息往核,可以簡單地使用“”箫爷。
  4. 如果外部作用域中已經(jīng)有了一個SCOPED_TRACE,則可以在內(nèi)部作用域中繼續(xù)使用SCOPED_TRACE聂儒。 在這種情況下虎锚,所有活動的跟蹤點都將以遇到宏的相反順序包含在故障消息中。
  5. 跟蹤轉(zhuǎn)儲可以在Emacs中選中行號并return衩婚,就會跳轉(zhuǎn)到源文件中的該行

傳遞致命錯誤

使用ASSERT_ *FAIL *有一個難以理解的常見陷阱窜护,即當(dāng)它們失敗時只會中止當(dāng)前功能,而不是整個測試非春。 例如柱徙,以下測試將發(fā)生段錯誤:

void Subroutine() {
  // Generates a fatal failure and aborts the current function.
  ASSERT_EQ(1, 2);

  // The following won't be executed.
  ...
}

TEST(FooTest, Bar) {
  Subroutine();  // The intended behavior is for the fatal failure
                 // in Subroutine() to abort the entire test.

  // The actual behavior: the function goes on after Subroutine() returns.
  int* p = NULL;
  *p = 3;  // Segfault!
}

為了緩解這種情況,googletest提供了三種不同的解決方案税娜。即拋出異常坐搔、(ASSERT | EXPECT)_NO_FATAL_FAILURE斷言和HasFatalFailure()函數(shù)。 在以下兩個小節(jié)中將對它們進(jìn)行了描述敬矩。

使用一個帶有異常處理的斷言

以下代碼可以將ASSERT-failure變成異常:

class ThrowListener : public testing::EmptyTestEventListener {
  void OnTestPartResult(const testing::TestPartResult& result) override {
    if (result.type() == testing::TestPartResult::kFatalFailure) {
      throw testing::AssertionException(result);
    }
  }
};
int main(int argc, char** argv) {
  ...
  testing::UnitTest::GetInstance()->listeners().Append(new ThrowListener);
  return RUN_ALL_TESTS();
}

如果有其他偵聽器概行,則應(yīng)在其他偵聽器之后添加此偵聽器,否則它們將看不到失敗的OnTestPartResult弧岳。

在子例程中斷言

如上所示凳忙,如果您的測試調(diào)用了一個其中有ASSERT_ *失敗的子例程,則該子例程返回后禽炬,測試將繼續(xù)涧卵。人們通常希望致命的失敗像異常一樣傳播。為此腹尖,googletest提供了以下宏

Fatal assertion Nonfatal assertion Verifies
ASSERT_NO_FATAL_FAILURE(statement); EXPECT_NO_FATAL_FAILURE(statement); statement doesn't generate any new fatal failures in the current thread.

該宏僅僅檢查執(zhí)行斷言的線程中的故障柳恐,來確定此類斷言的結(jié)果。如果statement創(chuàng)建了新線程热幔,那么這些線程中的故障將被忽略乐设。

ASSERT_NO_FATAL_FAILURE(Foo());

int i;
EXPECT_NO_FATAL_FAILURE({
  i = Bar();
});

Windows當(dāng)前不支持來自多個線程的斷言

在當(dāng)前測試中檢測失敗

如果當(dāng)前測試中的斷言遭受致命故障,::testing::Test類中的HasFatalFailure()將返回true。這使函數(shù)可以在子例程中捕獲致命故障并盡早返回绎巨。

class Test {

 public:

  ...

  static bool HasFatalFailure();

};

典型的用法是模擬一個拋出異常的操作:

TEST(FooTest, Bar) {

  Subroutine();

  // Aborts if Subroutine() had a fatal failure.

  if (HasFatalFailure()) return;




  // The following won't be executed.

  ...

}

如果在TEST()近尚,TEST_F()test fixture之外使用HasFatalFailure(),則必須添加::testing::Test::前綴场勤,如下所示:

if (::testing::Test::HasFatalFailure()) return;

類似的戈锻,如果當(dāng)前測試至少有一個非致命故障歼跟,則HasNonfatalFailure()返回true,如果當(dāng)前測試有兩種失敗中的任意一個,則HasFailure()返回true

記錄額外的信息

在測試代碼中楚昭,您可以調(diào)用RecordProperty("key",value)記錄額外的信息叹卷,其中value可以是字符串或intkey記錄的最后一個值將被發(fā)送到指定的XML輸出坪它。 例如:

TEST_F(WidgetUsageTest, MinAndMaxWidgets) {
  RecordProperty("MaximumWidgets", ComputeMaxUsage());
  RecordProperty("MinimumWidgets", ComputeMinUsage());
}

將會輸出XML:

  ...
    <testcase name="MinAndMaxWidgets" status="run" time="0.006" classname="WidgetUsageTest" MaximumWidgets="12" MinimumWidgets="9" />
  ...

注意:

  • RecordProperty()Test類的靜態(tài)成員骤竹。 因此,如果在TEST主體和測試夾具類的外部使用往毡,則必須以::testing::Test::作為前綴蒙揣。
  • key必須是合法的XML屬性名稱,并且不能和GTEST已有的屬性相沖突(name, status, time, classname, type_param, and value_param)开瞭。
  • 允許在測試的生命周期之外調(diào)用RecordProperty()懒震。 如果在測試外部調(diào)用它,但在測試套件的SetUpTestSuite()TearDownTestSuite()方法之間調(diào)用嗤详,它將被歸因于該測試套件的XML元素个扰。 如果在所有測試套件之外(例如在測試環(huán)境中)調(diào)用它,則它將歸因于頂級XML元素葱色。

在相同測試套件中的不同測試之間共享資源

googletest為每個測試創(chuàng)建一個新的test fixture對象递宅,使測試獨立且易于調(diào)試。然而苍狰,有時測試使用的資源昂貴办龄,因此“每個測試一份拷貝”模型的成本過高。

如果測試不更改資源淋昭,那么共享單個資源副本不會有任何危害俐填。 因此,除了按測試設(shè)置Setup/TearDown外翔忽,googletest還支持按testsuit設(shè)置SetUp/TearDown英融。 要使用它:

  1. test fixture類(例如FooTest)中,將一些成員變量聲明為靜態(tài)變量以保存共享資源歇式。

  2. test fixture類之外(通常在它的下面)矢赁,定義那些成員變量,可以選擇性的初始化它們贬丛。

  3. 在同一test fixture類中,定義一個靜態(tài)的void SetUpTestSuite()函數(shù)(請記住不要將它拼寫為帶有小u的SetupTestCase)來配置共享資源给涕,并定義一個靜態(tài)的TearDownTestCase()函數(shù)來清理共享資源豺憔。

GTEST在運行FooTest中第一個測試之前調(diào)用SetUpCase(在創(chuàng)建第一個FooTest對象前)额获,并且在最后一個測試之后(刪除最后一個FooTest對象之后)調(diào)用TearDownCase()。在中間恭应,所有的測試使用共享的資源抄邀。

請記住,測試順序是不確定的昼榛,因此代碼不能依賴于另一個測試境肾。 同樣,測試必須不能修改任何共享資源的狀態(tài)胆屿。如果確實要修改狀態(tài)奥喻,則必須在將控制權(quán)傳遞給下一個測試之前將狀態(tài)恢復(fù)為原始值。

例如:

class FooTest : public ::testing::Test {

 protected:

  // Per-test-suite set-up.

  // Called before the first test in this test suite.

  // Can be omitted if not needed.

  static void SetUpTestCase() {

    shared_resource_ = new ...;

  }




  // Per-test-suite tear-down.

  // Called after the last test in this test suite.

  // Can be omitted if not needed.

  static void TearDownTestCase() {

    delete shared_resource_;

    shared_resource_ = NULL;

  }




  // You can define per-test set-up logic as usual.

  virtual void SetUp() { ... }




  // You can define per-test tear-down logic as usual.

  virtual void TearDown() { ... }




  // Some expensive resource shared by all tests.

  static T* shared_resource_;

};




T* FooTest::shared_resource_ = NULL;




TEST_F(FooTest, Test1) {

  ... you can refer to shared_resource_ here ...

}




TEST_F(FooTest, Test2) {

  ... you can refer to shared_resource_ here ...

}

注意:盡管上面的代碼聲明SetUpTestSuite()protect非迹,但有時可能需要將其聲明為public环鲤,例如與TEST_P一起使用時。

全局SetUp和TearDown

setupteardown可以在測試層面憎兽,測試套件級別上進(jìn)行冷离,同樣也可以在程序級別上進(jìn)行。

首先纯命,定義一個知道如何setupteardown::test::Environment的子類:

class Environment : public ::testing::Environment {

 public:

  ~Environment() override {}




  // Override this to define how to set up the environment.

  void SetUp() override {}




  // Override this to define how to tear down the environment.

  void TearDown() override {}

};

接下來調(diào)用::testing::AddGlobalTestEnvironment()函數(shù)在GTEST中注冊一個Environment的實例西剥。


Environment* AddGlobalTestEnvironment(Environment* env);

當(dāng)調(diào)用RUN_ALL_TESTS()時,首先調(diào)用每個environment的SetUp()函數(shù)亿汞,接下來如果環(huán)境報告沒有報錯且未調(diào)用GTEST_SKIP()瞭空,則繼續(xù)運行測試。RUN_ALL_TEST()總是會調(diào)用每個environment對象的TearDown()函數(shù)留夜,無論測試有沒有運行匙铡。

注冊多個environment對象是可行的,在此套件中碍粥,按他們注冊的順序調(diào)用它們的SetUp()鳖眼,并按相反的順序調(diào)用他們的TearDown()

注意GTEST擁有已注冊環(huán)境對象的所有權(quán)嚼摩。 因此請勿自行刪除它們钦讳。

一般在main()函數(shù)中調(diào)用RUN_ALL_TEST()之前需要先調(diào)用AddGlobalTestEnvironment()。但是如果使用了gtest_main枕面,則需要在main()函數(shù)之前調(diào)用AddGlobalTestEnvironment()愿卒。實現(xiàn)這個的一種方法是定義一個全局變量:


::testing::Environment* const foo_env =

    ::testing::AddGlobalTestEnvironment(new FooEnvironment);

但是,強(qiáng)烈建議編寫自己的main()并在其中調(diào)用AddGlobalTestEnvironment()潮秘,因為依賴全局變量的初始化會使代碼更加難以閱讀琼开,當(dāng)從不同的翻譯單元注冊多個environment時,可能會導(dǎo)致問題枕荞,并且在environment之間也會存在依賴管理依賴關(guān)系(編譯器不保證初始化來自不同翻譯單元的全局變量的順序)柜候。

值參數(shù)化測試

值參數(shù)化測試允許使用不同的參數(shù)測試代碼搞动,而無需編寫同一測試的多個副本。 這在許多情況下很有用渣刷,例如:

  • 有一段代碼鹦肿,其行為受到一個或多個命令行標(biāo)志的影響,想要確保代碼在這些標(biāo)志取各種值的情況下都能正確執(zhí)行辅柴。
  • 要測試面向?qū)ο蟮慕涌诘牟煌瑢崿F(xiàn)箩溃。
  • 想要用不同的輸入測試代碼(數(shù)據(jù)驅(qū)動測試), 這一特性很容易被濫用,請謹(jǐn)慎使用碌嘀。

如何編寫值參數(shù)化測試

為了編寫值參數(shù)化測試涣旨,首先要定義一個testing::Testtesting::WithParamInterface<T>(后者是純接口類)的派生類-fixture類,其中T是參數(shù)類型筏餐。為了便于書寫开泽,可以直接從testing::TestWithParam<T>派生,它是testing::Testtesting::WithParamInterface<T>共同派生出來的一個類魁瞪。T可以是任意可拷貝類型穆律,如果它是一個裸指針,還需要負(fù)責(zé)指針的生命周期导俘。

注意:如果測試定義了SetUpTestCase()TearDownTestCase()峦耘,則必須將它們聲明為public而不是protected,才能使用TEST_P旅薄。

class FooTest :
    public testing::TestWithParam<const char*> {
  // You can implement all the usual fixture class members here.
  // To access the test parameter, call GetParam() from class
  // TestWithParam<T>.
};

// Or, when you want to add parameters to a pre-existing fixture class:
class BaseTest : public testing::Test {
  ...
};
class BarTest : public BaseTest,
                public testing::WithParamInterface<const char*> {
  ...
};

然后辅髓,使用TEST_P宏根據(jù)需要使用此fixture定義盡可能多的測試模式。_P后綴的意思是“參數(shù)化”或“模式”少梁。

TEST_P(FooTest, DoesBlah) {
  // Inside a test, access the test parameter with the GetParam() method
  // of the TestWithParam<T> class:
  EXPECT_TRUE(foo.Blah(GetParam()));
  ...
}

TEST_P(FooTest, HasBlahBlah) {
  ...
}

可以通過INSTANTIATE_TEST_CASE_P洛口,使用任何一組想要的參數(shù)來實例化test suite。googletest定義了許多函數(shù)來生成測試參數(shù)凯沪。它們返回我們所稱的參數(shù)生成器第焰。下面是它們的摘要,這些函數(shù)都在testing命名空間中:

Parameter Generator Behavior
Range(begin, end [, step]) Yields values {begin, begin+step, begin+step+step, ...}. The values do not include end. step defaults to 1.
Values(v1, v2, ..., vN) Yields values {v1, v2, ..., vN}.
ValuesIn(container) and ValuesIn(begin,end) Yields values from a C-style array, an STL-style container, or an iterator range [begin, end)
Bool() Yields sequence {false, true}.
Combine(g1, g2, ..., gN) Yields all combinations (Cartesian product) as std::tuples of the values generated by the N generators.

以下語句將實例化FooTest 測試套件(test suite)中的測試妨马,試參數(shù)參數(shù)值分別為“ meeny”挺举,“ miny”“ moe”

INSTANTIATE_TEST_CASE_P(InstantiationName,
                         FooTest,
                         testing::Values("meeny", "miny", "moe"));            

注意:上面的代碼必須放在全局或命名空間范圍烘跺,而不是函數(shù)中湘纵。

注意:不要忘記這一步! 如果忘記了滤淳,那么測試會默默地通過梧喷,但是其實沒有任何套件運行!

目前正在進(jìn)行一些工作,以使GoogleTestVerification測試套件能夠發(fā)現(xiàn)省略了INSTANTIATE_TEST_SUITE_P铺敌,并將該行為標(biāo)記為一個錯誤绊困。 如果有一個測試套件遺漏了INSTANTIATE_TEST_SUITE_P但不用視作一個錯誤,例如适刀,它位于可能由于其他原因而鏈接到的庫中,或者測試用例列表是動態(tài)的并且可能為空煤蹭,那么可以通過以下方式來標(biāo)記測試套件以取消此檢查:

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(FooTest);

為了區(qū)分該模式的不同實例笔喉,INSTANTIATE_TEST_SUITE_P的第一個參數(shù)將作為一個前綴添加到實際的測試套件名稱中。 請記住為不同的實例選擇唯一的前綴硝皂。 上面實例化的測試將具有以下名稱:

  • InstantiationName/FooTest.DoesBlah/0 for "meeny"
  • InstantiationName/FooTest.DoesBlah/1 for "miny"
  • InstantiationName/FooTest.DoesBlah/2 for "moe"
  • InstantiationName/FooTest.HasBlahBlah/0 for "meeny"
  • InstantiationName/FooTest.HasBlahBlah/1 for "miny"
  • InstantiationName/FooTest.HasBlahBlah/2 for "moe"

這些名稱可以在--gtest_filter中使用

下面的語句將再次實例化FooTest的所有測試常挚,每個測試的參數(shù)值分別為“ cat”“ dog”

const char* pets[] = {"cat", "dog"};
INSTANTIATE_TEST_SUITE_P(AnotherInstantiationName, FooTest,
                         testing::ValuesIn(pets));
                         

上面的命名實例化的測試名稱如下

  • AnotherInstantiationName/FooTest.DoesBlah/0 for "cat"
  • AnotherInstantiationName/FooTest.DoesBlah/1 for "dog"
  • AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat"
  • AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog"

請注意,INSTANTIATE_TEST_CASE_P將實例化給定測試套件中的所有測試稽物,無論它們的定義在INSTANTIATE_TEST_CASE_P語句之前還是之后奄毡。

創(chuàng)建值參數(shù)化抽象測試

之前,我們在同一源文件中定義并實例化FooTest贝或。 有時可能需要在庫中定義值參數(shù)化的測試吼过,然后讓別人實例化它們, 這種模式稱為抽象測試咪奖。 作為其使用的一個示例盗忱,在設(shè)計接口時,可以編寫標(biāo)準(zhǔn)的抽象測試套件(也許使用工廠函數(shù)作為測試參數(shù))羊赵,期望該接口的所有實現(xiàn)都能通過趟佃。 當(dāng)其他人實現(xiàn)這個接口時,他們可以實例化你的套件以獲得所有接口一致性測試昧捷。

應(yīng)該按如下形式組織代碼來定義抽象測試:

  1. 將定義參數(shù)化測試fixture的類定義在一個頭文件里闲昭,如foo_param_test.h,把這個看做是對抽象測試的聲明靡挥。
  2. TEST_P定義放在foo_param_test.cc中序矩,其中引用頭文件foo_param_test.h, 將此視為實現(xiàn)抽象測試芹血。

定義它們之后贮泞,可以通過引用foo_param_test.h頭文件,調(diào)用INSTANTIATE_TEST_SUITE_P()函數(shù)幔烛,并且依賴包含foo_param_test.cc的目標(biāo)庫來實例化它們啃擦。 這樣就可以在不同的源文件中多次實例化同一抽象測試套件。

為值參數(shù)化的測試參數(shù)指定名稱

INSTANTIATE_TEST_SUITE_P()的最后一個可選參數(shù)允許使用者定制一個能夠基于測試參數(shù)生成的測試名后綴的函數(shù)饿悬。該函數(shù)應(yīng)接受一個類型為testing::TestParamInfo <class ParamType>的參數(shù)令蛉,并返回std :: string

testing::PrintToStringParamName是一個內(nèi)置的測試名后綴生成器,它返回testing::PrintToString(GetParam())的值珠叔。它不適用于std::string或C字符串蝎宇。

注意:測試名稱必須是非空的,唯一的祷安,并且只能包含ASCII字母數(shù)字字符姥芥。特別是,它們不應(yīng)包含下劃線

class MyTestSuite : public testing::TestWithParam<int> {};

TEST_P(MyTestSuite, MyTest)
{
  std::cout << "Example Test Param: " << GetParam() << std::endl;
}

INSTANTIATE_TEST_SUITE_P(MyGroup, MyTestSuite, testing::Range(0, 10),
                         testing::PrintToStringParamName());

提供自定義函數(shù)可以更好地控制測試參數(shù)名稱的生成汇鞭,尤其是對于自動轉(zhuǎn)換不會生成有用的參數(shù)名稱的類型(例如凉唐,如上所述的字符串)。下面的示例說明了多個參數(shù)霍骄,枚舉類型和字符串的情況台囱,并演示了如何組合生成器。為了簡潔起見读整,它使用lambda表達(dá)式:

enum class MyType { MY_FOO = 0, MY_BAR = 1 };

class MyTestSuite : public testing::TestWithParam<std::tuple<MyType, string>> {
};

INSTANTIATE_TEST_SUITE_P(
    MyGroup, MyTestSuite,
    testing::Combine(
        testing::Values(MyType::VALUE_0, MyType::VALUE_1),
        testing::ValuesIn("", "")),
    [](const testing::TestParamInfo<MyTestSuite::ParamType>& info) {
      string name = absl::StrCat(
          std::get<0>(info.param) == MY_FOO ? "Foo" : "Bar", "_",
          std::get<1>(info.param));
      absl::c_replace_if(name, [](char c) { return !std::isalnum(c); }, '_');
      return name;
    });

類型測試

假設(shè)現(xiàn)在具有同一個接口的多個實現(xiàn)簿训,并且想要確保它們都滿足一些共同的要求∶准洌或者强品,可能已經(jīng)定義了幾種應(yīng)該符合相同“概念”的類型,并且想要對其進(jìn)行驗證车伞。在兩種情況下择懂,都希望針對不同類型重復(fù)相同的測試邏輯。

雖然可以為每種要測試的類型編寫一個TESTTEST_F(甚至可以將測試邏輯分解為從TEST調(diào)用的功能模板中)另玖,但這種操作很繁瑣且無法擴(kuò)展困曙,如果要對n種類型進(jìn)行m個測試,最終將編寫m * n個TEST谦去。

類型化測試允許在一系列的類型上重復(fù)相同的測試邏輯慷丽。盡管編寫類型測試時必須知道類型列表,但只需要編寫一次測試邏輯鳄哭。 下面是操作方式:

首先要糊,定義一個從::testing::Test派生的fixture類模板,應(yīng)當(dāng)通過某種類型對其進(jìn)行參數(shù)化:

template <typename T>

class FooTest : public ::testing::Test {

 public:

  ...

  typedef std::list<T> List;

  static T shared_;

  T value_;

};

接下來妆丘,將類型列表與測試套件相關(guān)聯(lián)锄俄,將對該列表中的每種類型重復(fù)此操作:


using MyTypes = ::testing::Types<char, int, unsigned int>;

TYPED_TEST_SUITE(FooTest, MyTypes);

類型別名(usingtypedef)是TYPED_TEST_SUITE宏正確解析所必需的。否則勺拣,編譯器會認(rèn)為類型列表中的每個逗號都會引入一個新的宏參數(shù)奶赠。

然后,使用TYPED_TEST()代替TEST_F()為該測試套件定義類型測試药有。 可以根據(jù)需要重復(fù)多次:

TYPED_TEST(FooTest, DoesBlah) {

  // Inside a test, refer to the special name TypeParam to get the type

  // parameter.  Since we are inside a derived class template, C++ requires

  // us to visit the members of FooTest via 'this'.
  

  TypeParam n = this->value_;


  // To visit static members of the fixture, add the 'TestFixture::'

  // prefix.

  n += TestFixture::shared_;


  // To refer to typedefs in the fixture, add the 'typename TestFixture::'

  // prefix.  The 'typename' is required to satisfy the compiler.

  typename TestFixture::List values;


  values.push_back(n);

  ...

}


TYPED_TEST(FooTest, HasPropertyA) { ... }

類型參數(shù)化測試

類型參數(shù)化測試與類型測試類似毅戈,不同之處在于它們不需要提前知道類型列表苹丸。相反,可以先定義測試邏輯苇经,然后再使用不同的類型列表實例化它赘理,甚至可以在同一程序中多次實例化它。

如果要設(shè)計接口或概念扇单,則可以定義一組類型參數(shù)化的測試商模,以驗證接口/概念的任何有效實現(xiàn)應(yīng)具有的屬性。 然后蜘澜,每個實現(xiàn)的作者都可以使用其類型實例化測試套件以驗證其是否符合要求阻桅,而不必重復(fù)編寫類似的測試。 例如:

首先兼都,定義一個像類型測試那樣的fixture類模板。

template <typename T>

class FooTest : public ::testing::Test {

  ...

};

然后聲明將定義一個類型參數(shù)化的測試套件

TYPED_TEST_SUITE_P(FooTest);

然后稽寒,使用TYPED_TEST_P()定義類型參數(shù)化的測試扮碧。 可以根據(jù)需要重復(fù)多次:

TYPED_TEST_P(FooTest, DoesBlah) {

  // Inside a test, refer to TypeParam to get the type parameter.

  TypeParam n = 0;

  ...

}


TYPED_TEST_P(FooTest, HasPropertyA) { ... }

現(xiàn)在,棘手的部分是杏糙,需要使用REGISTER_TYPED_TEST_SUITE_P宏注冊所有測試模式慎王,然后才能實例化它們。 宏的第一個參數(shù)是測試套件名稱, 其余的是此測試套件中測試的名稱:

REGISTER_TYPED_TEST_SUITE_P(FooTest,

                            DoesBlah, HasPropertyA);

最后宏侍,您可以隨意用所需的類型實例化模式赖淤。 如果將上面的代碼放在頭文件中,則可以#include將其包含在多個C ++源文件中谅河,并將其實例化多次咱旱。

typedef ::testing::Types<char, int, unsigned int> MyTypes;

INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes);

為了區(qū)分模式的不同實例,INSTANTIATE_TYPED_TEST_SUITE_P宏的第一個參數(shù)是一個前綴,它將添加到實際的測試套件名稱中绷耍。 請記住為不同的實例選擇唯一的前綴吐限。

在類型列表僅包含一種類型的特殊情況下,您可以直接編寫該類型褂始,而無需::testing::Types <...>诸典,如下所示:

INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, int);

測試private代碼

如果更改軟件的內(nèi)部實現(xiàn),只要用戶看不到更改崎苗,測試就不會中斷狐粱。因此,根據(jù)黑盒測試原則胆数,大多數(shù)時候應(yīng)該通過其公共接口來測試代碼肌蜻。

如果仍然需要測試內(nèi)部實現(xiàn)代碼,請考慮是否有更好的設(shè)計幅慌。需要測試內(nèi)部實現(xiàn)通常表明該類做了太多事情宋欺。考慮提取一個實現(xiàn)類,并對其進(jìn)行測試齿诞,然后在原始類中使用該實現(xiàn)類酸休。

如果一定要測試非公共接口代碼的話,例如以下兩種情況:

  • 靜態(tài)函數(shù)(和靜態(tài)成員函數(shù)不同)或者沒有命名的命名空間
  • private或者protected的類成員

可以使用獨特的方法來測試他們:

  • 未命名名稱空間中的靜態(tài)函數(shù)和定義/聲明僅在同一編譯單元中可見祷杈。要對其進(jìn)行測試斑司,可以將需要測試的.cc文件#include* _test.cc文件中。(#include .cc文件不是重用代碼的好方法但汞,不應(yīng)該在生產(chǎn)代碼中這樣做K薰巍)
  • 更好的方法是將私有代碼移到foo::internal命名空間中,其中foo是項目通常使用的命名空間私蕾,并將私有聲明放入* -internal.h文件中僵缺。編寫的.cc文件和測試允許包含此內(nèi)部標(biāo)頭,但客戶端則不允許踩叭。這樣磕潮,就可以完全測試內(nèi)部實現(xiàn),而不會泄漏給客戶容贝。
  • 私有類成員只能在類內(nèi)部或由友元訪問自脯。 要訪問類的私有成員,可以將test fixture聲明為類的友元斤富,并在fixture中定義訪問器膏潮。然后,使用test fixture進(jìn)行的測試就可以通過fixture中的訪問器訪問生產(chǎn)類的私有成員满力。請注意焕参,即使fixture是生產(chǎn)類的友元,測試也不會自動成為生產(chǎn)類的友元油额,因為它們是在fixture的子類中定義的龟糕。
  • 測試私有成員的另一種方法是將它們重構(gòu)為實現(xiàn)類,然后在* -internal.h文件中對其進(jìn)行聲明悔耘〗菜辏客戶端不允許包含此標(biāo)頭,但是測試代碼可以衬以。這就是所謂的Pimpl(私有實現(xiàn))習(xí)慣用法缓艳。

或者可以生產(chǎn)類中添加以下代碼,聲明一個獨立的測試作為被測試類的友元:

FRIEND_TEST(TestSuiteName, TestName);

例如:

// foo.h
class Foo {
  ...
 private:
  FRIEND_TEST(FooTest, BarReturnsZeroOnNull);

  int Bar(void* x);
};

// foo_test.cc
...
TEST(FooTest, BarReturnsZeroOnNull) {
  Foo foo;
  EXPECT_EQ(foo.Bar(NULL), 0);  // Uses Foo's private member Bar().
}

當(dāng)測試類在命名空間中定義時要特別注意:如果希望它們成為生產(chǎn)類的友元看峻,則應(yīng)該在生產(chǎn)類的同一名稱空間中定義test fixture和測試阶淘。 例如,如果要測試的代碼如下所示:

namespace my_namespace {

class Foo {
  friend class FooTest;
  FRIEND_TEST(FooTest, Bar);
  FRIEND_TEST(FooTest, Baz);
  ... definition of the class Foo ...
};

}  // namespace my_namespace

那么測試代碼應(yīng)該如下:

namespace my_namespace {

class FooTest : public ::testing::Test {
 protected:
  ...
};

TEST_F(FooTest, Bar) { ... }
TEST_F(FooTest, Baz) { ... }

}  // namespace my_namespace

捕獲失敗

如果要在googletest之上構(gòu)建測試工具集互妓,那么應(yīng)該使用什么框架測試這個測試工具集溪窒?當(dāng)然是 googletest坤塞。

所需要面臨的挑戰(zhàn)是驗證改測試工具集是否能夠正確報告故障。在通過引發(fā)異常報告失敗的框架中澈蚌,可以捕獲異常并對其進(jìn)行斷言摹芙。 但是googletest不使用異常,那么應(yīng)該如何測試一段代碼是否會產(chǎn)生預(yù)期的失斖鹈椤浮禾?

gunit-spi.h包含一些用于執(zhí)行此操作的結(jié)構(gòu)。在#include該頭文件之后份汗,就可以使用:

EXPECT_FATAL_FAILURE(statement, substring);

來斷言該包含給定substring的消息的statement在當(dāng)前線程中是否產(chǎn)生了致命錯誤(例如ASSERT_ *)盈电,或使用:

EXPECT_NONFATAL_FAILURE(statement, substring);

來斷言非致命錯誤。

上述宏僅檢查當(dāng)前線程中的失敗杯活,以確定這類期望的結(jié)果卧蜓。 如果statement創(chuàng)建新線程胞此,這些線程中的故障也將被忽略肝劲。如果您還想捕獲其他線程中的故障醉者,請改用以下宏之一:

  EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substring);
  EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substring);

注意:Windows暫時不支持來自多個線程的斷言

從技術(shù)上來說皂岔,還有以下警告:

  1. 這些宏不能使用流導(dǎo)入失敗信息
  2. EXPECT_FATAL_FAILURE{_ON_ALL_THREADS}()中的statement不能引用非靜態(tài)變量或者this對象的非靜態(tài)成員
  3. EXPECT_FATAL_FAILURE{_ON_ALL_THREADS}()中的statement不能返回值晶府。

程序化的注冊測試

TEST宏可處理絕大多數(shù)的測試用例椒袍,但是也會有極少數(shù)情況下需要運行時注冊邏輯坛吁。 對于這些情況摩幔,框架提供::testing::RegisterTest來允許調(diào)用者動態(tài)注冊任意測試彤委。

這是僅在TEST宏不足時才使用的高級API。 應(yīng)該盡可能首選宏或衡,因為它們避免了調(diào)用此函數(shù)的大部分復(fù)雜性焦影。

例如:

template <typename Factory>
TestInfo* RegisterTest(const char* test_suite_name, const char* test_name,
                       const char* type_param, const char* value_param,
                       const char* file, int line, Factory factory);

factory參數(shù)是可調(diào)用的(可移動構(gòu)造的)factory對象或函數(shù)指針,它創(chuàng)建Test對象的新實例封断。它處理呼叫者的所有權(quán)斯辰。 可調(diào)用對象的簽名為Fixture *(),其中Fixture是測試的test fixture類坡疼。所有的測試使用相同test_suite_name注冊彬呻,并且必須返回相同的fixture類型。 這會在運行時進(jìn)行檢查柄瑰。

該框架將從factory推斷出fixture類闸氮,并將為此調(diào)用SetUpTestSuiteTearDownTestSuite

必須在調(diào)用RUN_ALL_TESTS()之前調(diào)用::testing::RegisterTest教沾,否則行為是不確定的蒲跨。

例如:

class MyFixture : public ::testing::Test {
 public:
  // All of these optional, just like in regular macro usage.
  static void SetUpTestSuite() { ... }
  static void TearDownTestSuite() { ... }
  void SetUp() override { ... }
  void TearDown() override { ... }
};

class MyTest : public MyFixture {
 public:
  explicit MyTest(int data) : data_(data) {}
  void TestBody() override { ... }

 private:
  int data_;
};

void RegisterMyTests(const std::vector<int>& values) {
  for (int v : values) {
    ::testing::RegisterTest(
        "MyFixture", ("Test" + std::to_string(v)).c_str(), nullptr,
        std::to_string(v).c_str(),
        __FILE__, __LINE__,
        // Important to use the fixture type as the return type here.
        [=]() -> MyFixture* { return new MyTest(v); });
  }
}
...
int main(int argc, char** argv) {
  std::vector<int> values_to_test = LoadValuesFromConfig();
  RegisterMyTests(values_to_test);
  ...
  return RUN_ALL_TESTS();
}

獲取當(dāng)前的測試名

有時某個功能可能需要知道當(dāng)前正在運行的測試的名稱。 例如授翻,可能使用test fixture的SetUp()方法來根據(jù)正在運行的測試來設(shè)置最佳文件名或悲。::testing::TestInfo類具有以下信息:

namespace testing {

class TestInfo {
 public:
  // Returns the test suite name and the test name, respectively.
  //
  // Do NOT delete or free the return value - it's managed by the
  // TestInfo class.
  const char* test_suite_name() const;
  const char* name() const;
};

}

要獲取當(dāng)前正在運行的測試的TestInfo對象孙咪,請對UnitTest單例對象調(diào)用current_test_info()

// Gets information about the currently running test.
  // Do NOT delete the returned object - it's managed by the UnitTest class.
  const ::testing::TestInfo* const test_info =
    ::testing::UnitTest::GetInstance()->current_test_info();

  printf("We are in test %s of test suite %s.\n",
         test_info->name(),
         test_info->test_suite_name());      

如果沒有測試在運行,current_test_info()返回一個空指針巡语。 特別是翎蹈,不能從TestSuiteSetUp()TestSuiteTearDown()(可以隱式知道測試套件名稱)或他們調(diào)用的函數(shù)中獲取測試套件名稱捌臊。

通過處理測試事件來擴(kuò)展googletest

googletest提供了事件監(jiān)聽器API杨蛋,可讓接收有關(guān)測試程序進(jìn)度和測試失敗的通知±砼欤可以監(jiān)聽的事件包括測試程序逞力,測試套件或測試方法的開始和結(jié)束。 可以使用此API來擴(kuò)充或替換標(biāo)準(zhǔn)控制臺輸出糠爬,替換XML輸出或提供完全不同形式的輸出寇荧,例如GUI或數(shù)據(jù)庫。還可以將測試事件用作檢查點以實現(xiàn)資源泄漏檢查器执隧。

定義事件監(jiān)聽器

要定義事件監(jiān)聽器揩抡,可以將testing :: TestEventListenertesting :: EmptyTestEventListener子類化。前者是一個(抽象的)接口镀琉,可以覆override每個純虛擬方法來處理測試事件(例如峦嗤,測試開始時, OnTestStart()方法將被調(diào)用屋摔。) 后者提供了接??口中所有方法的空實現(xiàn)烁设,因此子類只需要override其關(guān)心的方法。

觸發(fā)事件時钓试,其上下文作為參數(shù)傳遞給處理程序函數(shù)装黑。 將使用以下參數(shù)類型:

  • UnitTest反映整個測試程序的狀態(tài)
  • TestSuite具有有關(guān)測試套件的信息,其中可以包含一個或多個測試
  • TestInfo包含一個測試的狀態(tài)
  • TestPartResult表示測試斷言的結(jié)果

事件處理程序函數(shù)可以檢查其接收到的參數(shù)弓熏,以找到有關(guān)事件和測試程序狀態(tài)的令人感興趣的信息恋谭。

例如:

class MinimalistPrinter : public ::testing::EmptyTestEventListener {
    // Called before a test starts.
    virtual void OnTestStart(const ::testing::TestInfo& test_info) {
      printf("*** Test %s.%s starting.\n",
             test_info.test_suite_name(), test_info.name());
    }

    // Called after a failed assertion or a SUCCESS().
    virtual void OnTestPartResult(const ::testing::TestPartResult& test_part_result) {
      printf("%s in %s:%d\n%s\n",
             test_part_result.failed() ? "*** Failure" : "Success",
             test_part_result.file_name(),
             test_part_result.line_number(),
             test_part_result.summary());
    }

    // Called after a test ends.
    virtual void OnTestEnd(const ::testing::TestInfo& test_info) {
      printf("*** Test %s.%s ending.\n",
             test_info.test_suite_name(), test_info.name());
    }
  };

使用事件監(jiān)聽器

main()函數(shù)調(diào)用RUN_ALL_TESTS()前,在GTEST事件監(jiān)聽器列表中添加定義的監(jiān)聽器的實例化來使用它挽鞠。

int main(int argc, char** argv) {
  ::testing::InitGoogleTest(&argc, argv);
  // Gets hold of the event listener list.
  ::testing::TestEventListeners& listeners =
        ::testing::UnitTest::GetInstance()->listeners();
  // Adds a listener to the end.  googletest takes the ownership.
  listeners.Append(new MinimalistPrinter);
  return RUN_ALL_TESTS();
}

僅有一個問題:默認(rèn)的測試結(jié)果打印器仍然有效疚颊,因此定義的輸出將于原始的輸出混合在一起。 要取消顯示默認(rèn)打印器信认,只需將其從事件偵聽器列表中釋放并刪除即可串稀。可以通過添加一行代碼來做到這一點:

  delete listeners.Release(listeners.default_result_printer());
  listeners.Append(new MinimalistPrinter);
  return RUN_ALL_TESTS();

現(xiàn)在狮杨,坐下來享受與默認(rèn)的測試完全不同的輸出母截。

可以在列表中追加一個以上的偵聽器。當(dāng)觸發(fā)On * Start()OnTestPartResult()事件時橄教,偵聽器將按照它們在列表中出現(xiàn)的順序來接收事件(因為新的偵聽器被添加到列表的末尾清寇,默認(rèn)的文本打印器和默認(rèn)的XML生成器將首先收到事件)喘漏。偵聽器將以相反的順序接收On * End()事件。

生成失敗監(jiān)聽器

處理事件時华烟,可以使用引發(fā)故障的宏(EXPECT _ *()翩迈,ASSERT _ *()FAIL()等)盔夜。但他們有一些限制:

  1. 無法在OnTestPartResult()中產(chǎn)生任何故障(否則將導(dǎo)致遞歸調(diào)用OnTestPartResult()
  2. 不允許處理OnTestPartResult()的偵聽器生成任何故障负饲。

將偵聽器添加到偵聽器列表時,應(yīng)該將處理OnTestPartResult()的偵聽器放在可能產(chǎn)生故障的偵聽器之前喂链。這樣可以確保后者產(chǎn)生的故障歸因于前者的正確測試返十。

運行測試程序:更多選項

googletest測試程序是普通的可執(zhí)行文件。構(gòu)建完成后椭微,可以直接運行它們洞坑,并通過以下環(huán)境變量和/或命令行標(biāo)志影響它們的行為。為了使這些標(biāo)志起作用蝇率,您的程序必須在調(diào)用RUN_ALL_TESTS()之前調(diào)用::testing::InitGoogleTest()迟杂。

要查看受支持的標(biāo)志及其用法的列表,請使用--help標(biāo)志運行測試程序本慕。 也可以使用-h排拷,-?或/锅尘?作為縮寫监氢。

如果同時由環(huán)境變量和標(biāo)志指定選項,則后者優(yōu)先鉴象。

選擇測試

列出測試名字

有時,有必要在運行它們之前在程序中列出可用的測試何鸡,以便在需要時可以應(yīng)用過濾器纺弊。包含標(biāo)志--gtest_list_tests會覆蓋所有其他標(biāo)志,并以以下格式列出測試:

TestSuite1.
  TestName1
  TestName2
TestSuite2.
  TestName

如果提供了該標(biāo)志骡男,則列出的測試均不會實際運行淆游, 此標(biāo)志沒有相應(yīng)的環(huán)境變量。

運行測試的子集

默認(rèn)情況下隔盛,googletest程序會運行用戶定義的所有測試犹菱。有些情況下,只想運行一部分測試(例如用于調(diào)試或快速驗證更改)吮炕。如果將GTEST_FILTER環(huán)境變量或--gtest_filter標(biāo)志設(shè)置為過濾器字符串腊脱,則googletest將僅運行其名稱(以TestSuiteName.TestName的形式)與過濾器匹配的測試。

過濾器的格式是由':'分隔的通配符模式列表(稱為正模式)龙亲,可以選擇后跟“-”和另一個由“:”分隔的模式列表(稱為負(fù)模式)陕凹。當(dāng)且僅當(dāng)測試匹配任何正模式但不匹配任何負(fù)模式時悍抑,測試才與過濾器匹配。

模式可以包含“ *”(與任何字符串匹配)或“杜耙?”(匹配任何單個字符)搜骡。為方便起見,過濾器“ * -NegativePatterns”也可以寫為“ -NegativePatterns”佑女。

例如:

  • ./foo_test 沒有標(biāo)志记靡,所以運行所有的測試
  • ./foo_test --gtest_filter=* 也運行所有測試,因為他匹配所有東西
  • ./foo_test --gtest_filter=FooTest.*運行FooTest測試套件里的所有測試
  • ./foo_test --gtest_filter=*Null*:*Constructor*運行名字里有NULL或者Constructor的測試
  • ./foo_test --gtest_filter=-*DeathTest.*運行所有非死亡測試
  • ./foo_test --gtest_filter=FooTest.*-FooTest.Bar運行測試套件FooTest中除了FooTest.Bar的其他測試
  • ./foo_test --gtest_filter=FooTest.*:BarTest.*-FooTest.Bar:BarTest.Foo運行測試套件FooTest中除了FooTest.Bar的測試以及測試套件BarTest中除了BarTest.foo的其他測試

短暫禁用測試

如果有無法立即修復(fù)的損壞測試团驱,則可以在其名稱中添加DISABLED_前綴,這會將其從執(zhí)行中排除摸吠。這比注釋掉代碼或使用#if 0更好,因為被禁用的測試仍然會被編譯店茶。

如果需要禁用測試套件中的所有測試蜕便,則可以將DISABLED_添加到每個測試名稱的前面,或者將其添加到測試套件名稱的前面贩幻。

例如轿腺,下面的測試不會運行但是仍然會被編譯:

// Tests that Foo does Abc.
TEST(FooTest, DISABLED_DoesAbc) { ... }

class DISABLED_BarTest : public ::testing::Test { ... };

// Tests that Bar does Xyz.
TEST_F(DISABLED_BarTest, DoesXyz) { ... }

注意:此功能僅應(yīng)用于暫時性的禁止測試。之后仍然需要修復(fù)已禁用的測試丛楚。如果測試程序包含任何禁用的測試族壳,則googletest會打印一條橫幅來提醒你。

提示:您可以輕松使用gsearch和/或grep來獲取禁用的測試的數(shù)量趣些,此數(shù)字可用作提高測試質(zhì)量的指標(biāo)仿荆。

暫時運行不可運行的測試

要將禁用的測試包括在測試執(zhí)行中,只需使用--gtest_also_run_disabled_tests標(biāo)志調(diào)用測試程序或?qū)?code>GTEST_ALSO_RUN_DISABLED_TESTS環(huán)境變量設(shè)置為非0的值即可坏平。您可以將其與--gtest_filter標(biāo)志結(jié)合使用拢操,以進(jìn)一步選擇要運行的禁用測試 。

重復(fù)測試

偶爾舶替,會遇到測試結(jié)果是不確定值的測試令境。也許它只會在1%的時間內(nèi)失敗,這使得在調(diào)試器中重現(xiàn)該錯誤變得相當(dāng)困難顾瞪。這可能是造成挫敗感的主要原因舔庶。

--gtest_repeat標(biāo)志允許多次重復(fù)程序中的所有(或選定的)測試方法。希望不穩(wěn)定的測試最終會失敗陈醒,并提供調(diào)試的機(jī)會惕橙。 使用方法如下:

$ foo_test --gtest_repeat=1000
Repeat foo_test 1000 times and don't stop at failures.

$ foo_test --gtest_repeat=-1
A negative count means repeating forever.

$ foo_test --gtest_repeat=1000 --gtest_break_on_failure
Repeat foo_test 1000 times, stopping at the first failure.  This
is especially useful when running under a debugger: when the test
fails, it will drop into the debugger and you can then inspect
variables and stacks.

$ foo_test --gtest_repeat=1000 --gtest_filter=FooBar.*
Repeat the tests whose name matches the filter 1000 times.

如果測試程序包含global set-up/tear-down代碼,則在每次迭代中也會重復(fù)執(zhí)行钉跷,因為其中可能會出現(xiàn)脆弱性弥鹦。還可以通過設(shè)置GTEST_REPEAT環(huán)境變量來指定重復(fù)計數(shù)。

亂序測試

可以指定--gtest_shuffle標(biāo)志(或?qū)?code>GTEST_SHUFFLE環(huán)境變量設(shè)置為1)以按隨機(jī)順序在程序中運行測試爷辙。這有助于揭示測試之間的不良依存關(guān)系

默認(rèn)情況下惶凝,googletest使用當(dāng)前時間當(dāng)做計算隨機(jī)數(shù)的種子吼虎,因此每次都會得到不同的序列〔韵剩控制臺輸出包括隨機(jī)種子值思灰,以便以后可以重現(xiàn)與測試序列相關(guān)的失敗。如果要明確指定隨機(jī)種子混滔,請使用--gtest_random_seed = SEED標(biāo)志(或設(shè)置GTEST_RANDOM_SEED環(huán)境變量)洒疚,其中SEED是[0,99999]范圍內(nèi)的整數(shù)坯屿。種子值0很特殊:它告訴googletest執(zhí)行從當(dāng)前時間獲得種子的默認(rèn)行為油湖。

如果將其與--gtest_repeat = N結(jié)合使用,則googletest在每次迭代中選擇其他隨機(jī)種子领跛,并重新隨機(jī)排列測試乏德。

控制測試的輸出

給終端輸出標(biāo)記顏色

GTEST可以使用顏色標(biāo)記終端輸出,讓用戶更容易發(fā)現(xiàn)重要信息吠昭。

您可以將GTEST_COLOR環(huán)境變量或--gtest_color命令行標(biāo)志設(shè)置為yes喊括,noauto(默認(rèn)設(shè)置)來啟用顏色,禁用顏色或讓googletest決定矢棚。 當(dāng)值為auto時郑什,當(dāng)且僅當(dāng)輸出到終端并且TERM環(huán)境變量設(shè)置為xtermxterm-color時,googletest才會使用顏色蒲肋。

不顯示運行時間

默認(rèn)情況下蘑拯,googletest會顯示運行每個測試所需的時間。 要禁用它兜粘,使用--gtest_print_time = 0命令行標(biāo)志運行測試程序申窘,或者將GTEST_PRINT_TIME環(huán)境變量設(shè)置為0

不輸出UTF-8文本

如果斷言失敗孔轴,則googletest會以十六進(jìn)制編碼的字符串以及可讀的UTF-8文本(如果其中包含有效的非ASCII UTF-8字符)的形式輸出string類型的預(yù)期和實際值剃法。如果由于沒有兼容UTF-8的輸出媒體而不想顯示UTF-8文本,請使用--gtest_print_utf8 = 0來運行測試程序距糖,或者將環(huán)境變量GTEST_PRINT_UTF8設(shè)置為0玄窝。

生成XML報告

googletest除了可以正常輸出文本外牵寺,還可以輸出詳細(xì)的XML報告悍引。該報告包含每個測試的持續(xù)時間,可以幫助確定慢速測試帽氓。儀表板還使用該報告顯示每個測試方法的錯誤消息趣斤。

要生成XML報告,請將GTEST_OUTPUT環(huán)境變量或--gtest_output標(biāo)志黎休,設(shè)置為字符串“ xml:path_to_output_file”浓领,這將在給定位置創(chuàng)建文件玉凯。 也可以只使用字符串“ xml”,在這種情況下联贩,將輸出到當(dāng)前目錄的test_detail.xml漫仆。

如果指定一個目錄(例如,在Linux上為“ xml:output / directory /”或在Windows上為“ xml:output \ directory \”)泪幌,則googletest將在該目錄中創(chuàng)建XML文件盲厌,該文件以測試可執(zhí)行文件命名(例如測試程序foo_testfoo_test.exe的結(jié)果為foo_test.xml)。 如果文件已經(jīng)存在(可能是上次運行遺留下來的文件)祸泪,則googletest將選擇其他名稱(例如foo_test_1.xml)以避免覆蓋它吗浩。

該報告是基于junitreport Ant任務(wù)。由于該格式最初是用于Java的没隘,因此需要進(jìn)行一些解釋才能使其適用于googletest測試懂扼,如下所示:

<testsuites name="AllTests" ...>
  <testsuite name="test_case_name" ...>
    <testcase    name="test_name" ...>
      <failure message="..."/>
      <failure message="..."/>
      <failure message="..."/>
    </testcase>
  </testsuite>
</testsuites>
  • 根節(jié)點<testsuites>代表整個測試程序
  • <testsuite>元素代表googletest的測試套件
  • <testcase>元素代表googletest的測試函數(shù)

下面的程序:

TEST(MathTest, Addition) { ... }
TEST(MathTest, Subtraction) { ... }
TEST(LogicTest, NonContradiction) { ... }

將生成如下報告:

<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="3" failures="1" errors="0" time="0.035" timestamp="2011-10-31T18:52:42" name="AllTests">
  <testsuite name="MathTest" tests="2" failures="1" errors="0" time="0.015">
    <testcase name="Addition" status="run" time="0.007" classname="">
      <failure message="Value of: add(1, 1)&#x0A;  Actual: 3&#x0A;Expected: 2" type="">...</failure>
      <failure message="Value of: add(1, -1)&#x0A;  Actual: 1&#x0A;Expected: 0" type="">...</failure>
    </testcase>
    <testcase name="Subtraction" status="run" time="0.005" classname="">
    </testcase>
  </testsuite>
  <testsuite name="LogicTest" tests="1" failures="0" errors="0" time="0.005">
    <testcase name="NonContradiction" status="run" time="0.005" classname="">
    </testcase>
  </testsuite>
</testsuites>

需要注意的事項:

  • <testsuites><testsuite>元素的test屬性告訴googletest程序或測試套件包含多少個測試函數(shù),而failures屬性告訴他們其中有多少失敗右蒲。
  • time屬性以秒為單位表示測試阀湿、測試套件或整個測試程序的持續(xù)時間。
  • timestamp屬性記錄測試執(zhí)行的本地日期和時間品嚣。
  • 每個<failure>元素對應(yīng)一個失敗的googletest斷言炕倘。

生成JSON報告

googletest還可以生成JSON報告作為XML的另一種格式。要生成JSON報告翰撑,請將GTEST_OUTPUT環(huán)境變量或--gtest_output標(biāo)志罩旋,設(shè)置為字符串“ json:path_to_output_file”,GTEST將在給定位置創(chuàng)建文件眶诈。 也可以只使用字符串“ json”涨醋,在這種情況下,可以在當(dāng)前目錄的test_detail.json文件中找到輸出逝撬。

JSON報告形式如下:

{
  "$schema": "http://json-schema.org/schema#",
  "type": "object",
  "definitions": {
    "TestCase": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "tests": { "type": "integer" },
        "failures": { "type": "integer" },
        "disabled": { "type": "integer" },
        "time": { "type": "string" },
        "testsuite": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/TestInfo"
          }
        }
      }
    },
    "TestInfo": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "status": {
          "type": "string",
          "enum": ["RUN", "NOTRUN"]
        },
        "time": { "type": "string" },
        "classname": { "type": "string" },
        "failures": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Failure"
          }
        }
      }
    },
    "Failure": {
      "type": "object",
      "properties": {
        "failures": { "type": "string" },
        "type": { "type": "string" }
      }
    }
  },
  "properties": {
    "tests": { "type": "integer" },
    "failures": { "type": "integer" },
    "disabled": { "type": "integer" },
    "errors": { "type": "integer" },
    "timestamp": {
      "type": "string",
      "format": "date-time"
    },
    "time": { "type": "string" },
    "name": { "type": "string" },
    "testsuites": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/TestCase"
      }
    }
  }
}

該報告使用符合以下JSON編碼的Proto3的格式

syntax = "proto3";

package googletest;

import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";

message UnitTest {
  int32 tests = 1;
  int32 failures = 2;
  int32 disabled = 3;
  int32 errors = 4;
  google.protobuf.Timestamp timestamp = 5;
  google.protobuf.Duration time = 6;
  string name = 7;
  repeated TestCase testsuites = 8;
}

message TestCase {
  string name = 1;
  int32 tests = 2;
  int32 failures = 3;
  int32 disabled = 4;
  int32 errors = 5;
  google.protobuf.Duration time = 6;
  repeated TestInfo testsuite = 7;
}

message TestInfo {
  string name = 1;
  enum Status {
    RUN = 0;
    NOTRUN = 1;
  }
  Status status = 2;
  google.protobuf.Duration time = 3;
  string classname = 4;
  message Failure {
    string failures = 1;
    string type = 2;
  }
  repeated Failure failures = 5;
}

例如浴骂,以下程序:

TEST(MathTest, Addition) { ... }
TEST(MathTest, Subtraction) { ... }
TEST(LogicTest, NonContradiction) { ... }

將有以下輸出:

{
  "tests": 3,
  "failures": 1,
  "errors": 0,
  "time": "0.035s",
  "timestamp": "2011-10-31T18:52:42Z",
  "name": "AllTests",
  "testsuites": [
    {
      "name": "MathTest",
      "tests": 2,
      "failures": 1,
      "errors": 0,
      "time": "0.015s",
      "testsuite": [
        {
          "name": "Addition",
          "status": "RUN",
          "time": "0.007s",
          "classname": "",
          "failures": [
            {
              "message": "Value of: add(1, 1)\n  Actual: 3\nExpected: 2",
              "type": ""
            },
            {
              "message": "Value of: add(1, -1)\n  Actual: 1\nExpected: 0",
              "type": ""
            }
          ]
        },
        {
          "name": "Subtraction",
          "status": "RUN",
          "time": "0.005s",
          "classname": ""
        }
      ]
    },
    {
      "name": "LogicTest",
      "tests": 1,
      "failures": 0,
      "errors": 0,
      "time": "0.005s",
      "testsuite": [
        {
          "name": "NonContradiction",
          "status": "RUN",
          "time": "0.005s",
          "classname": ""
        }
      ]
    }
  ]
}

注意:JSON文檔的確切格式可能會更改。

控制如何報告失敗

將斷言失敗轉(zhuǎn)變?yōu)閿帱c

在debugger下運行測試程序時宪潮,如果調(diào)試器可以非常方便的捕獲斷言失敗并自動進(jìn)入交互模式溯警。googletest的break-on-failure模式支持此行為

要啟用它,需要將GTEST_BREAK_ON_FAILURE環(huán)境變量設(shè)置為非0的值狡相√萸幔或者可以使用--gtest_break_on_failure命令行標(biāo)志。

禁用捕獲拋出的測試異常

無論是否啟用例外尽棕,都可以使用googletest喳挑。如果測試拋出C ++異常或(在Windows上)結(jié)構(gòu)化異常(SEH),默認(rèn)情況下googletest會捕獲該異常伊诵,將其報告為測試失敗单绑,然后繼續(xù)執(zhí)行下一個測試方法。這使得可以盡可能多的運行測試曹宴。另外搂橙,在Windows上,未捕獲的異常將導(dǎo)致彈出窗口笛坦,因此捕獲異撤菅酰可以自動運行測試。

在調(diào)試測試失敗時弯屈,可能希望調(diào)試器處理異常蜗帜,以便可以在引發(fā)異常時檢查調(diào)用堆棧。 為此资厉,請將GTEST_CATCH_EXCEPTIONS環(huán)境變量設(shè)置為0厅缺,或在運行測試時使用--gtest_catch_exceptions = 0標(biāo)志。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宴偿,一起剝皮案震驚了整個濱河市湘捎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌窄刘,老刑警劉巖窥妇,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異娩践,居然都是意外死亡活翩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門翻伺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來材泄,“玉大人,你說我怎么就攤上這事吨岭±冢” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵辣辫,是天一觀的道長旦事。 經(jīng)常有香客問我,道長急灭,這世上最難降的妖魔是什么姐浮? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮化戳,結(jié)果婚禮上单料,老公的妹妹穿的比我還像新娘。我一直安慰自己点楼,他們只是感情好扫尖,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著掠廓,像睡著了一般换怖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蟀瞧,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天沉颂,我揣著相機(jī)與錄音,去河邊找鬼悦污。 笑死铸屉,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的切端。 我是一名探鬼主播彻坛,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼踏枣!你這毒婦竟也來了昌屉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤茵瀑,失蹤者是張志新(化名)和其女友劉穎间驮,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體马昨,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡竞帽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鸿捧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抢呆。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖笛谦,靈堂內(nèi)的尸體忽然破棺而出抱虐,到底是詐尸還是另有隱情,我是刑警寧澤饥脑,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布恳邀,位于F島的核電站,受9級特大地震影響灶轰,放射性物質(zhì)發(fā)生泄漏谣沸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一笋颤、第九天 我趴在偏房一處隱蔽的房頂上張望乳附。 院中可真熱鬧内地,春花似錦、人聲如沸赋除。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽举农。三九已至荆针,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間颁糟,已是汗流浹背航背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留棱貌,地道東北人玖媚。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像婚脱,于是被迫代替她去往敵國和親最盅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345