JMock来实现孤立测试(转)

JMock是帮助创建mock对象的工具,它基于Java开发,在Java测试与开发环境中有不可比拟的优势,更重要的是,它大大简化了虚拟对象的使用。本文中,通过一个简单的测试用例来说明JMock如何帮助我们实现这种孤立测试。

    我们在测试某类时,由于它要与其他类发生联系,因此往往在测试此类的代码中也将与之联系的类也一起测试了。这种测试,将使被测试的类直接依赖于其他类,一旦其他类发生改变,被测试类也随之被迫改变。更重要的是,这些其他类可能尚未经过测试,因此必须先测试这些类,才能测试被测试类。这种情况下,测试驱动开发成为空谈。而如果其他类中也引用了被测试类,我们到底先测试哪一个类?因此,在测试中,如果我们能将被测试类孤立起来,使其完全不依赖于其他类的具体实现,这样,我们就能做到测试先行,先测试哪个类,就先实现哪个类,而不管与之联系的类是否已经实现。

    虚拟对象(mock object)就是为此需要而诞生的。它通过JDK中的反射机制,在运行时动态地创建虚拟对象。在测试代码中,我们可以验证这些虚拟对象是否被正确地调用了,也可以在明确的情况下,让其返回特定的假想值。而一旦有了这些虚拟对象提供的服务,被测试类就可以将虚拟对象作为其他与之联系的真实对象的替身,从而轻松地搭建起一个很完美的测试环境。

    JMock是帮助创建mock对象的工具,它基于Java开发,在Java测试与开发环境中有不可比拟的优势,更重要的是,它大大简化了虚拟对象的使用。

    本文中,通过一个简单的测试用例来说明JMock如何帮助我们实现这种孤立测试。有三个主要的类,User,UserDAO,及UserService。本文中,我们只需测试UserService,准备虚拟UserDAO。对于User,由于本身仅是一个过于简单的POJO,可以不用测试。但如果你是一个完美主义者,也可以使用JMock的虚拟它。在这领域,JMock几乎无所不能。:)

    User是一个POJO,用以在视图中传输数据及映射数据库。其代码如下:

package com.sarkuya.model;

public class User {
    private String name;

    public User() {
    }
    
    public User(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}




    UserDAO负责与数据库打交道,通过数据库保存、获取User的信息。尽管我们可以不用知道JMock如何通过JDK的反射机制来实现孤立测试,但至少应知道,JDK的反射机制要求这些在运行时创建的动态类必须定义接口。在使用JMock的环境中,由于我们要虚拟UserDAO,意味着UserDAO必须定义接口。代码如下:

package com.sarkuya.dao;

import com.sarkuya.model.User;

public interface UserDAO {
    public void saveUser(User user);
    public User getUser(Long id);
}

UserService存有UserDAO的引用,通过其对外提供应用级的服务。相应地,我们先定义了其接口(尽管在本文中,作为被测试类,UserService不需要有接口,但如果以后此类需要被虚拟,也应该带有接口,基于此原因,我们也为其定义了接口)。

package com.sarkuya.service;

import com.sarkuya.dao.UserDAO;
import com.sarkuya.model.User;

public interface UserService {
    public void setUserDAO(UserDAO userDAO);
    
    public void saveUser(User user);
    public User getUser(Long id);
}



    可以看到,除了setUserDAO()外,其另外的方法与UserDAO一样。这是设计模式中门面模式的典型应用,应用只通过UserService提供服务,而UserService在内部通过调用UserDAO来实现相应的功能。

    根据测试先行的原则,你应该先写测试,再编写实现。这里先编写实现的原因,主要是使读者更加清楚我们接着要测试什么。由于本文是着重介绍JMock的使用,加上UserServiceImpl比较简单,因此先列出其代码如下:

package com.sarkuya.service.impl;

import com.sarkuya.dao.UserDAO;
import com.sarkuya.model.User;
import com.sarkuya.service.UserService;

public class UserServiceImpl implements UserService {
    private UserDAO userDAO;
    
    public UserServiceImpl() {
    }

    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

    public User getUser(Long id) {
        return userDAO.getUser(id);
    }

    public void saveUser(User user) {
        userDAO.saveUser(user);
    }
}



    下面是UserService的测试代码:

package jmock;
import static org.testng.AssertJUnit.*;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Test;
 
public class UserServiceTest {
 
 Mockery context = new Mockery();
 UserDao userDao = null;
 private UserService userService = new UserServiceImp();
 
 @BeforeSuite
 protected void setUp() {   
  userDao = context.mock(UserDao.class);   
  userService.setUserDao(userDao);
    }   
 
 @Test
 public void testGetUser() {
        final User fakeUser = new User("John");
       
        //expectations
        context.checking(new Expectations() {{
            one (userDao).getUser(1l);
            will (returnValue(fakeUser));
        }});              
       
        User user = userService.getUser(1L);
        assertNotNull(user);
        assertEquals("John", user.getName());
    }
 
 @Test
 public void testSaveUser(){
  final User fakeUser = new User("John");
       
        //expectations
  context.checking(new Expectations() {{
            one (userDao).getUser(1l);
            will (returnValue(fakeUser));
        }});              
       
        final User user = userService.getUser(1L);
        assertEquals("John", user.getName());
       
      
      //expectations
  context.checking(new Expectations() {{
            one (userDao).saveUser(fakeUser);           
        }});
  
   user.setName("eafy");
   userService.saveUser(user);
  
  //expectations
  context.checking(new Expectations() {{
             one (userDao).getUser(1l);
             will (returnValue(user));
     }});
  
  User modifiedUser = userService.getUser(1L);
        assertEquals("eafy", modifiedUser.getName());       
        
 }
}


    此段代码有几点应注意:

1、此测试类采用testng

2、Mockery context = new Mockery();jmock2.0 采用Mockery
3、在setup()中,将userDAO.class传入context.Mock()后
    通过以上实例代码及其说明,我们看出,用好JMock的关键是先设置监控条件,再写相应的测试语句。一旦设好监控条件后,在某段代码块执行完毕时,如果监控条件未得到满足,或是没有通过expects()再次重设条件,测试将无法通过。
4、context.checking(new Expectations() {{
            one (userDao).getUser(1l);
            will (returnValue(fakeUser));
        }});表示测试一次,返回User对象
5、testSaveUser中首先保存一个用户对象,再修改属性,再判断修改后的值,是否正确


    以上介绍了JMock的基本使用方法。而这种基本用法,占了全面掌握JMock所需学习的知识70%以上。关于JMock的更多细节,感兴趣的读者可以访问JMock的网站进一步学习。

(做了部分修改)
posted @ 2008-07-03 15:45  eafy.ye  阅读(581)  评论(0编辑  收藏  举报