根据iBatis的JPetStore学习测试示例的写法
JpetStore提供了一个整套的测试代码对于想进行单元测试却不知道如何进行单元测试的人(me)来说,是一个很好的学习机会。
JpetStore的单元测试代码与它的系统源文件是独立分离的,在test文件夹中。打开文件夹可以很清楚的发现该文件夹的内容组成结构与源文件基本一致。
JpetStore分为领域层(domain),持久层(peristence),服务层(service),表现层(presentation),它的测试也分这几个层来进行。下面就按层来学习它的test。
1、 领域层(domain)
由于领域层的代码主要是用于在各层之间传递的类,所以,此层的测试代码也比较简单,主要有两个测试类,BeanTest.java与DomainFixture.java。
BeanTest.java类主要是利用包com.ibatis.common.beans的功能,来完成系统中所有bean(领域层的类与表现层的类)get与set方法的测试,避免遗漏。
DomainFixture.java类主要是构造两个用于其他层进行测试的领域实体。
2、 持久层(persistence)
持久层主要是测试访问数据库的代码功能。主要可以分成三个类:PersistenceFixture、BasePersistenceTest和各种实际测试的类如:AccountDaoTest。
PersistenceFixture类,这个类完成所有持久层代码的数据库的链接。
BasePersistenceTest类,所有持久层类的父类,引用类PersistenceFixture,构造一个DaoManager的实例,用于取得各实际测试类的Dao。
AccountDaoTest类,具体测试持久层的类,通过一下语句得到dao,然后进行测试。
private AccountDao acctDao = (AccountDao)daoMgr.getDao(AccountDao.class);
3、 服务层(service)
服务层由于需调用持久层来完成测试功能,所以,采用模拟对象(Mock Object)来完成。模拟对象的背后目的就是创建一个轻量级的、可控制的对象来代替测试中需要的真实对象,模拟真实对象的行为和功能,方便进行分层测试。JpetStore主要采用的是JMock。具体的介绍见(http://www.jetmaven.net/contents/documents/j_jMock_intro.php)
public
class
AccountServiceTest
extends
MockObjectTestCase
{
public void testShouldVerifyGetAccountIsCalledByUsername() {
Mock mock = mock(AccountDao.class);//创建模拟对象
//定义所要模拟的函数的具体信息
mock.expects(once())//模拟对象要执行的次数
.method("getAccount")//模拟对象要执行的函数
.with(NOT_NULL)//函数的入口参数
.will(returnValue(new Account()));//函数的返回参数
AccountService accountService = new AccountService((AccountDao) mock.proxy());
//根据模拟对象的代理完成模拟对象与实际对象的关联
accountService.getAccount("cbegin");
}
}
上面是服务层AccountService的其中一个函数的测试代码。所有使用JMock的类必须继承于MockObjectTestCase。它的具体使用方法已经在备注中写明。
public void testShouldVerifyGetAccountIsCalledByUsername() {
Mock mock = mock(AccountDao.class);//创建模拟对象
//定义所要模拟的函数的具体信息
mock.expects(once())//模拟对象要执行的次数
.method("getAccount")//模拟对象要执行的函数
.with(NOT_NULL)//函数的入口参数
.will(returnValue(new Account()));//函数的返回参数
AccountService accountService = new AccountService((AccountDao) mock.proxy());
//根据模拟对象的代理完成模拟对象与实际对象的关联
accountService.getAccount("cbegin");
}
}
4、 表现层(presentation)
表现层和服务层的测试方法大致相同。首先通过模拟对象(mock object)模拟服务层,然后调用模拟对象完成实际的功能测试。下面是一个典型的bean的测试代码
public
class
AccountBeanTest
extends
MockObjectTestCase
{
public void testShouldSuccessfullyCallServicesToCreateNewAccount() {
Account account = DomainFixture.newTestAccount();
Mock accountServiceMock = mock(AccountService.class);
accountServiceMock.expects(once())
.method("insertAccount")
.with(NOT_NULL);
accountServiceMock.expects(once())
.method("getAccount")
.with(NOT_NULL)
.will(returnValue(account));
Mock catalogServiceMock = mock(CatalogService.class);
catalogServiceMock.expects(once())
.method("getProductListByCategory")
.with(NOT_NULL)
.will(returnValue(new PaginatedArrayList(5)));
AccountBean accountBean = new AccountBean((AccountService)accountServiceMock.proxy(), (CatalogService)catalogServiceMock.proxy());
accountBean.setAccount(account);
String result = accountBean.newAccount();
assertEquals(AbstractBean.SUCCESS, result);
}
}
public void testShouldSuccessfullyCallServicesToCreateNewAccount() {
Account account = DomainFixture.newTestAccount();
Mock accountServiceMock = mock(AccountService.class);
accountServiceMock.expects(once())
.method("insertAccount")
.with(NOT_NULL);
accountServiceMock.expects(once())
.method("getAccount")
.with(NOT_NULL)
.will(returnValue(account));
Mock catalogServiceMock = mock(CatalogService.class);
catalogServiceMock.expects(once())
.method("getProductListByCategory")
.with(NOT_NULL)
.will(returnValue(new PaginatedArrayList(5)));
AccountBean accountBean = new AccountBean((AccountService)accountServiceMock.proxy(), (CatalogService)catalogServiceMock.proxy());
accountBean.setAccount(account);
String result = accountBean.newAccount();
assertEquals(AbstractBean.SUCCESS, result);
}
}
通过学习JPetStoreShop的测试代码,基本上可以学到这样几点
1) 测试代码与实际代码分离
2) 测试代码与实际代码的组成结构相同,易于查看
3) 测试代码要分层进行
4) 领域层中的BeanTest.java类可以复用
5) 持久层的测试代码类的关系可以学习
6) 服务层的Jmock中模拟对象(mock object)的使用方法
本文只是简单的对JpetStoreShop中的代码进行了分析,包括测试代码的组成以及基本关系,并未细化到测试代码要测试哪些点。具体的测试点还需要参考其他书籍进行学习。关于Jmock的学习见http://www.jetmaven.net/contents/documents/j_jMock_intro.php
关于测试的心得见http://www.blogjava.net/AndersLin/archive/2006/06/12/52298.html