[C++]初識google test--單元測試神器
初識google test
gtest是google的一個開源專案,專門用來做單元測試的。學習難度不算非常的大,適用於多個平臺。主要就是使用斷言來判斷程式碼的正確性。
在google test這個github中可以下載gtest的原始碼,並且在檔案中有make資料夾,可以用terminal,make產生可執行檔案,實際上在makefile裡面寫出了編譯成可執行檔案所需要的程式碼。可以開啟這個檔案改變其中的檔名引數,來編譯不同的程式碼。閱讀sample,也可以對gtest有大概的瞭解。
以下是我簡單翻譯的github上的知識介紹。
為什麼選擇gtest?
- 測試應該是獨立的和可重複的。gtest通過以不同的對像來執行他們,從而分割不同的測試。
- 測試應該被很好的組織和反映測試程式碼的結構。
- 測試應該是可重用的和可移用的,能夠用於不同的平臺(作業系統)。
- 當測試失敗時,提供足夠多的資訊。
- gtest可以使作者專心於測試的內容而不用在意其他的東西。
- 測試應該是迅速的。
基本概念
gtest通過斷言(assertions)去測試程式碼的行為。一個test case可以有多個test,所以應該根據測試的框架去編寫test。當多個tests在一個test case需要公用物件和子程式時,應該把他們放在test fixture 類中。
Assertion(斷言)
gtest是通過斷言來判定程式碼的行為的,如果斷言失敗了,gtest會輸出斷言的原始檔和所在的行號。gtest提供兩種版本的斷言。
ASSERT_* 產生致命錯誤,直接導致函式終止。
EXPECT_* 產生非致命錯誤,函式繼續執行。
因為ASSERT_失敗時會直接導致程式終止,所以有可能直接掉過 clean-up的程式碼,從而導致記憶體洩露,所以需要格外的注意。
可以使用<<定值錯誤資訊:
ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";
for (int i = 0; i < x.size(); ++i) {
EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}
任何可以被ostream接受的型別,都可以被定值為錯誤資訊。
基本斷言
這些斷言是用來剪短的判斷正誤的。
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_TRUE( condition) ; |
EXPECT_TRUE( condition) ; |
condition is true |
ASSERT_FALSE( condition) ; |
EXPECT_FALSE( condition) ; |
condition is false |
適用於:Linux, Windows, Mac.
二元比較
這些斷言適用於比較兩個數的。
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_EQ( val1, val2); |
EXPECT_EQ( val1, val2); |
val1 == val2 |
ASSERT_NE( val1, val2); |
EXPECT_NE( val1, val2); |
val1 != val2 |
ASSERT_LT( val1, val2); |
EXPECT_LT( val1, val2); |
val1 < val2 |
ASSERT_LE( val1, val2); |
EXPECT_LE( val1, val2); |
val1 <= val2 |
ASSERT_GT( val1, val2); |
EXPECT_GT( val1, val2); |
val1 > val2 |
ASSERT_GE( val1, val2); |
EXPECT_GE( val1, val2); |
val1 >= val2 |
如果斷言錯誤,則gtest會輸出兩個val。
兩個引數一定都是可比較的,否則會出現編譯錯誤。
值得注意的是,如果比較的是兩個const char*,不要使用ASSERT_EQ(),因為他們會比較兩個指標指向的地址是否是一樣的,而不是比較他們的值。所以應該使用ASSERT_STREQ(),後面會提到。特別是,如果要比較null,應該這麼使用ASSERT_STREQ(NULL, c_string)(因為NULL在巨集定義中認為是0,在c++11中給出nullptr,可以解決這個問題。)。然而如果是string,則應該使用ASSERT_EQ()。
字串比較
這裡指的string其實是c string(char *)。
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_STREQ( str1, str2); |
EXPECT_STREQ( str1, _str_2); |
the two C strings have the same content |
ASSERT_STRNE( str1, str2); |
EXPECT_STRNE( str1, str2); |
the two C strings have different content |
ASSERT_STRCASEEQ( str1, str2); |
EXPECT_STRCASEEQ( str1, str2); |
the two C strings have the same content, ignoring case |
ASSERT_STRCASENE( str1, str2); |
EXPECT_STRCASENE( str1, str2); |
the two C strings have different content, ignoring case |
注意到‘CASE’斷言中意味著忽略case。
實際上他們都接受wchar_t。如果wchar_t比較失敗了,他們的值會被輸出為UTF-8的原始字串。
一個NULL指標和空字串是不一樣的!
簡單例子
TEST(test_case_name, test_name) {
... test body ...
}
TEST接受兩個引數,第一個是case的名字,第二個是test的名字。注意他們都必須是C++的識別符號,並且不能含有下劃線。一個test的完整名字包括他的case和自己的名字。
我們舉個簡單的例子:
int Factorial(int n); // Returns the factorial of n
它的測試程式碼可能像這樣:
// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(1, Factorial(0));
}
// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(6, Factorial(3));
EXPECT_EQ(40320, Factorial(8));
}
這個測試中有一個case FactorialTest
, 以及兩個test,HandlesZeroInput
和 HandlesPositiveInput
。他們測試輸出時會一起給出資訊。從而體現的測試程式碼的結構。
Test Fixture
如果你發現寫出兩個或多個測試他們都會操作於相似的資料時,你可以使用test fixture。
基本步驟為:
- 繼承於
::testing::Test
。用protected:
或則public:
開始,由此他們才可以訪問fixture的物件。
- 繼承於
- 宣告變數。
- 如果需要的話,寫出SetUp()作為建構函式,TearDown()作為解構函式。
- 用TEST_F()來替換TEST()。
#ifndef GTEST_SAMPLES_SAMPLE3_INL_H_
#define GTEST_SAMPLES_SAMPLE3_INL_H_
#include <stddef.h>
// Queue is a simple queue implemented as a singled-linked list.
//
// The element type must support copy constructor.
template <typename E> // E is the element type
class Queue;
// QueueNode is a node in a Queue, which consists of an element of
// type E and a pointer to the next node.
template <typename E> // E is the element type
class QueueNode {
friend class Queue<E>;
public:
// Gets the element in this node.
const E& element() const { return element_; }
// Gets the next node in the queue.
QueueNode* next() { return next_; }
const QueueNode* next() const { return next_; }
private:
// Creates a node with a given element value. The next pointer is
// set to NULL.
explicit QueueNode(const E& an_element) : element_(an_element), next_(NULL) {}
// We disable the default assignment operator and copy c'tor.
const QueueNode& operator = (const QueueNode&);
QueueNode(const QueueNode&);
E element_;
QueueNode* next_;
};
template <typename E> // E is the element type.
class Queue {
public:
// Creates an empty queue.
Queue() : head_(NULL), last_(NULL), size_(0) {}
// D'tor. Clears the queue.
~Queue() { Clear(); }
// Clears the queue.
void Clear() {
if (size_ > 0) {
// 1. Deletes every node.
QueueNode<E>* node = head_;
QueueNode<E>* next = node->next();
for (; ;) {
delete node;
node = next;
if (node == NULL) break;
next = node->next();
}
// 2. Resets the member variables.
head_ = last_ = NULL;
size_ = 0;
}
}
// Gets the number of elements.
size_t Size() const { return size_; }
// Gets the first element of the queue, or NULL if the queue is empty.
QueueNode<E>* Head() { return head_; }
const QueueNode<E>* Head() const { return head_; }
// Gets the last element of the queue, or NULL if the queue is empty.
QueueNode<E>* Last() { return last_; }
const QueueNode<E>* Last() const { return last_; }
// Adds an element to the end of the queue. A copy of the element is
// created using the copy constructor, and then stored in the queue.
// Changes made to the element in the queue doesn't affect the source
// object, and vice versa.
void Enqueue(const E& element) {
QueueNode<E>* new_node = new QueueNode<E>(element);
if (size_ == 0) {
head_ = last_ = new_node;
size_ = 1;
} else {
last_->next_ = new_node;
last_ = new_node;
size_++;
}
}
// Removes the head of the queue and returns it. Returns NULL if
// the queue is empty.
E* Dequeue() {
if (size_ == 0) {
return NULL;
}
const QueueNode<E>* const old_head = head_;
head_ = head_->next_;
size_--;
if (size_ == 0) {
last_ = NULL;
}
E* element = new E(old_head->element());
delete old_head;
return element;
}
// Applies a function/functor on each element of the queue, and
// returns the result in a new queue. The original queue is not
// affected.
template <typename F>
Queue* Map(F function) const {
Queue* new_queue = new Queue();
for (const QueueNode<E>* node = head_; node != NULL; node = node->next_) {
new_queue->Enqueue(function(node->element()));
}
return new_queue;
}
private:
QueueNode<E>* head_; // The first node of the queue.
QueueNode<E>* last_; // The last node of the queue.
size_t size_; // The number of elements in the queue.
// We disallow copying a queue.
Queue(const Queue&);
const Queue& operator = (const Queue&);
};
#endif // GTEST_SAMPLES_SAMPLE3_INL_H_
#include "sample3.h"
#include "gtest/gtest.h"
// To use a test fixture, derive a class from testing::Test.
class QueueTest : public testing::Test {
protected: // You should make the members protected s.t. they can be
// accessed from sub-classes.
// virtual void SetUp() will be called before each test is run. You
// should define it if you need to initialize the varaibles.
// Otherwise, this can be skipped.
virtual void SetUp() {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// virtual void TearDown() will be called after each test is run.
// You should define it if there is cleanup work to do. Otherwise,
// you don't have to provide it.
//
// virtual void TearDown() {
// }
// A helper function that some test uses.
static int Double(int n) {
return 2*n;
}
// A helper function for testing Queue::Map().
void MapTester(const Queue<int> * q) {
// Creates a new queue, where each element is twice as big as the
// corresponding one in q.
const Queue<int> * const new_q = q->Map(Double);
// Verifies that the new queue has the same size as q.
ASSERT_EQ(q->Size(), new_q->Size());
// Verifies the relationship between the elements of the two queues.
for ( const QueueNode<int> * n1 = q->Head(), * n2 = new_q->Head();
n1 != NULL; n1 = n1->next(), n2 = n2->next() ) {
EXPECT_EQ(2 * n1->element(), n2->element());
}
delete new_q;
}
// Declares the variables your tests want to use.
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};
// When you have a test fixture, you define a test using TEST_F
// instead of TEST.
// Tests the default c'tor.
TEST_F(QueueTest, DefaultConstructor) {
// You can access data in the test fixture here.
EXPECT_EQ(0u, q0_.Size());
}
// Tests Dequeue().
TEST_F(QueueTest, Dequeue) {
int * n = q0_.Dequeue();
EXPECT_TRUE(n == NULL);
n = q1_.Dequeue();
ASSERT_TRUE(n != NULL);
EXPECT_EQ(1, *n);
EXPECT_EQ(0u, q1_.Size());
delete n;
n = q2_.Dequeue();
ASSERT_TRUE(n != NULL);
EXPECT_EQ(2, *n);
EXPECT_EQ(1u, q2_.Size());
delete n;
}
// Tests the Queue::Map() function.
TEST_F(QueueTest, Map) {
MapTester(&q0_);
MapTester(&q1_);
MapTester(&q2_);
}
主函式寫法
在主函式中使用RUN_ALL_TESTS()。
#include "this/package/foo.h"
#include "gtest/gtest.h"
namespace {
// The fixture for testing class Foo.
class FooTest : public ::testing::Test {
protected:
// You can remove any or all of the following functions if its body
// is empty.
FooTest() {
// You can do set-up work for each test here.
}
virtual ~FooTest() {
// You can do clean-up work that doesn't throw exceptions here.
}
// If the constructor and destructor are not enough for setting up
// and cleaning up each test, you can define the following methods:
virtual void SetUp() {
// Code here will be called immediately after the constructor (right
// before each test).
}
virtual void TearDown() {
// Code here will be called immediately after each test (right
// before the destructor).
}
// Objects declared here can be used by all tests in the test case for Foo.
};
// Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
const string input_filepath = "this/package/testdata/myinputfile.dat";
const string output_filepath = "this/package/testdata/myoutputfile.dat";
Foo f;
EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
}
// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
// Exercises the Xyz feature of Foo.
}
} // namespace
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}