您的位置:首页 > 编程语言 > Go语言

googletest试用

2015-06-08 15:22 489 查看
最近一段时间在推进自动化测试和部署系统的工作。之前大量的测试大多手工或半自动完成,在开发过程中占用了不少宝贵的时间,耗费了不少心力;且有时候由于需求推进的紧迫导致开发在测试的过程中不够谨慎,疏忽了对于一些边界情况的考虑,最后在系统上线后才发现问题,在一定程度上给某些用户带来了一些困惑,影响了这部分用户的正常使用。为了减轻开发者的测试工作量,也为了预防测试工作执行的不彻底,打算借着这段空闲期推进下自动化测试的内容。

先简单介绍下我们的系统,整个系统的架构是一个C/S的结构,客户端有安卓和iOS,客户端与后端通过HTTP协议来协商,后端采用的技术栈主要是Apache+CGI(一种最古老的服务端编程技术),使用的编程语言是C++。基于C++的测试框架有很多,最终我选用了googletest(简称gtest),因为使用简单,上手快,可扩展性比较强。

以前没做过测试相关的工作,对于测试的认识也比较粗浅。在我作为开发者有限的视野里,测试是一项很神圣的工作。从技术层面上来说,测试是很有技术含量的工作,牛逼的测试除了基本功扎实,也是一个牛逼的开发;从软件开发过程来说,测试有效地保障了系统的稳定性和性能,降低了软件的成本和风险,是一件相当有意义的事情。但在国内很多公司,特别是创业类型的小公司(比如我现在所处的),测试这件事情相当不受重视。基本上是谁开发谁负责测试,没有专业的测试负责这件事情,那么软件的好坏完全就要看开发的水平和良心。虽然现在都提倡DevOps,但实际上很多开发者都缺乏测试这方面的知识和意识。

回到gtest这个话题,安装过程比较简单,参考README文件可以搞定。我下载的是gtest1.7版本,编译完gtest后需要用ar将gtest-all.o纳入静态库中即可使用。如何使用gtest可以参考gtest的文档或者源码中提供的sample,实际上我就是看了几个sample就开始写代码了,所以sample的代码一定要看。在我实现的自动化测试系统中只用到了gtest中两个feature:Assertions和Test
Fixtures,实际上已经能很好地帮助我完成这个系统了,代码也相当简洁,请求的构造和编码以及响应的解码可以在Test Fixtures的SetUp函数中完成,真正校验的部分在各个TEST_F测试用例中完成就好了;为了做到同一个测试可以被多种请求参数复用,请求参数均以文件配置的形式输入,意味着如果要新增一种情况的输入只需要写一个配置文件就可以了,后来发现gtest也提供了类似的feature:值参数化测试(value-parameterized test)。

在使用gtest的时候,实际上最重要的概念还是assertion,因为无论如何,最终你需要用assertion来校验你的函数、模块或者系统是否符合正确的输入输出规范,才能够真正检验你的测试是否通过。在编写gtest测试程序的时候,你需要理解这样一个概念:一个测试程序可以包含多个测试用例,而一个测试用例可以包含一到多个测试,当同一个测试用例中的多个测试需要共享对象和子程序时,可以使用test
fixture类来帮助完成。下面来看一个简单的测试用例:

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));
}


其中,TEST()宏用来定义测试函数,它包含两个参数,第一个参数表示测试用例的名称,第二个参数表示测试的名称。在上面的例子中,定义了一个测试用例FactorialTest,而该测试用例包含两个测试HandlesZeroInput和HandlesPositiveInput。测试中的EXPECT_EQ宏正是assertions的一种,它用来检验两个数值是否相等。再来看一个使用Test Fixture的例子:

template <typename E> // E is the element type.
class Queue {
public:
Queue();
void Enqueue(const E& element);
E* Dequeue(); // Returns NULL if the queue is empty.
size_t size() const;
...
};

class QueueTest : public ::testing::Test {
protected:
virtual void SetUp() {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}

virtual void TearDown() {}

Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};

TEST_F(QueueTest, IsEmptyInitially) {
EXPECT_EQ(0, q0_.size());
}

