目录

  • 1. 概述
  • 2. 层次结构
  • 3. 动机与模糊单元测试的界限
  • 4. 结论

1. 概述

测试应用程序的服务层有许多方法。本文的目的是展示一种在隔离状态下单独测试此层的方法,完全模拟与数据库的交互。

本示例将使用 Spring 进行依赖注入,使用 JUnit、HamcrestMockito 进行测试,但具体技术可以根据需要变化。

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. 结论

这种方法始终导致更专注于细节的测试,使其更具弹性和适应变化。测试失败的唯一原因是正在测试的责任已损坏。


« 上一篇: Pattern Matching in Scala
» 下一篇: 用Java测试REST API