gtest使用初级指南

来源:互联网 发布:导入mac的照片在哪里 编辑:程序博客网 时间:2024/06/05 10:24

之前在 http://blog.csdn.net/fengbingchun/article/details/39667571 中对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'torMyString(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 typeclass 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 typeclass 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 numbersif (n <= 1) return false;// Trivial case 2: even numbersif (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 3for (int i = 3;; i += 2) {// We only have to try i up to the squre root of nif (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 itclass 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 finishesconst 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 startstime_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 itclass 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 otherwiseint 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




原创粉丝点击