之前对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;
}


执行结果如下:




gtest 取测试名 gtest github_gtest 取测试名



GitHubhttps://github.com/fengbingchun/Messy_Test