编写优美的GTest测试案例

 使用gtest也有很长一段时间了,这期间也积累了一些经验,所以分享一下。GTest为我们提供了便捷的测试框架,让我们只需要关注案例本身。如何在GTest框架下写出优美的测试案例,我觉得必须要做到:

  案例的层次结构一定要清晰

  案例的检查点一定要明确

  案例失败时一定要能精确的定位问题

  案例执行结果一定要稳定

  案例执行的时间一定不能太长

  案例一定不能对测试环境造成破坏

  案例一定独立,不能与其他案例有先后关系的依赖

  案例的命名一定清晰,容易理解

  案例的可维护性也是非常重要,如果做到上面的8点,自然也就做到了可维护性。下面来分享一下我对于上面8点的经验:

  1. 案例的层次结构一定要清晰

  所谓层次结构,至少要让人一眼就能分辨出被测代码和测试代码。简单的说,就是知道你在测什么。由于是进行接口测试,我已经习惯了如下的案例层次:

  DataDefine

  我会将测试案例所需要的数据,以及数据之间的联系全部在预先定义好。测试数据与案例逻辑的分离,有利于维护和扩展测试案例。同时,GTest先天就支持测试数据参数化,为测试数据的分离提供了进一步的便捷。什么是测试数据参数化?就是你可以预先定义好一批各种各样的数据,而你只需要编写一个测试案例的逻辑代码,gtest会将定义好的数据逐个套入测试案例中进行执行。具体的做法请见:玩转Google开源C++单元测试框架Google Test系列(gtest)之四 - 参数化

  SUT

  SUT,即system under test,表明你的测试对象是什么,它可以是一个类(CUT),对象(OUT),函数(MUT),甚至可以是整个应用程序(AUT)。我单独将这个层次划分出来,主要有两个目的:

  (1)明确的表示出你的测试对象是什么

  (2)为复杂调用对象包装简单调用接口

  明确表示测试对象是什么,便于之后对测试案例的维护和对测试案例的理解。同时,对于一些被测对象,你想要调用它需要经过一系列烦琐的过程,这时,就需要将这一烦琐的调用过程隐藏起来,而只关注被测对象的输入和输出。

  TestCase

  测试工程中,必须非常明确的表示出哪些是测试案例,哪些是其他的辅助文件。通常,我们会在测试案例的文件名加上Test前缀(或者后缀)。我建议,将所有的测试案例文件或代码放在最显眼的地方,让所有看到你的测试工程的人,第一眼看到的就是测试案例,这很重要。

  Checker

  对于一个复杂系统的接口测试,仅仅坚持输入和输出是远远不够的。比如测试一个写数据库的函数,函数的返回值告诉你数据已经成功写入是远远不够的,你必须亲身去数据库中查个究竟才行。因此,对于某一类的测试案例,我们可以抽象出一些通用的检查点代码。

  如果做到上面的分层,那么一个测试案例写出来的结构应该会是这个样子:


TEST(TestFoo, JustDemo)

{

GetTestData(); // 获取测试数据

CallSUT(); // 调用被测方法

CheckSomething(); // 检查点验证

}

  这样的测试案例,一目了然。

  2. 案例的检查点一定要明确

  一定要明确案例的检查点是什么,并且让检查点尽量集中。有一个不好的习惯就是核心的检查点在分布在多个函数中,需要不断的跳转才能了解到这个案例检查了些什么。好的做法应该是尽量让检查点集中,能够非常清晰的分辨出案例对被测代码做了哪些检查。所以,尽量让Gtest的ASSERT_和EXPECT_系列的宏放在明显和正确的地方。

  3. 案例失败时一定要能精确的定位问题

  测试案例失败时,我们通常手忙脚乱。如果一个测试案例Failed,却不能立即推断是被测代码的Bug的话,这个测试案例也有待改进。我们可以在一些复杂的检查点断言中加入一些辅助信息,方便我们定位问题。比如下面这个测试案例:


int n = -1;

bool actualResult = Foo::Dosometing(n);

ASSERT_TRUE(actualResult)

  如果测试案例失败了,会得到下面的信息:


Value of: actualResult

Actual: false

