技术开发 频道

使用JMock来实现孤立测试

    根据测试先行的原则,你应该先写测试,再编写实现。这里先编写实现的原因,主要是使读者更加清楚我们接着要测试什么。由于本文是着重介绍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 com.sarkuya.service;
import com.sarkuya.dao.UserDAO;
import com.sarkuya.model.User;
import com.sarkuya.service.impl.UserServiceImpl;
import junit.framework.*;
import org.jmock.Mock;
import org.jmock.MockObjectTestCase;
public class UserServiceTest extends MockObjectTestCase {
    private UserService userService = new UserServiceImpl();

        private Mock userDAO = null;

        public UserServiceTest(String testName) {
        super(testName);
    }

        protected void setUp() throws Exception {
        userDAO = new Mock(UserDAO.class);
        userService.setUserDAO((UserDAO)userDAO.proxy());
    }

        protected void tearDown() throws Exception {
    }

    public static Test suite() {
        TestSuite suite = new TestSuite(UserServiceTest.class);

        return suite;
    }

        public void testGetUser() {
        User fakeUser = new User("John");
        userDAO.expects(once()).method("getUser").with(eq(1L)).will(returnValue(fakeUser));

        User user = userService.getUser(1L);
        assertNotNull(user);
        assertEquals("John", user.getName());
    }

        public void testSaveUser() {
        User fakeUser = new User("John");

        userDAO.expects(once()).method("getUser").with(eq(1L)).will(returnValue(fakeUser));
        User user = userService.getUser(1L);
        assertEquals("John", user.getName());

        userDAO.expects(once()).method("saveUser").with(same(fakeUser));
        user.setName("Mike");
        userService.saveUser(user);

        userDAO.expects(once()).method("getUser").with(eq(1L)).will(returnValue(user));
        User modifiedUser = userService.getUser(1L);
        assertEquals("Mike", user.getName());
    }
}

    此段代码有几点应注意:

    1、此测试类继承了JMock的MockObjectTestCase

    2、private Mock userDAO = null;说明userDao是一个准备虚拟的对象

    3、在setup()中,将userDAO.class传入Mock()后,再通过proxy()方法返回一个UserDAO的代理类实例(即虚拟对象实例),并赋值于userService

    4、在testGetUser()方法中,如果我们先将第一行及第二行代码屏蔽掉,可以看出,这是一个真实环境下的测试代码。先获取一个User,然后确认其非空值,再确认其姓名为“John”。此时,在真实环境下,这段代码要测试成功的前提必须是UserDAO已经连接到了数据库,然后返回一个User后传给UserService。

    但问题是,到目前为止,且不说UserDAO还未经历连接数据库这一系列繁琐而痛苦的过程,我们甚至还未实现UserDAO的接口!那么,为何加上第一行及第二行代码后就可以了呢?这正是JMock的威力所在。先实例化一个测试用的fakeUser,然后通过一系列的指令,在第二行代码中告诉JMock应该如何“做假”。尽管这句代码很长,我们可作如下理解:

    1) userDAO.expects(once()):我们期望userDAO的某方法被执行一次,如果此方法未被执行,或者执行了二次以上,测试就不会通过

    2) method("getUser"):这个期望被执行一次的方法名为userDAO.getUser()

    3) with(eq(1L)):执行getUser()方法时,确认其传入的参数值为“1L”

    4) will(returnValue(fakeUser)):上述条件均满足后,返回一个虚假的对象,即我们前面实例化的fakeUser

    总体来说,当设定好第二行语句后,JMock就在后台监控着,确保userDAO.getUser()必须,且只被执行一次,且参数“1L”已经正确地传给了此方法,一旦这些条件被满足,就返回fakeUser。

    而在第三行,User user = userService.getUser(1L)将触发所有这些条件,作为奖励,它接受了奖品fakeUser并赋值于user对象。而下面第四行及第五行均对此user对象进行测试,不通过才怪。

    5) testSaveUser()方法中的原理类似。其思路是,将id为“1”的user从数据库中取出,将其名改为“Mike”,再存回数据库,然后再从数据库中取出此user,确保其名字已被改变。

    第五行userDAO.expects(once()).method("saveUser").with(same(fakeUser))比较特殊。首先,with(same(fakeUser))说明,传入参数必须是fakeUser此实例,尽管我们在下面的语句中通过user.setName("Mike"),但只是改变了其name的属性,而fakeUser的实例引用并未发生改变,因此可以满足条件。其次,其后没有.will(returnValue(fakeUser)),因为userDAO.saveUser()不需要返回任何对象或基本数据类型。

    另外,当再次执行userDAO.expects()时,JMock将重设其监控条件。我们也可以通过userDAO.reset()来显式是清除监控条件。

    通过以上实例代码及其说明,我们看出,用好JMock的关键是先设置监控条件,再写相应的测试语句。一旦设好监控条件后,在某段代码块执行完毕时,如果监控条件未得到满足,或是没有通过expects()再次重设条件,或通过reset()来显式是清除监控条件,测试将无法通过。

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

0
相关文章