第五次重构我们引入了数据库的设计,用户信息要从数据库中读取,问候语库存储在数据库中,并支持添加与更新。数据库的引入使自动化测试变得困难了,因为数据状态总是变化着的,而这种变化使得测试过程不能复现,这是我们不愿看到的。因此,我们在设计时将业务与数据库访问分离,形成了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. Vue2 框架开发的单页程序页面首次加载慢的原因与优化方案

    在用Vue2 框架进行单页面开发时,开发完成后项目打包到线上环境,发现vendor脚本有963K,app.css文件也有四百多k,用户第一次打开网页加载这两个文件要十多秒,会使页面白屏十多秒,之后再次 ...

  2. Visual Stuido插件大全

    JS Enhancements 使用JS能像C#代码一样折叠成块 Code Compare Code Compare is a powerful file and folder comparison ...

  3. ccf-20160903--炉石传说

    本题思路如下图: 题目和代码如下: 问题描述 试题编号: 201609-3 试题名称: 炉石传说 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 <炉石传说:魔兽英雄传& ...

  4. IDEA设置注释的颜色

    IDEA默认的灰色注释确实让人看不清,但如果把灰色调成黑色又和代码的颜色相同了,所以,不如给注释添加上绿色的背景,又护眼又容易分辨 新版本的IDEA打开Settings——Editot——Color ...

  5. pThreads线程(一) 基本API

    1.创建线程  int pthread_create(pthread_t *restrict_ptid,              const pthread_attr_t *restrict_att ...

  6. js从数组中删除指定值(不是指定位置)的元素

    RT: js从数组中删除指定值的元素,注意是指定值,而不是指定位置. 比如数组{1,2,3,4,5},我要删除其中的元素3,但是这个3的位置我是不知道的,只知道要删除值为3的这一个元素,请问要怎么写? ...

  7. 解决jqueryeasyUI dialog 弹出窗体超出浏览器,导致不能关闭的bug

    使用panel的onMove事件攻克了panel,dialog以及window组件在被拖动时,会超出浏览器边界而无法拖回的情况. 当窗体被拖出浏览器有边界时.$(document).width();会 ...

  8. Java关于ReentrantLock获取锁和释放锁源码跟踪

    通过对ReentrantLock获取锁和释放锁源码跟踪主要想进一步深入学习AQS. 备注:AQS中的waitStatus状态码含义:

  9. remove-duplicates-from-sorted-list (删除)

    题意略: 思路:先造一个点它与所有点的值都不同,那么只要后面两个点的值相同就开始判断后面是不是也相同,最后将相同的拆下来就可以了. #include<iostream> #include& ...

  10. Blinker 后台数据分析

    如何解析出后台服务器认证信息,供自己的设备连接. 测试程序 天气 增加了 Debug输出信息功能 1手机APP添加控件信息   2硬件烧录程序 #define BLINKER_PRINT Serial ...