• 大话重构连载15:采用Mock技术完成测试


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

    这是我们的设计:

    图4.3 HelloWorld的设计图

     

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

     1     private HelloWorld helloWorld = null;
     2     private GreetingToUserImpl greetingToUser = null;
     3     private GreetingAboutTimeImpl greetingAboutTime = null;
     4     private final static List<GreetingRule> GREETING_RULES = getRules();
     5     
     6     /**
     7      * @throws java.lang.Exception
     8      */
     9     @Before
    10     public void setUp() throws Exception {
    11         helloWorld = new HelloWorld();
    12         greetingToUser = new GreetingToUserImpl();
    13         greetingAboutTime = new GreetingAboutTimeImpl();
    14         helloWorld.setGreetingToUser(greetingToUser);
    15         helloWorld.setGreetingAboutTime(greetingAboutTime);
    16     }
    17     
    18     /**
    19      * @throws java.lang.Exception
    20      */
    21     @After
    22     public void tearDown() throws Exception {
    23         helloWorld = null;
    24         greetingToUser = null;
    25         greetingAboutTime = null;
    26     }
    27 
    28     /**
    29      * Test method for {@link org...HelloWorld#sayHello(java.util.Date, java.lang.String)}.
    30      */
    31     @Test
    32     public void testSayHelloInTheMorning() {
    33         final Date now = DateUtil.createDate(2013, 9, 7, 9, 23, 11);
    34         final long userId = 2013090701;
    35         
    36         UserDao userDao = createMock(UserDao.class);
    37         GreetingRuleDao greetingRuleDao = createMock(GreetingRuleDao.class);
    38         expect(userDao.loadUser(userId)).andAnswer(new IAnswer<User>(){
    39             @Override
    40             public User answer() throws Throwable {
    41                 User user = new User();
    42                 user.setUserId(userId);
    43                 user.setName("鲍晓妹");
    44                 return user;
    45             }});
    46         expect(greetingRuleDao.findAllGreetingRules())
    47         .andAnswer(new IAnswer<List<GreetingRule>>(){
    48             @Override
    49             public List<GreetingRule> answer() throws Throwable {
    50                 return GREETING_RULES;
    51             }});
    52         replay(userDao);
    53         replay(greetingRuleDao);
    54         
    55         greetingToUser.setUserDao(userDao);
    56         greetingAboutTime.setGreetingRuleDao(greetingRuleDao);
    57         
    58         String result = helloWorld.sayHello(now, userId);
    59         Assert.assertEquals("Hi, 鲍晓妹. Good morning!", result);
    60         verify(userDao);
    61         verify(greetingRuleDao);
    62     }

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

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

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

    1     final long userId = 2013090701;
    2     expect(userDao.loadUser(userId)).andAnswer(new IAnswer<User>(){
    3             @Override
    4             public User answer() throws Throwable {
    5                 User user = new User();
    6                 user.setUserId(userId);
    7                 user.setName("鲍晓妹");
    8                 return user;
    9             }});

    我们希望被测程序在执行的时候调用了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:

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

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

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

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

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

  • 相关阅读:
    git初学【常用命令、上传项目到码云或从码云拉取、克隆项目】
    dedecms自学
    sublime3使用笔记
    路由功能
    bootstrap模态框篇【遇到的问题】
    justgage.js的使用
    fullpage.js使用方法
    js&jq遇到的问题(不断更新中)
    图灵完备——停机问题
    中断
  • 原文地址:https://www.cnblogs.com/mooodo/p/testWithMock.html
Copyright © 2020-2023  润新知