第五次重构我们引入了数据库的设计,用户信息要从数据库中读取,问候语库存储在数据库中,并支持添加与更新。数据库的引入使自动化测试变得困难了,因为数据状态总是变化着的,而这种变化使得测试过程不能复现,这是我们不愿看到的。因此,我们在设计时将业务与数据库访问分离,形成了UserDao与GreetingRuleDao。此时,我们的设计应当遵从“依赖反转”原则,即将UserDao与GreetingRuleDao设计成接口,并编写它们的实现UserDaoImpl与GreetingRuleDaoImpl。这样设计就为我们Mock掉UserDao与GreetingRuleDao的实现类创造了条件。

这是我们的设计:

图4.3 HelloWorld的设计图

为此,我们编写了这样的测试程序:

     private HelloWorld helloWorld = null;
private GreetingToUserImpl greetingToUser = null;
private GreetingAboutTimeImpl greetingAboutTime = null;
private final static List<GreetingRule> GREETING_RULES = getRules(); /**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
helloWorld = new HelloWorld();
greetingToUser = new GreetingToUserImpl();
greetingAboutTime = new GreetingAboutTimeImpl();
helloWorld.setGreetingToUser(greetingToUser);
helloWorld.setGreetingAboutTime(greetingAboutTime);
} /**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
helloWorld = null;
greetingToUser = null;
greetingAboutTime = null;
} /**
* Test method for {@link org...HelloWorld#sayHello(java.util.Date, java.lang.String)}.
*/
@Test
public void testSayHelloInTheMorning() {
final Date now = DateUtil.createDate(2013, 9, 7, 9, 23, 11);
final long userId = 2013090701; UserDao userDao = createMock(UserDao.class);
GreetingRuleDao greetingRuleDao = createMock(GreetingRuleDao.class);
expect(userDao.loadUser(userId)).andAnswer(new IAnswer<User>(){
@Override
public User answer() throws Throwable {
User user = new User();
user.setUserId(userId);
user.setName("鲍晓妹");
return user;
}});
expect(greetingRuleDao.findAllGreetingRules())
.andAnswer(new IAnswer<List<GreetingRule>>(){
@Override
public List<GreetingRule> answer() throws Throwable {
return GREETING_RULES;
}});
replay(userDao);
replay(greetingRuleDao); greetingToUser.setUserDao(userDao);
greetingAboutTime.setGreetingRuleDao(greetingRuleDao); String result = helloWorld.sayHello(now, userId);
Assert.assertEquals("Hi, 鲍晓妹. Good morning!", result);
verify(userDao);
verify(greetingRuleDao);
}

这段测试程序比较长,但其它部分都是打酱油的,核心是那个testSayHelloInTheMorning()用例,即问候早上好这个用例。userDao与greetingRuleDao是两个接口,我们在实例化它们的时候,并没有去创建它们的实现类,而是用Mock的方式进行创建:

     UserDao userDao = createMock(UserDao.class);
GreetingRuleDao greetingRuleDao = createMock(GreetingRuleDao.class);

随后我们开始定义它们的行为loadUser()与getAllGreetingRules()。在这个测试用例中,我们并不关心它们是怎样去数据库里查询数据并返回的,我们只关心它们是否得到应该得到的参数,并要求它们按照规定返回一个结果:

     final long userId = 2013090701;
expect(userDao.loadUser(userId)).andAnswer(new IAnswer<User>(){
@Override
public User answer() throws Throwable {
User user = new User();
user.setUserId(userId);
user.setName("鲍晓妹");
return user;
}});

我们希望被测程序在执行的时候调用了userDao.loadUser(userId),并且调用时传入的参数userId = 2013090701。如果测试过程中传入的参数是这个值,这一项检查点可以通过,否则不能通过。随后我们希望该函数返回“鲍晓妹”这个用户对象。通过Mock程序,我们完全将数据库访问的过程剥离在自动化测试之外,而只是验证它的输入参数,并指定测试所需的返回结果。也就是说数据访问过程被Mock掉,而大大降低了测试难度。

