之前对google的开源库gtest进行过介绍,现在看那篇博文,感觉有些没有说清楚,这里再进行总结下:
Google Test是Google的开源C++单元测试框架,简称gtest。它的License是New BSD License,可以商用。它是跨平台的,不仅可以应用在各PC端(Windows、Linux、Mac),也可以应用在各移动端(Android、iOS)。目前最新的稳定版为1.8.0,可从 https://github.com/google/googletest/releases 下载源码。
在Windows下编译gtest source code比较简单,因为源码中在googletest/msvc目录下已经有gtest.sln工程,直接双击打开即可。
单元测试(Unit Test)是对软件基本组成单元(如函数或是一个类的方法)进行的测试,是开发者编写的一小段代码,用于检验被测试代码一个很小的、很明确的功能是否正确。
在gtest中,一个测试用例(test case)可以包含一个或多个测试。一个测试程序可以包含多个测试用例。
一、断言(Assertions)
gtest中,断言是用以检查条件是否为真,用以验证测试code的行为。一个断言的结果包括成功(success)、非致命失败(nonfatal failure)、致命失败(fatal failure)三种。断言是宏,类似于函数调用。
断言可以分为两类,一类是ASSERT_*系列,一类是EXPECT_*系列,它们的区别:EXPECT_*版本的断言失败时产生非致命失败,但不会中止当前函数。ASSERT_*版本的断言失败时会产生致命失败,并结束当前函数,当前函数中ASSERT_*后面的语句将不会执行。
ASSERT_*常常被用于后续测试逻辑强制依赖的处理结果的断言,如创建对象后检查指针是否为空,若为空,则后续对象方法调用会失败;而EXPECT_*则用于即使失败也不会影响后续测试逻辑的处理结果的断言,如某个方法返回结果的多个属性的检查。
ASSERT_*(expected,actual)/EXPECT_EQ(expected, actual),应该把想要测试的表达式放在actual的位置,把期望值放在expected的位置。
可以通过操作符”<<”将一些自定义的信息输出,如:
EXPECT_EQ(v1, v2) << “this is a error!”;
断言包括:
(1)、true/false:ASSERT_TRUE(condition)、ASSERT_FALSE(condition)
(2)、compare two values:ASSERT_EQ(val1, val2)、ASSERT_NE(val1, val2)、ASSERT_LT(val1, val2)、ASSERT_LE(val1, val2)、ASSERT_GT(val1, val2)、ASSERT_GE(val1, val2)
(3)、string comparison: ASSERT_STREQ(str1, str2)、ASSERT_STRNE(str1,str2)、ASSERT_STRCASEEQ(str1, str2)、ASSERT_STRCASENE(str1,str2)
其中带”CASE”的断言是忽略大小写比较。从后缀名就应该可以区分每个比较的意义,如TRUE(条件为真)、FALSE(条件为假)、EQ(=)、NE(!=)、LT(<)、LE(<=)、GT(>)、GE(>=)。以上均存在对应的EXPECT_*断言。
二、TEST()宏
为了创建一个test:
(1)、使用TEST()来定义和命名一个test函数。TEST()宏是普通的C++函数,没有返回值。
(2)、在test函数内部,可以包含任何有效地C++语句,并由gtest断言来检查值。
(3)、此test函数结果由断言决定,有任何断言错误(fatally or non-fatally)或在此函数内部产生crash,则此test失败,否则成功。
TEST()包含两个参数,从一般到具体。第一个参数表示这个测试用例(test case)的名字,第二个参数表示在这个test case中这个test的名字。名字必须是有效地C++标识符,不能包含下划线。一个test的全名由test case和其自身名称组成。
gtest用test case来管理所有test,所以逻辑上相关的test应该放到同一个test case中。
三、TEST_F()宏
如果你写的多个test都在操作类似的数据,那么你能使用test fixture。它允许你在多个不同的test中复用相同的对象配置。
为了创建一个fixture:
(1)、派生一个类继承自::testing::Test,并使用protected或public限定符,因为后面将会从子类中访问fixture的成员。
(2)、在这个派生类里,声明你想使用的所有对象。
(3)、如果必要,编写一个默认构造函数或SetUp()函数来准备每个测试对象。
(4)、如果必要,写一个析构函数或TearDown()函数释放你在SetUp()中分配的任何资源。
(5)、如果需要,为你的tests定义共享函数。
当用fixture时,用TEST_F()代替TEST(),因为在test fixture中,TEST_F()允许你访问对象和共享函数。
像TEST(),TEST_F()也有两个参数,第一个是test case的名字,但是这个名字必须是test fixture类的名字。
在使用TEST_F()之前,你必须先定义一个test fixture类。
对于用TEST_F()定义的每一个test,gtest将会:
(1)、在运行时,创建一个全新的test fixture对象。
(2)、通过SetUp()函数立即初始化它。
(3)、运行test.
(4)、调用TearDown()函数清理申请的资源。
(5)、删除test fixture对象。在相同的test case中,不同的test有不同的test fixture对象,在创建下一个test fixture对象前,gtest总会先删除test fixture。在多个test中,gtest不能重用相同的test fixture。在一个test中对test fixture所作的改变不会影响其它的test。
四、testing::InitGoogleTest(argc,argv)函数
testing::InitGoogleTest函数对gtest框架进行初始化,并解析命令行参数,移除所有识别的标志。它允许用户通过各种标志控制测试程序的行为,如可设置重复运行测试次数、将结果输出到xml文件。必须在调用RUN_ALL_TESTS之前调用它,它还负责注册需要运行的所有测试用例。
五、RUN_ALL_TESTS()宏
在定义了所有的test后,通过RUN_ALL_TESTS()执行它们。
RUN_ALL_TESTS():负责执行所有测试,运行所有test,在代码中只能调用一次,如果所有tests成功,返回0,否则返回1。它会自动地探测并运行你链接到的所有测试----它们可以来自不同的测试用例,甚至是来自不同的文件。在默认情况下,结果输出到标准输出。
当调用RUN_ALL_TESTS()宏时:
(1)、保存所有gtest标志的状态。
(2)、为第一个test创建一个test fixture对象。
(3)、通过SetUp()函数初始化。
(4)、在这个fixture对象上运行这个test。
(5)、通过fixture的TearDown()函数释放资源。
(6)、删除这个fixture对象。
(7)、恢复所有gtest标志的状态。
(8)、为下一个test重复上面的操作步骤,直至运行完所有tests。
只能调用RUN_ALL_TESTS()一次。
更详细介绍可参考官网:https://github.com/google/googletest/blob/master/googletest/docs/Primer.md
以下的test code主要来自gtest中的samples:
funset.hpp:
#ifndef FBC_GTEST_TEST_FUNSET_HPP_
#define FBC_GTEST_TEST_FUNSET_HPP_
#include <string>
// reference: googletest/samples
/
// Returns n! (the factorial of n). For negative n, n! is defined to be 1.
int Factorial(int n);
// Returns true iff n is a prime number.
bool IsPrime(int n);
///
// A simple string class.
class MyString {
private:
const char* c_string_;
const MyString& operator=(const MyString& rhs);
public:
// Clones a 0-terminated C string, allocating memory using new.
static const char* CloneCString(const char* a_c_string);
// The default c'tor constructs a NULL string.
MyString() : c_string_(NULL) {}
// Constructs a MyString by cloning a 0-terminated C string.
explicit MyString(const char* a_c_string) : c_string_(NULL) {
Set(a_c_string);
}
// Copy c'tor
MyString(const MyString& string) : c_string_(NULL) {
Set(string.c_string_);
}
// D'tor. MyString is intended to be a final class, so the d'tor
// doesn't need to be virtual.
~MyString() { delete[] c_string_; }
// Gets the 0-terminated C string this MyString object represents.
const char* c_string() const { return c_string_; }
size_t Length() const {
return c_string_ == NULL ? 0 : strlen(c_string_);
}
// Sets the 0-terminated C string this MyString object represents.
void Set(const char* c_string);
};
//
// 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 // FBC_GTEST_TEST_FUNSET_HPP_
funset.cpp:
#include "funset.hpp"
// reference: googletest/samples
///
// Returns n! (the factorial of n). For negative n, n! is defined to be 1.
int Factorial(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
// Returns true iff n is a prime number.
bool IsPrime(int n) {
// Trivial case 1: small numbers
if (n <= 1) return false;
// Trivial case 2: even numbers
if (n % 2 == 0) return n == 2;
// Now, we have that n is odd and n >= 3.
// Try to divide n by every odd number i, starting from 3
for (int i = 3;; i += 2) {
// We only have to try i up to the squre root of n
if (i > n / i) break;
// Now, we have i <= n/i < n.
// If n is divisible by i, n is not prime.
if (n % i == 0) return false;
}
// n has no integer factor in the range (1, n), and thus is prime.
return true;
}
///
// Clones a 0-terminated C string, allocating memory using new.
const char* MyString::CloneCString(const char* a_c_string) {
if (a_c_string == NULL) return NULL;
const size_t len = strlen(a_c_string);
char* const clone = new char[len + 1];
memcpy(clone, a_c_string, len + 1);
return clone;
}
// Sets the 0-terminated C string this MyString object
// represents.
void MyString::Set(const char* a_c_string) {
// Makes sure this works when c_string == c_string_
const char* const temp = MyString::CloneCString(a_c_string);
delete[] c_string_;
c_string_ = temp;
}
test_TEST.cpp:
#include <iostream>
#include <vector>
#include <string>
#include <gtest/gtest.h>
#include "funset.hpp"
// reference: googletest/samples
/
// Tests factorial of negative numbers.
TEST(FactorialTest, Negative) {
// This test is named "Negative", and belongs to the "FactorialTest"
// test case.
EXPECT_EQ(1, Factorial(-5));
EXPECT_EQ(1, Factorial(-1));
EXPECT_GT(Factorial(-10), 0);
}
// Tests factorial of 0.
TEST(FactorialTest, Zero) {
EXPECT_EQ(1, Factorial(0));
}
// Tests factorial of positive numbers.
TEST(FactorialTest, Positive) {
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(6, Factorial(3));
EXPECT_EQ(40320, Factorial(8));
}
// Tests negative input.
TEST(IsPrimeTest, Negative) {
// This test belongs to the IsPrimeTest test case.
EXPECT_FALSE(IsPrime(-1));
EXPECT_FALSE(IsPrime(-2));
EXPECT_FALSE(IsPrime(INT_MIN));
}
// Tests some trivial cases.
TEST(IsPrimeTest, Trivial) {
EXPECT_FALSE(IsPrime(0));
EXPECT_FALSE(IsPrime(1));
EXPECT_TRUE(IsPrime(2));
EXPECT_TRUE(IsPrime(3));
}
// Tests positive input.
TEST(IsPrimeTest, Positive) {
EXPECT_FALSE(IsPrime(4));
EXPECT_TRUE(IsPrime(5));
EXPECT_FALSE(IsPrime(6));
EXPECT_TRUE(IsPrime(23));
}
/
// Tests the default c'tor.
TEST(MyString, DefaultConstructor) {
const MyString s;
EXPECT_STREQ(NULL, s.c_string());
EXPECT_EQ(0u, s.Length()) << "ok";
}
const char kHelloString[] = "Hello, world!";
// Tests the c'tor that accepts a C string.
TEST(MyString, ConstructorFromCString) {
const MyString s(kHelloString);
EXPECT_EQ(0, strcmp(s.c_string(), kHelloString));
EXPECT_EQ(sizeof(kHelloString) / sizeof(kHelloString[0]) - 1, s.Length());
fprintf(stderr, "Print Info: sizeof(kHelloString): %d, sizeof(kHelloString[0]): %d, s.Length(): %d\n",
sizeof(kHelloString), sizeof(kHelloString[0]), s.Length());
}
// Tests the copy c'tor.
TEST(MyString, CopyConstructor) {
const MyString s1(kHelloString);
const MyString s2 = s1;
EXPECT_EQ(0, strcmp(s2.c_string(), kHelloString));
}
// Tests the Set method.
TEST(MyString, Set) {
MyString s;
s.Set(kHelloString);
EXPECT_EQ(0, strcmp(s.c_string(), kHelloString));
// Set should work when the input pointer is the same as the one
// already in the MyString object.
s.Set(s.c_string());
ASSERT_EQ(0, strcmp(s.c_string(), kHelloString));
// Can we set the MyString to NULL?
s.Set(NULL);
EXPECT_STREQ(NULL, s.c_string());
}
test_TEST_F.cpp:
#include <iostream>
#include <vector>
#include <string>
#include <time.h>
#include <gtest/gtest.h>
#include "funset.hpp"
// reference: googletest/samples
/
// 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() override {
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_);
}
/
// teaches how to reuse a test fixture in multiple test cases by deriving sub-fixtures from it
class QuickTest : public testing::Test {
protected:
// Remember that SetUp() is run immediately before a test starts.
// This is a good place to record the start time.
virtual void SetUp() override {
start_time_ = time(NULL);
}
// TearDown() is invoked immediately after a test finishes. Here we
// check if the test was too slow.
virtual void TearDown() override {
// Gets the time when the test finishes
const time_t end_time = time(NULL);
// Asserts that the test took no more than ~5 seconds. Did you
// know that you can use assertions in SetUp() and TearDown() as
// well?
EXPECT_TRUE(end_time - start_time_ <= 5) << "The test took too long.";
}
// The UTC time (in seconds) when the test starts
time_t start_time_;
};
// We derive a fixture named IntegerFunctionTest from the QuickTest
// fixture. All tests using this fixture will be automatically
// required to be quick.
class IntegerFunctionTest : public QuickTest {
// We don't need any more logic than already in the QuickTest fixture.
// Therefore the body is empty.
};
// Now we can write tests in the IntegerFunctionTest test case.
// Tests Factorial()
TEST_F(IntegerFunctionTest, Factorial) {
// Tests factorial of negative numbers.
EXPECT_EQ(1, Factorial(-5));
EXPECT_EQ(1, Factorial(-1));
EXPECT_GT(Factorial(-10), 0);
// Tests factorial of 0.
EXPECT_EQ(1, Factorial(0));
// Tests factorial of positive numbers.
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(6, Factorial(3));
EXPECT_EQ(40320, Factorial(8));
}
// Tests IsPrime()
TEST_F(IntegerFunctionTest, IsPrime) {
// Tests negative input.
EXPECT_FALSE(IsPrime(-1));
EXPECT_FALSE(IsPrime(-2));
EXPECT_FALSE(IsPrime(INT_MIN));
// Tests some trivial cases.
EXPECT_FALSE(IsPrime(0));
EXPECT_FALSE(IsPrime(1));
EXPECT_TRUE(IsPrime(2));
EXPECT_TRUE(IsPrime(3));
// Tests positive input.
EXPECT_FALSE(IsPrime(4));
EXPECT_TRUE(IsPrime(5));
EXPECT_FALSE(IsPrime(6));
EXPECT_TRUE(IsPrime(23));
}
//
// teaches how to reuse a test fixture in multiple test cases by deriving sub-fixtures from it
class QueueTest_2 : public QuickTest {
protected:
virtual void SetUp() override {
// First, we need to set up the super fixture (QuickTest).
QuickTest::SetUp();
// Second, some additional setup for this fixture.
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// By default, TearDown() inherits the behavior of
// QuickTest::TearDown(). As we have no additional cleaning work
// for QueueTest, we omit it here.
//
// virtual void TearDown() override {
// QuickTest::TearDown();
// }
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};
// Tests the default constructor.
TEST_F(QueueTest_2, DefaultConstructor) {
EXPECT_EQ(0u, q0_.Size());
}
// Tests Dequeue().
TEST_F(QueueTest_2, Dequeue) {
int* n = q0_.Dequeue();
EXPECT_TRUE(n == NULL);
n = q1_.Dequeue();
EXPECT_TRUE(n != NULL);
EXPECT_EQ(1, *n);
EXPECT_EQ(0u, q1_.Size());
delete n;
n = q2_.Dequeue();
EXPECT_TRUE(n != NULL);
EXPECT_EQ(2, *n);
EXPECT_EQ(1u, q2_.Size());
delete n;
}
test_TEST.cpp(main function):
#include <iostream>
#include <vector>
#include <gtest/gtest.h>
int main()
{
std::vector<char*> argv{
#ifdef _DEBUG
"E:/GitCode/Messy_Test/lib/dbg/x64_vc12/gtest_Test.exe",
#else
"E:/GitCode/Messy_Test/lib/rel/x64_vc12/gtest_Test.exe",
#endif
//"--gtest_repeat=2 ", // 重复运行测试次数
//"--gtest_break_on_failure", // 遇到failure退出
"--gtest_filter=*", // 指定需要运行的tests
//"--gtest_print_time=0", // don't print the elapsed time of each test
"--gtest_output=xml:E:/GitCode/Messy_Test/testdata/info.xml" // 结果输出到指定的xml文件
};
int argc = argv.size();
// Initializes Google Test. This must be called before calling RUN_ALL_TESTS()
testing::InitGoogleTest(&argc, argv.data());
// Use this function in main() to run all tests. It returns 0 if all
// tests are successful, or 1 otherwise
int ret = RUN_ALL_TESTS();
if (ret == 0) fprintf(stderr, "========== all tests are succeseful =========\n");
else fprintf(stderr, "********** some tests are failed **********\n");
return ret;
}
执行结果如下:
GitHub: https://github.com/fengbingchun/Messy_Test