转自:黑客日
编译:https://www.baeldung.com/testing-the-java-service-layer
1、简述
有很多方法可以测试Service层。本文的目标是展示一种隔离的测试方式:将于数据库交互的整个逻辑都Mock掉。
示例使用了Spring作为依赖注入、JUnit、Hamcrest、Mockito。
2、分层
典型的Java web应用都在DAO/DAL层之上有一个服务层,DAO/DAL层负责调用原生的持久层逻辑。
Service层
| 01 02 03 04 05 06 07 08 09 10 11 12 | @Service public class FooService implements IFooService{ @Autowired IFooDAO dao; @Override public Long create( Foo entity ){ return this.dao.create( entity ); } } |
DAO/DAL层
| 01 02 03 04 05 06 07 08 09 10 11 12 13 | @Repository public class FooDAO extends HibernateDaoSupport implements IFooDAO{ public Long create( Foo entity ){ Preconditions.checkNotNull( entity ); return (Long) this.getHibernateTemplate().save( entity ); } } |
3、单元测试的动机和关注点
当单元测试的对象是一个服务的时候,“单元”就是Service类,测试需要模拟Service其下的层,这里就是模拟DAO/DAL层,并验证与之的交互操作。
同样地,对DAO/DAL层的单元测试来说,要模拟掉数据库层(本例中就是HibernateTemplate),并验证相关交互操作。
这个方法是 有效的,但是这样的测试是脆弱的。增加或移除一层的话,就需要完全重写测试。因为测试依赖了层的具体结构,层的变化就意味着测试的变化。
为了避免这种不灵活,我们通过延展“单元”的定义从而扩展单元测试的范围。我们可以把Service层通过DAO和其他方式穿透到持久层的操作看做一个“单元”。单元测试就针对Service层的API,模拟掉原生的持久化逻辑。本例里面就是HibernateTemplate。
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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() ) ); } } |
现在测试只关注Service类的一个职责:当创建被触发,相关操作是否传递到了数据库。
例子里面用Mockito框架的验证语法来检查HiebernateTemplate的save方法是否被调用。测试信任hibernate的保存逻辑,因此创建实体的职责只需要像这样验证save方法是否被调用。
当然保存逻辑也需要被测试,但是这是其他测试的范畴。
4、结论
这种方式会产出更多具有弹性和灵活性的测试。测试失败唯一的原因就是被测试逻辑之下的职责失败了。比如HibernateTemplate的save方法。