Expected:true

  这样的结果对于我们来说,几乎没有什么用。因为我们根本不知道actualResult是什么,以及在什么情况下才会出现非预期值。因此,在断言处多加入一些信息,将有助于定位问题:


int n = -1;

bool actualResult = Foo::Dosometing(n);

ASSERT_TRUE(actualResult) << L"Call Foo::Dosometing(n) when n = " << n;

  4. 案例执行结果一定要稳定

  要保证测试案例在什么时候、什么情况下执行的结果都是一样的。一个一会成功一会失败的案例是没有意义的。要保证案例稳定性的方法有很多,比如杜绝案例之间的影响,有时候,由于前一个案例执行完后,将一些系统的环境破坏了,导致后面的案例执行失败。在测试某些本身就存在一定几率或延时的系统时,使用超时机制是比较简单的办法。比如,你需要测试一个启动Windows服务的方法,如果我们在调用了该方法后立即进行检查,很可能检查点会失败,有时候也许又是通过的。这是因为Windows服务由Stop状态到Running状态,中间还要经过一个Padding状态。所以,简单的做法是使用超时机制,隔断时间检查一次,直到超过某个最大忍受时间。


ASSERT_TRUE(StartService('xxx'));

int tryTimes = 0;

int status = GetServiceStatus('xxx');

while (status != Running)

{

if (tryTimes >= 10)

break;

::Sleep(200);

tryTimes++;

status = GetServiceStatus('xxx');

}

ASSERT_EQ(Running, status) << "Check the status after StartService('xxx')";

  5. 案例执行的时间一定不能太长

  我们应该尽量让案例能够快速的执行,一方面,我们可以通过优化我们的代码来减少运行时间,比如,减少对重复内容的读取。一方面,对于一些比较耗时的操作,比如文件系统,网络操作,我们可以使用Mock对象来替代真实的对象。使用GMock是一个不错的选择。

  6. 案例一定不能对测试环境造成破坏

  有的案例需要在特定的环境下来能执行,因此会在案例的初始化时对环境进行一些修改。注意,不管对什么东西进行了修改,一定要保证在案例执行完成的TearDown中将这些环境都还原回来。否则有可能对后面的案例造成影响,或者出现一些莫名其妙的错误。

  7. 案例一定独立,不能与其他案例有先后关系的依赖

  任何一个案例都不依赖于其他测试案例,任何一个案例的执行结果都不应该影响到别的案例。任何一个案例都可以单独拿出去正确的执行。所以,不能寄希望于前一个案例所做的环境准备,因为这是不对的。

  8. 案例的命名一定清晰,容易理解

  案例的名字要规范,长不要紧,一定要清晰的表达测试案例的用途。比如,下面的测试案例名称都是不好的:


TEST(TestFoo, Test)

TEST(TestFoo, Normal)

TEST(TestFoo, Alright)

  比如像下面的案例名称就会好一点:


TEST(TestFoo, Return_True_When_ParameterN_Larger_Then_Zero)

TEST(TestFoo, Return_False_When_ParameterN_Is_Zero)

最新内容请见作者的GitHub页:http://qaseven.github.io/

时间: 2024-09-23 15:15:19

编写优美的GTest测试案例的相关文章

用函数式编程技术编写优美的 JavaScript_ibm_javascript技巧

因为函数式编程采用了完全不同的组织程序的方式,所以那些习惯于采用命令式范例的程序员可能会发现函数式编程有点难学.在这篇文章中,您将了解一些关于如何采用函数式风格,用 JavaScript 编写良好的.优美的代码的示例.我将讨论:  函数式编程概念,包括匿名函数.调用函数的不同方法,以及将函数作为参数传递给其他函数的方式. 函数式概念的运用,采用的示例包括:扩展数组排序:动态 HTML 生成的优美代码:系列函数的应用. 函数式编程概念  请告诉每个人.请把这个提交给:      Digg     

设计-如何编写一段c++测试代码