如果UserDao与GreetingRuleDao的Mock程序不能得到规定的参数时,测试就不能通过,这就是说传递给Mock程序的参数也成为了测试程序要验证的一个输出。随后,Mock程序返回规定值,该规定值则成为了被测程序的一个输入。最后,被测程序根据这个输入返回结果,为测试程序所验证,测试结束(如图4.4所示)。

图4.4 HelloWorld的自动化测试

图中的BUS层才是我们大量编码,应当自动化测试的部分。既然是测试就是验证怎样的输入,应当得到怎样的输出。Web层向BUS层发出的请求,即调用BUS层某个类的方法,就是测试用例中的一个输入,执行完该方法后的返回值就是测试用例的一个输出。但是,这对输入输出并不是该测试用例的全部。这里经过BUS层处理以后,经过一系列的逻辑判断和数据操作,随后会去调用DAO层进行数据访问操作。调用DAO层时所传递的参数,就是测试用例的另一个输出。图中,从DAO层的输入,到它的输出,这段数据库访问的过程被Mock程序Mock掉了,因为它不在这个用例的测试范围。然后DAO层返回给BUS层一个结果,该结果就是测试用例的另一个输入。接着BUS层会再次对这个返回结果进行处理,最后返回给Web层最终的结果。这就是采用Mock方式进行自动化测试的一个完整流程。

采用自动化测试,测试程序将不再验证后台的数据库是否正确,同时也不再验证前台的Web应用及其前端设备是否正确。在该例中,系统的真正目的是要在前台显示对用户的问候,因此将会有一个Action或Servlet调用HelloWorld:

     Date now = DateUtil.getNow();
String user = SessionUtil.getCurrentUser(session);
HelloWorld helloWorld = new HelloWorld();
String greeting = helloWorld.sayHello(now, user);
request.setAttribute(“greeting”, greeting);

然而,这些程序都不适合自动化测试而应采用手工测试。回顾HelloWorld自动化测试建立的过程我们不难发现,它在设计之初就实现了业务逻辑与Web应用、与数据访问的分离,所以它可以轻易的建立自动化测试。但是,不幸的是,我们大多数的遗留系统在设计之初都没有考虑到这些。因此,我说,在重构之初首先建立自动化测试机制是不现实的,我们只能采用手工测试结合QTP的方式。只有当我们通过重构,使系统架构满足自动化测试的条件之后,自动化测试才可以开展。

毫无疑问,测试与重构形成了一个“鸡生蛋,还是蛋生鸡”的怪圈,成为我们实践系统重构一大拦路虎。本书将在后面详细讨论这个话题(详见第十六章 测试的困境),为你破解这个谜团。

大话重构连载首页:http://www.cnblogs.com/mooodo/p/talkAboutRefactoringHome.html

特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!

