Google Test(GTest)使用方法和源码解析——自定义输出技术的分析和应用
? ? ? ? 在介紹自定義輸出機(jī)制之前,我們先了解下AssertResult類型函數(shù)。(轉(zhuǎn)載請指明出于breaksoftware的csdn博客)
在函數(shù)中使用AssertionResult
? ? ? ??AssertionResult只有兩種類型:
- AssertionSuccess()
- AssertionFailure()
? ? ? ? 要么成功,要么失敗,我們就可以使用基礎(chǔ)斷言來判斷
::testing::AssertionResult IsEven(int n) {if ((n % 2) == 0)return ::testing::AssertionSuccess() << n << " is even";elsereturn ::testing::AssertionFailure() << n << " is odd";
}TEST(TestAssertResult, Check) {EXPECT_FALSE(IsEven(0));EXPECT_TRUE(IsEven(1));
}
? ? ? ? 我們在IsEven函數(shù)中輸出了額外的內(nèi)容(Actual中),便于我們之后查看結(jié)果
Value of: IsEven(0)Actual: true (0 is even)
Expected: false
error: Value of: IsEven(1)Actual: false (1 is odd)
Expected: true
自定義輸出斷言
? ? ? ? 如果默認(rèn)的輸出結(jié)果不能滿足我們的需要,或者我們的類型不支持字符流輸出,我們就需要自定義輸出。我們可以使用
| 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 |
| ... | ... | ... |
? ? ? ? 這一系列GTest也是有5對函數(shù),最高是ASSERT/EXPECT_PRED_FORMAT5。我們看下使用的例子
::testing::AssertionResult IsEven2(const char* expr, int n) {if ((n % 2) == 0)return ::testing::AssertionSuccess() << expr << " = " << n << " is even";elsereturn ::testing::AssertionFailure() << expr << " = " << n << " is odd";
}TEST(TestAssertResult, Check2) {int a = 0;int b = 1;EXPECT_PRED_FORMAT1(IsEven2, a);EXPECT_PRED_FORMAT1(IsEven2, b);
}
? ? ? ? 我們發(fā)現(xiàn),用于判斷的表達(dá)式要求返回類型是AssertionResult。因為源碼底層是
#define GTEST_ASSERT_(expression, on_failure) \GTEST_AMBIGUOUS_ELSE_BLOCKER_ \if (const ::testing::AssertionResult gtest_ar = (expression)) \; \else \on_failure(gtest_ar.failure_message())
? ? ? ? 其次要求用于判斷的表達(dá)式第一個參數(shù)要是一個const char*類型數(shù)據(jù),它用于傳遞參數(shù)的名字。于是上面的測試輸出是
error: b = 1 is odd
自定義類型輸出
? ? ? ? 一些情況下,我們自定義類型可能是個復(fù)雜的符合結(jié)構(gòu)。C++編譯器并不知道怎么輸出它,這個時候我們就需要告訴GTest如何去輸出了。目前有兩種方式
定義輸出運(yùn)算符函數(shù)
? ? ? ? 比如待測類是class Bar。我們只要定義一個方法
::std::ostream& operator<<(::std::ostream& os, const Bar& bar) {return os << bar.DebugString(); // whatever needed to print bar to os
}
? ? ? ? 通過Bar暴露出來的方法將該對象輸出。我們看一個例子
#include <vector>
#include <string>
using namespace std;
class Bar {class Data {public:Data() {strData = "17";intData = 11;}public:std::string strData;int intData;};
public :std::string DebugString() const {std::string output = "Bar.Data.strData = ";output += data.strData;output += "\t";output += "Bar.Data.intData = ";char intBuffer[16] = {0};itoa(data.intData, intBuffer, 10);output += string(intBuffer);return output;}Data data;
};::std::ostream& operator<<(::std::ostream& os, const Bar& bar) {return os << bar.DebugString(); // whatever needed to print bar to os
}bool IsCorrectBarIntVector(vector<pair<Bar, int> > bar_ints) {return false;
}TEST(TestSelfDefineOutput, Test1) {vector<pair<Bar, int> > bar_ints;Bar bar;bar_ints.push_back(pair<Bar, int>(bar, 1));EXPECT_TRUE(IsCorrectBarIntVector(bar_ints))<< "bar_ints = " << ::testing::PrintToString(bar_ints);
}
? ? ? ? 我們將Bar設(shè)計為一個較為復(fù)雜的結(jié)構(gòu),然后定義了一個函數(shù)DebugString用于輸出其包含的變量。我們讓斷言進(jìn)入出錯狀態(tài),查看其輸出
Actual: false
Expected: true
bar_ints = { (Bar.Data.strData = 17 Bar.Data.intData = 11, 1) }
? ? ? ? 可以看出來,GTest將Vector類型的數(shù)據(jù)格式化輸出(使用了PrintToString方法),并使用我們自定義DebugString輸出了自定義結(jié)構(gòu)。
? ? ? ? 這兒有個有趣的地方,PrintToString的實現(xiàn),比如它是如何判斷它是個容器的
template <typename T>
void PrintTo(const T& value, ::std::ostream* os) {DefaultPrintTo(IsContainerTest<T>(0), is_pointer<T>(), value, os);
}
typedef int IsContainer;
template <class C>
IsContainer IsContainerTest(int /* dummy */,typename C::iterator* /* it */ = NULL,typename C::const_iterator* /* const_it */ = NULL) {return 0;
}typedef char IsNotContainer;
template <class C>
IsNotContainer IsContainerTest(long /* dummy */) { return '\0'; }
? ? ? ? 編譯器遇到這種情況時,會試著用返回IsContainer的方法去匹配方法,但是如何發(fā)現(xiàn)class C沒有迭代器,則用返回IsNotContaner的函數(shù)取匹配。這樣就可以區(qū)分模板類是否是容器了。
? ? ? ? 還有個一is_pointer模板方法,用于判斷是否是指針。
template <typename T>
struct is_pointer : public false_type {};template <typename T>
struct is_pointer<T*> : public true_type {};
? ? ? ? 在我們的測試?yán)又?#xff0c;由于數(shù)據(jù)是個容器,且不是指針。那么將會匹配到
template <typename C>
void DefaultPrintTo(IsContainer /* dummy */,false_type /* is not a pointer */,const C& container, ::std::ostream* os) {
? ? ? ? 其實DefaultPrintTo方法還有其他兩個,只是本次沒有匹配到
template <typename T>
void DefaultPrintTo(IsNotContainer /* dummy */,true_type /* is a pointer */,T* p, ::std::ostream* os) {
template <typename T>
void DefaultPrintTo(IsNotContainer /* dummy */,false_type /* is not a pointer */,const T& value, ::std::ostream* os) {
定義PrintTo方法
? ? ? ? 有些時候,輸出運(yùn)算符可能被其他業(yè)務(wù)邏輯占用了。GTest就提供了一個針對性的方法,定義PrintTo方法,我們可以這么去做
void PrintTo(const Bar& bar, ::std::ostream* os) {*os << bar.DebugString(); // whatever needed to print bar to os
}
? ? ? ? 那么它在什么時候被調(diào)用的呢?PrintToString最終會調(diào)到如下的函數(shù)中,
template <typename C>
void DefaultPrintTo(IsContainer /* dummy */,false_type /* is not a pointer */,const C& container, ::std::ostream* os) {
......for (typename C::const_iterator it = container.begin();it != container.end(); ++it, ++count) {
......internal::UniversalPrint(*it, os);}
......
}
template <typename T1, typename T2>
void PrintTo(const ::std::pair<T1, T2>& value, ::std::ostream* os) {*os << '(';// We cannot use UniversalPrint(value.first, os) here, as T1 may be// a reference type. The same for printing value.second.UniversalPrinter<T1>::Print(value.first, os);*os << ", ";UniversalPrinter<T2>::Print(value.second, os);*os << ')';
}
template <typename T>
class UniversalPrinter {public:GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180)static void Print(const T& value, ::std::ostream* os) {PrintTo(value, os);}GTEST_DISABLE_MSC_WARNINGS_POP_()
};
? ? ? ? 其中UniversalPrinter<T1>::Print(value.first, os)會被我們定義的PrintTo匹配到,從而被調(diào)用。
總結(jié)
以上是生活随笔為你收集整理的Google Test(GTest)使用方法和源码解析——自定义输出技术的分析和应用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Google Test(GTest)使用
- 下一篇: Google Test(GTest)使用