Перейти к основному содержимому

Избегайте хрупких тестов для сервисного уровня

· 3 мин. чтения

Оглавление

1. Обзор

Существует много способов протестировать сервисный уровень приложения. Цель этой статьи — показать один из способов изолированного модульного тестирования этого уровня, полностью имитируя взаимодействие с базой данных.

В этом примере будет использоваться Spring для внедрения зависимостей, JUnit, Hamcrest и Mockito для тестирования, но технологии могут различаться.

2. Слои ** **

Типичное веб-приложение Java будет иметь сервисный уровень поверх уровня DAL/DAO, который, в свою очередь, будет вызывать необработанный уровень сохраняемости.

1.1. Сервисный уровень

@Service
public class FooService implements IFooService{

@Autowired
IFooDAO dao;

@Override
public Long create( Foo entity ){
return this.dao.create( entity );
}

}

1.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, чтобы проверить, был ли вызван метод сохранения в шаблоне гибернации, фиксируя аргумент в процессе, чтобы его также можно было проверить. Ответственность за создание объекта проверяется с помощью этого интерактивного теста без необходимости проверки какого-либо состояния — тест доверяет тому, что логика сохранения в спящем режиме работает должным образом. Конечно, это тоже нужно проверить, но это другая ответственность и другой вид проверки.

4. Вывод

Этот метод неизменно приводит к более целенаправленным тестам, что делает их более устойчивыми и гибкими к изменениям. Единственная причина, по которой тест теперь должен провалиться, заключается в том, что тестируемая ответственность нарушена.