问题描述 如何编写一段c++测试代码 如题,我是一位初学者,现有一道题如下: 编一段代码,分别实现插入排序,合并排序和快速排序的算法.然后编写测试代码,设计测试数据集,编写测试程序,测试正确性.算法复杂性和效率. 题目中注明检测正确性的测试程序应该输出一个遍历结果,保证所有分支语句都检测到.不太理解这是要我做什么--以及比较算法复杂性.效率,是需我明确地输出花费时间吗? 希望有大神可以在测试代码这方面指点迷津,谢谢! 解决方案 "主要是你要准备足够充分的测试数据,这样才能保证代码的覆盖率.&qu

java-求:junit的简单测试案例

问题描述 求:junit的简单测试案例 哪位大神能给我一个junit的简单测试案例,新手,初次使用junit4,谢谢. 最好是带参数的. 解决方案 1:引入junit包: 2:@Test public void test(){ //写入要执行的方法 } 此时不需要main方法,该方法体也可以执行. 解决方案二: 网上搜索一下会有一大堆

Drozer – Android APP安全评估工具(附测试案例)

Drozer原名mercury,是一款不错的Android APP安全评估工具.现在有社区版/专业版两个版本. 具体的使用说明可以参考https://www.mwrinfosecurity.com/system/assets/559/original/mwri_drozer-users-guide_2013-09-11.pdf  测试案例 某Android APP由于Content Provider的控制不严,导致可读写APP私有的数据库,具体的步骤和思路: 1.查找APP完整的包名字(某些命令

用函数式编程技术编写优美的 JavaScript_javascript技巧

级别: 初级 Shantanu Bhattacharya (shantanu@justawordaway.com), 首席顾问, Siemens Information Systems Limited 2006 年 7 月 20 日 函数式或声明性编程是非常强大的编程方法,正逐渐在软件行业流行起来.这篇文章将介绍一些相关的函数式编程概念,并提供有效使用这些概念的示例.作者将解释如何使用 JavaScript(TM)(JavaScript 能导入函数式编程的构造和特性)编写优美的代码. 简介 函数

《有效的单元测试》一1.1 国情咨文:编写更好的测试

1.1 国情咨文:编写更好的测试 下述概念如今已被广泛推荐,即开发者应该编写自动化测试,以便当发现回归问题时就使构建失败.而且,测试先行的编程风格已有大量的专业研究,使用自动化测试不仅是保护回归,而且是帮助设计,在编写代码之前就指出代码的期望行为,从而在验证实现之前先验证设计.作为顾问,我见过很多团队.组织.产品和代码.看看今天的我们,很明显自动化测试已经成为主流.这很棒,因为没有自动化测试,大多数软件项目会比现在更糟.自动化测试改善了你的生产力,使你获得并保持开发速度.救命!我是单元测试新手如

《Metasploit渗透测试手册》—第8章8.8节编写FileZilla FTP模糊测试器

8.8 编写FileZilla FTP模糊测试器Metasploit渗透测试手册前面已分析过模糊测试模块的工作过程,本节中将进一步构建自己的小型FTP模糊测试器,用于对FileZilla FTP服务器进行模糊测试. 怎样实现构建模糊测试器的基本模板与前面开发辅助模块所用的模板类似,基本模板应具有如下的形式. require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Scanner def initi

gtest中如何跳出当前测试案例

在Apache服务器的套件中,有一个叫做 ab (ApacheBench) 的工具. ApacheBench 主要是用来测试Apache服务器执行效率用的 ApacheBench 可以针对某个特定的 URL 仿真出连续的联机请求 同时还可以仿真出同时间点数个相同的联机请求,因此利用 ApacheBench 可帮助我们在网站开发期间仿真实际上线可能的情况,利用仿真出来的数据做为调整服务器设定或程序的依据. 参数说明 01bixiaopeng@172-13-3-157 ~$ ab -h 02Usag

使用Flex编写的ActionScript正则表达式测试工具

为了在开发过程中,测试正则表达式更方便,用Flex编写了下面的正则表达 式测试工具,也大家分享,欢迎提出改进意见 在独立的窗口中查看:http://my.jhost.cn/iihe602_2/flex/sdk3/RegexTester.html 现在样例 比较少,今后将逐渐增加 1 <?xml version="1.0" encoding="utf-8"?> 2 <mx:Application xmlns:mx="http://www.a