Автор оригинала: Eugen Paraschiv.
содержание
- 1. Обзор
- 2. Слои
- 3. Мотивация и размытие строк модульного теста
- 4. Заключение
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. Мотивация и Размытие строк модульного теста
При модульном тестировании службы стандартным unit обычно является сервис класс , вот так просто. Тест будет имитировать слой под ним – в данном случае слой 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, захватывая аргумент в процессе, чтобы его также можно было проверить. ответственность за создание сущности проверяется с помощью этого теста взаимодействия, без необходимости проверять какое – либо состояние-тест верит, что логика сохранения в спящем режиме работает должным образом. Конечно, это тоже нужно проверить, но это другая ответственность и другой тип теста.
4. Заключение
Этот метод неизменно приводит к более целенаправленным тестам, что делает их более устойчивыми и гибкими к изменениям. Единственная причина, по которой тест теперь должен провалиться, заключается в том, что ответственность под тестом нарушена.