TEST_F(QueueTest, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(NULL, n);

n = q1_.Dequeue();
ASSERT_TRUE(n != NULL);
EXPECT_EQ(1, *n);
EXPECT_EQ(0, q1_.size());
delete n;

n = q2_.Dequeue();
ASSERT_TRUE(n != NULL);
EXPECT_EQ(2, *n);
EXPECT_EQ(1, q2_.size());
delete n;
}


上面这个例子测试的是一个先进先出队列的测试代码。首先定义了一个Test Fixture类QueueTest继承::testing::Test类,并实现了SetUp()和TearDown()虚函数。接下来定义了两个测试IsEmptyInitially和DequeueWorks,它们隶属于同一个测试用例QueueTest,注意此时定义测试函数的宏并不是TEST()而是TEST_F(),且TEST_F()第一个参数(即测试用例名称)要和Test Fixture类名保持一致。执行测试的逻辑大概是这样:先创建一个Test
Fixture类对象,调用SetUp()完成初始化,执行测试用例下的某个测试,调用TearDown()完成清理工作,销毁Test Fixture对象。不同的测试使用不同的Test Fixture对象,因此同一个测试用例下的不同测试在执行它的测试之前都是相同的状态。Test Fixture类中定义的共有成员可以在测试中直接访问,如例子中的q0_、q1_和q2_。

细心的人可能注意到上面的两个例子都没有main函数,是的,我们来看看上面两个例子的main函数:

int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}


先调用::testing::InitGoogleTest()函数来解析命令行参数,gtest提供了一系列命令行参数供使用,有兴趣的可以在编译生成你的测试程序后,执行时加上-h参数,会有相关的说明。如下:

This program contains tests written using Google Test. You can use the
following command line flags to control its behavior:

Test Selection:
--gtest_list_tests
List the names of all tests instead of running them. The name of
TEST(Foo, Bar) is "Foo.Bar".
--gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS]
Run only the tests whose name matches one of the positive patterns but
none of the negative patterns. '?' matches any single character; '*'
matches any substring; ':' separates two patterns.
--gtest_also_run_disabled_tests
Run all disabled tests too.

Test Execution:
--gtest_repeat=[COUNT]
Run the tests repeatedly; use a negative count to repeat forever.
--gtest_shuffle
Randomize tests' orders on every iteration.
--gtest_random_seed=[NUMBER]
Random number seed to use for shuffling test orders (between 1 and
99999, or 0 to use a seed based on the current time).

Test Output:
--gtest_color=(yes|no|auto)
Enable/disable colored output. The default is auto.
--gtest_print_time=0
Don't print the elapsed time of each test.
--gtest_output=xml[:DIRECTORY_PATH/|:FILE_PATH]
Generate an XML report in the given directory or with the given file
name. FILE_PATH defaults to test_details.xml.
--gtest_stream_result_to=HOST:PORT
Stream test results to the given server.

Assertion Behavior:
--gtest_death_test_style=(fast|threadsafe)
Set the default death test style.
--gtest_break_on_failure
Turn assertion failures into debugger break-points.
--gtest_throw_on_failure
Turn assertion failures into C++ exceptions.
--gtest_catch_exceptions=0
Do not report exceptions as test failures. Instead, allow them
to crash the program or throw a pop-up (on Windows).

Except for --gtest_list_tests, you can alternatively set the corresponding
environment variable of a flag (all letters in upper-case). For example, to
disable colored text output, you can either specify --gtest_color=no or set
the GTEST_COLOR environment variable to no.

For more information, please read the Google Test documentation at http://code.google.com/p/googletest/. If you find a bug in Google Test
(not one in your own code or tests), please report it to
<googletestframework@googlegroups.com>.


调用RUN_ALL_TESTS()函数执行你所定义的所有测试。如果你没有使用更加高级的feature,main函数这段代码一般是保持不变的,so不妨把它们放在头文件里:-),更多高级特性可以参考这里

参考链接:
gtest项目地址
gtest官方文档
玩转gtest系列 (国人所写,写的比较全面,推荐给不爱看英文文档的)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  testing gtest