目录
- 1. 概述
- 2. 层次结构
- 3. 动机与模糊单元测试的界限
- 4. 结论
1. 概述
测试应用程序的服务层有许多方法。本文的目的是展示一种在隔离状态下单独测试此层的方法,完全模拟与数据库的交互。
本示例将使用 Spring 进行依赖注入,使用 JUnit、Hamcrest 和 Mockito 进行测试,但具体技术可以根据需要变化。
2. 层次结构
典型的 Java Web 应用程序通常在数据访问/数据访问对象 (DAL/DAO) 层之上有一层服务层,而这一层又会调用底层的原始持久层。
2.1. 服务层
@Service
public class FooService implements IFooService{
@Autowired
IFooDAO dao;
@Override
public Long create( Foo entity ){
return this.dao.create( entity );
}
}
2.2. DAL/DAO 层
@Repository
public class FooDAO extends HibernateDaoSupport implements IFooDAO{
public Long create( Foo entity ){
Preconditions.checkNotNull( entity );
return (Long) this.getHibernateTemplate().save( entity );
}
}
3. 动机与模糊单元测试的界限
在测试服务时,通常的 单元 是服务 类 ,就这么简单。测试会模拟其下层的交互 - 在这个例子中是 DAO/DAL 层,并验证其上的交互。对于 DAO 层也是一样 - 模拟与数据库(例如 HibernateTemplate)的交互并验证与之的交互。
这是一种有效的方法,但会导致脆弱的测试 - 几乎任何添加或删除一层都可能意味着完全重写测试。这是因为测试依赖于层的精确结构,而对这种结构的任何改变都意味着测试的更改。
为了避免这种不灵活性,我们可以通过改变单元测试的范围来扩展其定义 - 我们可以将持久操作视为一个单元,从服务层到 DAO,再到最底层的持久层。现在,单元测试将消费服务层的 API,并将底层持久性(如 HibernateTemplate)模拟出来:
public class FooServiceUnitTest{
FooService instance;
private HibernateTemplate hibernateTemplateMock;
@Before
public void before(){
this.instance = new FooService();
this.instance.dao = new FooDAO();
this.hibernateTemplateMock = mock( HibernateTemplate.class );
this.instance.dao.setHibernateTemplate( this.hibernateTemplateMock );
}
@Test
public void whenCreateIsTriggered_thenNoException(){
// When
this.instance.create( new Foo( "testName" ) );
}
@Test( expected = NullPointerException.class )
public void whenCreateIsTriggeredForNullEntity_thenException(){
// When
this.instance.create( null );
}
@Test
public void whenCreateIsTriggered_thenEntityIsCreated(){
// When
Foo entity = new Foo( "testName" );
this.instance.create( entity );
// Then
ArgumentCaptor< Foo > argument = ArgumentCaptor.forClass( Foo.class );
verify( this.hibernateTemplateMock ).save( argument.capture() );
assertThat( entity, is( argument.getValue() ) );
}
}
现在,测试只关注单一职责 - 当创建触发时,创建是否到达了数据库?
最后一个测试使用 Mockito 验证语法检查 save
方法是否被调用了 Hibernate 模板,并在此过程中捕获参数以便进行检查。通过这种交互测试,验证创建实体的责任,而无需检查任何状态 - 测试信任 Hibernate 的保存逻辑按预期工作。当然,这也需要测试,但这又是另一个责任和另一种类型的测试。
4. 结论
这种方法始终导致更专注于细节的测试,使其更具弹性和适应变化。测试失败的唯一原因是正在测试的责任已损坏。