案例概述
有许多方法可以测试应用程序的Service层。本文的目标是通过模拟完全与数据库的交互来展示单独测试该层的一种方法。
这个例子将使用Spring进行依赖注入,JUnit,Hamcrest和Mockito进行测试,但技术可能会有所不同。
案例分层
典型的Java Web应用程序将在DAL/DAO层之上具有服务层,该层又将调用原始持久层。
Service层
@Service
public class FooService implements IFooService{
@Autowired
IFooDAO dao;
@Override
public Long create( Foo entity ){
return this.dao.create( entity );
}
}
DAL/DAO层
@Repository
public class FooDAO extends HibernateDaoSupport implements IFooDAO{
public Long create( Foo entity ){
Preconditions.checkNotNull( entity );
return (Long) this.getHibernateTemplate().save( entity );
}
}
模糊的单元测试和动机
当我们进行测试服务时,标准测试通常是服务类。测试将模拟下面的方式 - 在这种情况下DAO/DAL层验证与其上面的交互。DAO层做同样的事情 - 模拟与数据库的交互(本例使用HibernateTemplate)并验证。
这是一种有效的方法,但它会导致脆性测试 - 添加或删除图层几乎总是意味着完全重写测试。发生这种情况是因为测试依赖于层的确切结构,并且对此的更改意味着对测试的更改。
为了避免僵化,我们可以通过更改单元的定义来扩大单元测试的范围 - 我们可以将持久操作视为一个单元,从Service层到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验证语法来检查是否已在hibernate模板上调用save方法,捕获过程中的参数以便也可以检查它。通过此交互测试验证创建实体的责任,而无需检查任何状态 - 测试信任hibernate保存逻辑并预期工作。当然,这也需要进行测试,但这是另一种单元和另一种类型的测试。
案例结论
这种技术总是会出现更集中的测试,这使得它们更具弹性和灵活性。测试失败的唯一原因是因为测试中的责任被打破了。
评论区