В то время как фиктивные объекты являются идеальными инструментами для модульного тестирования, имитация через фиктивные фреймворки может превратить ваши юнит-тесты в неразрешимый беспорядок.
Основная причина этой сложности в том, что наши объекты слишком велики. У них много методов, и эти методы возвращают другие объекты, которые также имеют методы. Когда мы передаем фиктивную версию такого объекта в качестве параметра, мы должны убедиться, что все его методы возвращают допустимые объекты.
Это приводит к неизбежной сложности, из-за которой юнит-тесты практически невозможно поддерживать.
Иерархия объектов
В качестве примера возьмем интерфейс Region
из jcabi-динамо (для краткости этот фрагмент и все остальные в этой статье упрощены):
1
2
3
|
public interface Region { Table table(String name); } |
Его метод table()
возвращает экземпляр интерфейса Table
, который имеет свои собственные методы:
1
2
3
4
5
|
public interface Table { Frame frame(); Item put(Attributes attrs); Region region(); } |
Интерфейс Frame
, возвращаемый методом frame()
, также имеет свои собственные методы. И так далее. Чтобы создать правильно смоделированный экземпляр интерфейса Region
, обычно нужно создать дюжину других фиктивных объектов. С Mockito это будет выглядеть так:
1
2
3
4
5
6
7
8
9
|
public void testMe() { // many more lines here... Frame frame = Mockito.mock(Frame. class ); Mockito.doReturn(...).when(frame).iterator(); Table table = Mockito.mock(Table. class ); Mockito.doReturn(frame).when(table).frame(); Region region = Mockito.mock(Region. class ); Mockito.doReturn(table).when(region).table(Mockito.anyString()); } |
И все это только строительные леса перед фактическим тестированием.
Пример использования
Допустим, вы разрабатываете проект, который использует jcabi-динамо для управления данными в DynamoDB. Ваш класс может выглядеть примерно так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
public class Employee { private final String name; private final Region region; public Employee(String empl, Region dynamo) { this .name = empl; this .region = dynamo; } public Integer salary() { return Integer.parseInt( this .region .table( "employees" ) .frame() .where( "name" , this .name) .iterator() .next() .get( "salary" ) .getN() ); } } |
Вы можете представить, насколько сложно будет провести юнит-тестирование этого класса, например, с помощью Mockito. Сначала мы должны смоделировать интерфейс Region
. Затем мы должны смоделировать интерфейс Table
и убедиться, что он возвращается методом table()
. Затем мы должны смоделировать интерфейс Frame
и т. Д.
Модульный тест будет намного дольше, чем сам класс. Кроме того, его реальная цель, которая заключается в проверке получения зарплаты сотрудника, не будет очевидна для читателя.
Более того, когда нам нужно протестировать похожий метод аналогичного класса, нам нужно будет заново запустить этот макет с нуля. Опять же, несколько строк кода, которые будут выглядеть очень похоже на то, что мы уже написали.
Поддельные классы
Решение состоит в том, чтобы создавать поддельные классы и отправлять их вместе с реальными классами. Это то, что делает jcabi-динамо . Просто посмотрите на его JavaDoc . Существует пакет с именем com.jcabi.dynamo.mock
который содержит только поддельные классы, подходящие только для модульного тестирования.
Хотя их единственная цель — оптимизировать модульное тестирование, мы отправляем их вместе с рабочим кодом в одном пакете JAR.
Вот как будет выглядеть тест, когда используется поддельный класс MkRegion
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public class EmployeeTest { public void canFetchSalaryFromDynamoDb() { Region region = new MkRegion( new H2Data().with( "employees" , new String[] { "name" }, new String[] { "salary" } ) ); region.table( "employees" ).put( new Attributes() .with( "name" , "Jeff" ) .with( "salary" , new AttributeValue().withN( 50000 )) ); Employee emp = new Employee( "Jeff" , region); assertThat(emp.salary(), equalTo( 50000 )) } } |
Этот тест выглядит для меня очевидным. Сначала мы создаем поддельную область DynamoDB, которая работает поверх хранилища H2Data
(база данных H2 в памяти). Хранилище будет готово для одной таблицы employees
с name
хеш-ключа и одним атрибутом salary
.
Затем мы помещаем запись в таблицу с хешем Jeff
и зарплатой 50000
.
Наконец, мы создаем экземпляр класса Employee
и проверяем, как он выбирает зарплату из DynamoDB.
В настоящее время я делаю то же самое почти во всех библиотеках с открытым исходным кодом, с которыми я работаю. Я создаю коллекцию поддельных классов, которые упрощают тестирование внутри библиотеки и для ее пользователей.
Похожие сообщения
Вы также можете найти эти сообщения интересными:
- Искусство тестирования программного обеспечения Гленфорд Майерс
- Испытания CasperJS в Maven Build
- XML / XPath Matchers для Hamcrest
- Ошибки приветствуются
- Phantomjs как HTML-валидатор
Ссылка: | Встроенные поддельные объекты от нашего партнера по JCG Егора Бугаенко в блоге About Programming . |