编写优美的GTest测试案例
http://www.cnblogs.com/coderzh/archive/2010/01/09/beautiful-testcase.html
使用gtest也有很长一段时间了,这期间也积累了一些经验,所以分享一下。GTest为我们提供了便捷的测试框架,让我们只需要关注案例本身。如何在GTest框架下写出优美的测试案例,我觉得必须要做到:
- 案例的层次结构一定要清晰
- 案例的检查点一定要明确
- 案例失败时一定要能精确的定位问题
- 案例执行结果一定要稳定
- 案例执行的时间一定不能太长
- 案例一定不能对测试环境造成破坏
- 案例一定独立,不能与其他案例有先后关系的依赖
- 案例的命名一定清晰,容易理解
案例的可维护性也是非常重要,如果做到上面的8点,自然也就做到了可维护性。下面来分享一下我对于上面8点的经验:
1. 案例的层次结构一定要清晰
所谓层次结构,至少要让人一眼就能分辨出被测代码和测试代码。简单的说,就是知道你在测什么。由于是进行接口测试,我已经习惯了如下的案例层次:
![]()
DataDefine
我会将测试案例所需要的数据,以及数据之间的联系全部在预先定义好。测试数据与案例逻辑的分离,有利于维护和扩展测试案例。同时,GTest先天就支持测试数据参数化,为测试数据的分离提供了进一步的便捷。什么是测试数据参数化?就是你可以预先定义好一批各种各样的数据,而你只需要编写一个测试案例的逻辑代码,gtest会将定义好的数据逐个套入测试案例中进行执行。具体的做法请见:玩转Google开源C++单元测试框架Google Test系列(gtest)之四 - 参数化
SUT
SUT,即system under test,表明你的测试对象是什么,它可以是一个类(CUT),对象(OUT),函数(MUT),甚至可以是整个应用程序(AUT)。我单独将这个层次划分出来,主要有两个目的:
- 明确的表示出你的测试对象是什么
- 为复杂调用对象包装简单调用接口
明确表示测试对象是什么,便于之后对测试案例的维护和对测试案例的理解。同时,对于一些被测对象,你想要调用它需要经过一系列烦琐的过程,这时,就需要将这一烦琐的调用过程隐藏起来,而只关注被测对象的输入和输出。
TestCase
测试工程中,必须非常明确的表示出哪些是测试案例,哪些是其他的辅助文件。通常,我们会在测试案例的文件名加上Test前缀(或者后缀)。我建议,将所有的测试案例文件或代码放在最显眼的地方,让所有看到你的测试工程的人,第一眼看到的就是测试案例,这很重要。
Checker
对于一个复杂系统的接口测试,仅仅坚持输入和输出是远远不够的。比如测试一个写数据库的函数,函数的返回值告诉你数据已经成功写入是远远不够的,你必须亲身去数据库中查个究竟才行。因此,对于某一类的测试案例,我们可以抽象出一些通用的检查点代码。
如果做到上面的分层,那么一个测试案例写出来的结构应该会是这个样子:
TEST(TestFoo, JustDemo)
{
GetTestData(); // 获取测试数据 CallSUT(); // 调用被测方法 CheckSomething(); // 检查点验证
}
这样的测试案例,一目了然。
2. 案例的检查点一定要明确
一定要明确案例的检查点是什么,并且让检查点尽量集中。有一个不好的习惯就是核心的检查点在分布在多个函数中,需要不断的跳转才能了解到这个案例检查了些什么。好的做法应该是尽量让检查点集中,能够非常清晰的分辨出案例对被测代码做了哪些检查。所以,尽量让Gtest的ASSERT_和EXPECT_系列的宏放在明显和正确的地方。
3. 案例失败时一定要能精确的定位问题
测试案例失败时,我们通常手忙脚乱。如果一个测试案例Failed,却不能立即推断是被测代码的Bug的话,这个测试案例也有待改进。我们可以在一些复杂的检查点断言中加入一些辅助信息,方便我们定位问题。比如下面这个测试案例:
bool actualResult = Foo::Dosometing(n);
ASSERT_TRUE(actualResult)
如果测试案例失败了,会得到下面的信息:
Actual: false
Expected:true
这样的结果对于我们来说,几乎没有什么用。因为我们根本不知道actualResult是什么,以及在什么情况下才会出现非预期值。因此,在断言处多加入一些信息,将有助于定位问题:
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 = ;
int status = GetServiceStatus('xxx');
while (status != Running)
{
if (tryTimes >= )
break;
::Sleep();
tryTimes++;
status = GetServiceStatus('xxx');
}
ASSERT_EQ(Running, status) << "Check the status after StartService('xxx')";
5. 案例执行的时间一定不能太长
我们应该尽量让案例能够快速的执行,一方面,我们可以通过优化我们的代码来减少运行时间,比如,减少对重复内容的读取。一方面,对于一些比较耗时的操作,比如文件系统,网络操作,我们可以使用Mock对象来替代真实的对象。使用GMock是一个不错的选择。
6. 案例一定不能对测试环境造成破坏
有的案例需要在特定的环境下来能执行,因此会在案例的初始化时对环境进行一些修改。注意,不管对什么东西进行了修改,一定要保证在案例执行完成的TearDown中将这些环境都还原回来。否则有可能对后面的案例造成影响,或者出现一些莫名其妙的错误。
7. 案例一定独立,不能与其他案例有先后关系的依赖
任何一个案例都不依赖于其他测试案例,任何一个案例的执行结果都不应该影响到别的案例。任何一个案例都可以单独拿出去正确的执行。所以,不能寄希望于前一个案例所做的环境准备,因为这是不对的。
8. 案例的命名一定清晰,容易理解
案例的名字要规范,长不要紧,一定要清晰的表达测试案例的用途。比如,下面的测试案例名称都是不好的:
TEST(TestFoo, Normal)
TEST(TestFoo, Alright)
比如像下面的案例名称就会好一点:
TEST(TestFoo, Return_False_When_ParameterN_Is_Zero)
作者:CoderZh(CoderZh的技术博客 - 博客园)
微博:http://t.sina.com.cn/coderzh
出处:http://coderzh.cnblogs.com
文章版权归本人所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
编写优美的GTest测试案例的更多相关文章
- gtest命令行测试案例
使用gtest编写的测试案例通常本身就是一个可执行文件,因此运行起来非常方便.同时,gtest也为我们提供了一系列的运行参数(环境变量.命令行参数或代码里指定),使得我们可以对案例的执行进行一些有效的 ...
- (转)编写Spring的第一个案例并测试Spring的开发环境
http://blog.csdn.net/yerenyuan_pku/article/details/52832145 Spring4.2.5的开发环境搭建好了之后,我们来编写Spring的第一个案例 ...
- Storm自带测试案例的运行
之前Storm安装之后,也知道了Storm的一些相关概念,那么怎么样才可以运行一个例子对Storm流式计算有一个感性的认识呢,那么下面来运行一个Storm安装目录自带的测试案例,我们的Storm安装在 ...
- Kafka吞吐量测试案例
Kafka吞吐量测试案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 领英公司参考连接:https://www.slideshare.net/JiangjieQin/produc ...
- Hadoop基础-MapReduce入门篇之编写简单的Wordcount测试代码
Hadoop基础-MapReduce入门篇之编写简单的Wordcount测试代码 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本文主要是记录一写我在学习MapReduce时的一些 ...
- Selenium私房菜系列5 -- 第一个Selenium RC测试案例
<Selenium简介>中讲过,Selenium RC支持多种语言编写测试案例,如:C#,Python.在工作中,我倾向于是用Python这类动态语言编写测试案例,因为这样的测试案例无需编 ...
- Selenium(十四):自动化测试模型介绍、模块化驱动测试案例、数据驱动测试案例
1. 自动化测试模型介绍 随着自动化测试技术的发展,演化为了集中模型:线性测试.模块化驱动测试.数据驱动测试和关键字驱动测试. 下面分别介绍这几种自动化测试模型的特点. 1.1 线性测试 通过录制或编 ...
- 实现如下类之间的继承关系,并编写Music类来测试这些类。
实现如下类之间的继承关系,并编写Music类来测试这些类. package com.hanqi.test; public class Instrument { //输出弹奏乐器 public void ...
- mvn编写主代码与测试代码
maven编写主代码与测试代码 3.2 编写主代码 项目主代码和测试代码不同,项目的主代码会被打包到最终的构件中(比如jar),而测试代码只在运行测试时用到,不会被打包.默认情况下,Maven假设项目 ...
随机推荐
- AGG 之贝塞尔插值
原文: http://liyiwen.iteye.com/blog/705489 之前 comp.graphic.algorithms 上有一个讨论,是关于怎么样使用曲线对多边形进行插值处理,使得最终 ...
- poj1023
题目大意:有趣的数字系统 在一个k位的2的补码,,位的索引是从0到k-1位的,值是-2^(k-1),其他位置的i是2^i( (0 ≤ i < k-1) ),例如,3位的数字101是-2^2 + ...
- zzuoj 10408: C.最少换乘【最短路dijkstra】
10408: C.最少换乘 Time Limit: 2 Sec Memory Limit: 128 MBSubmit: 31 Solved: 8[Submit][Status][Web Board ...
- java项目使用Echarts 做柱状堆叠图,包含点击事件
基础知识请自行百度查看,以下直接贴出实现代码: <%@ page pageEncoding="UTF-8"%><!DOCTYPE html><html ...
- windows下计算文件的md和sha值
在windows下可以使用FCIV命令行工具计算文件的md5和sha值,具体例子如下: FCIV -md5 -sha1 path\filename.ext 例如: FCIV-md5-sha1 c:\w ...
- Git git rebase 使用
原文:http://gitbook.liuhui998.com/4_2.html 一.基本 git rebase用于把一个分支的修改合并到当前分支. 假设你现在基于远程分支"origin ...
- js~fancybox为我们提供的iframe功能
对于fancybox我们已经耳熟能详了,一般用来到表单的弹框,提示弹框等,而今天,我们将分页列表也使用fancybox来实现一下,这东西听起来简单,但做起来还真不是那么回事,有事细节需要我们注意的,首 ...
- JQuery请求WebService返回数据的几种处理方式
打开自己的博客仔细浏览了一番,发现已经好久没有写博客了,由于最近一直比较忙碌懈怠了好多.默默反省三分钟.......言归正传,现在就对最近在学习webservice的过程中遇到的几种类型的问题中我的理 ...
- Java基础知识强化之集合框架笔记05:Collection集合的遍历
1.Collection集合的遍历 Collection集合直接是不能遍历的,所以我们要间接方式才能遍历,我们知道数组Array方便实现变量,我们可以这样: 使用Object[] toArray() ...
- Thinkphp 3.2及以上版本实现支付宝担保交易、即时到账接口类、函数和使用方法
给客户开发网站时需要用到支付宝在线付款功能,小云到thinkphp网站溜了一圈,代码是有,可是都不怎么全,因此这篇文章诞生了! 本篇文章讲解了三个类的实现,担保交易.即时到账.双功能收款(该功能支付宝 ...