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