大话重构连载15:采用Mock技术完成测试的更多相关文章

  1. 一个简单的使用mock技术进行测试的例子

    Account: public class Account { private String acountId; private long balance; public Account(String ...

  2. 使用模拟对象(Mock Object)技术进行测试驱动开发

    敏捷开发 敏捷软件开发又称敏捷开发,是一种从上世纪 90 年代开始引起开发人员注意的新型软件开发方法.和传统瀑布式开发方法对比,敏捷开发强调的是在几周或者几个月很短的时间周期,完成相对较小功能,并交付 ...

  3. NS实现采用的技术大多是PHP,如果采用java、 .net是否同样适用?

    SNS采用的技术可不都是PHP (不局限于国内),特别是国外的新兴公司,基本上没有再用PHP的了,国内到还是蛮常用的.简单说说我知道的几个案例:Facebook (PHP):Facebook采用PHP ...

  4. 采用WPF技术,开发OFD电子文档阅读器

    前言 OFD是国家标准版式文档格式,于2016年生效.OFD文档国家标准参见<电子文件存储与交换格式版式文档>.既然是国家标准,OFD随后肯定会首先在政务系统使用,并逐步推向社会各个方面. ...

  5. [转帖]英特尔首款采用10nm技术的混合CPU“Lakefield”即将发布

    英特尔首款采用10nm技术的混合CPU“Lakefield”即将发布 intel 也出soc了 里面的东西 跟 安卓和 apple的a系列很像. https://baijiahao.baidu.com ...

  6. Java采用反射技术创建对象后对目标类的成员变量和成员方法进行访问

    实现: package com.ljy; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * * @Class ...

  7. Scrum敏捷软件开发之技术实践——测试驱动开发TDD

    重复无聊的定义 测试驱动开发,英文全称Test-Driven Development,简称TDD,是一种不同于传统软件开发流程的新型的开发方法.它要求在编写某个功能的代码之前先编写测试代码,然后只编写 ...

  8. 采用jmeter和泛化测试dubbo服务接口

    采用jmeter和泛化测试dubbo服务接口 http://blog.csdn.net/linuu/article/details/54313560

  9. Deepin 15.4 如何使用 罗技无线键盘/鼠标(采用优联技术)

    1.罗技的“无线优联技术”还是非常强大的,它跟具体的操作系统无关: 2.你只需要 让“优联接收器(一个USB设备)”跟 “无线键盘/鼠标” 配对即可,配对完之后,就无需再配对,即使把“优联接收器”插到 ...

随机推荐

  1. [20171206]rman与truncate.txt

    [20171206]rman与truncate.txt --//昨天下班在回家的路上,突然想起以前遇到的问题,就是truncate表后,rman做备份时会备份多少truncate表的信息,--//当时 ...

  2. Asp.net Mvc、webApi配置允许跨域

    Web.config 下<system.webServer> 节点下配置 <httpProtocol> <customHeaders> <add name=& ...

  3. DMZ 区域

    下面对DMZ区域进行简要介绍:DMZ是网络的一个区域,介于外网与内网之间的一个特殊区域,也称隔离区.它不同于传统的防火墙设置,DMZ防火墙方案为要保护的内部网络增加了一道安全防线,通常认为是非常安全的 ...

  4. cc1plus.exe: error: unrecognized command line option "-fno-keep-inline-dllexport "

    在Windows环境下的控制台上,通过qmake指令编译Qt程序时,出现 cc1plus.exe: error: unrecognized command line option "-fno ...

  5. 【hexo】01安装

    什么是 Hexo? Hexo 是一个快速.简洁且高效的博客框架.Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页. 安装前提 安装 Hexo 相当简 ...

  6. ccf--20151203--画图

    本题思路如下: 题目和代码如下: 问题描述 试题编号: 201512-3 试题名称: 画图 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 用 ASCII 字符来画图是一件有趣 ...

  7. python3的C3算法

    一.基本概念 1. mro序列 MRO是一个有序列表L,在类被创建时就计算出来. 通用计算公式为: mro(Child(Base1,Base2)) = [ Child ] + merge( mro(B ...

  8. 一次ASM磁盘空间假装耗尽 ORA-15041: DISKGROUP SPACE EXHAUSTED

    给ASM磁盘新增一块盘进去,ASM_DISK2剩余空间四百多G: SQL> select * from v$asm_diskgroup;   GROUP_NUMBER NAME         ...

  9. Scout YYF I POJ - 3744(矩阵优化)

    题意:一条路上有n个地雷,给出地雷的位置.某人从起点(位置1)出发,走一步的概率是p,走两步的概率是(1-p),然后问有多少概率走过这个雷区. 思路: 只要走过最后一个地雷就代表走过雷区了. 而每到 ...

  10. rac添加新节点的步骤与方法2

    上一篇文章,把节点删除了.这次新增加一个节点 .新增加的节点是host03.如下: #Public IP192.168.16.45 racdb1192.168.16.46 racdb2192.168. ...