… И я высказал идею, что любой класс, который не содержит никакой логики, на самом деле не нуждается в модульном тестировании. В это я включил свой объект доступа к данным, DAO, и вместо этого предпочел интеграционное тестирование этого класса, чтобы убедиться, что он работает в сотрудничестве с базой данных.
Сегодняшний блог посвящен написанию обычного или классического модульного теста, который обеспечивает изоляцию объекта тестирования с использованием объектов-заглушек. Код, который мы будем тестировать, опять же, AddressService :
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
40
41
42
43
44
45
46
47
48
|
@Component public class AddressService { private static final Logger logger = LoggerFactory.getLogger(AddressService. class ); private AddressDao addressDao; /** * Given an id, retrieve an address. Apply phony business rules. * * @param id * The id of the address object. */ public Address findAddress( int id) { logger.info( "In Address Service with id: " + id); Address address = addressDao.findAddress(id); address = businessMethod(address); logger.info( "Leaving Address Service with id: " + id); return address; } private Address businessMethod(Address address) { logger.info( "in business method" ); // Apply the Special Case Pattern (See MartinFowler.com) if (isNull(address)) { address = Address.INVALID_ADDRESS; } // Do some jiggery-pokery here.... return address; } private boolean isNull(Object obj) { return obj == null ; } @Autowired @Qualifier ( "addressDao" ) void setAddressDao(AddressDao addressDao) { this .addressDao = addressDao; } } |
В книге Майкла Фезера «Эффективная работа с устаревшим кодом» говорится, что тест не является модульным тестом, если:
- Это говорит с базой данных.
- Он общается через сеть.
- Это касается файловой системы.
- Вы должны сделать специальные вещи в вашей среде (например, редактирование файлов конфигурации), чтобы запустить его.
Чтобы соблюдать эти правила, вам необходимо изолировать тестируемый объект от остальной части вашей системы, и именно здесь вступают объекты-заглушки. Объекты-заглушки — это объекты, которые вводятся в ваш объект и используются для замены реальных объектов в тестовых ситуациях. Мартин Фаулер определяет заглушки, в своем эссе « Насмешки не заглушки» :
«Заглушки обеспечивают постоянные ответы на вызовы, сделанные во время теста, обычно вообще не реагируя ни на что, кроме того, что запрограммировано для теста. Заглушки могут также записывать информацию о вызовах, такую как заглушка шлюза электронной почты, которая запоминает сообщения, которые она «отправила», или, возможно, только сколько сообщений она «отправила» ».
Выбрать слово для описания заглушек очень сложно, я мог бы выбрать пустышку или подделку, но есть типы заменяющих объектов, которые известны как пустышки или подделки — также описанные Мартином Фаулером:
- Пустые объекты передаются, но никогда не используются. Обычно они просто используются для заполнения списков параметров.
- Ложные объекты на самом деле имеют рабочие реализации, но обычно используют некоторые ярлыки, которые делают их непригодными для производства (хороший пример — база данных в памяти).
Тем не менее, я видел другие определения термина поддельный объект, например, Рой Ошеров в своей книге «Искусство модульного тестирования» определяет поддельный объект как:
- Подделка — это общий термин, который можно использовать для описания заглушки или фиктивного объекта … потому что оба выглядят как реальный объект.
… Поэтому я, как и многие другие, склонен называть все заменяющие объекты либо фиктивными, либо заглушками, поскольку между ними есть разница, но об этом позже.
При тестировании AddressService нам нужно заменить реальный объект доступа к данным на объект-заглушку, и в этом случае он выглядит примерно так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
public class StubAddressDao implements AddressDao { private final Address address; public StubAddressDao(Address address) { this .address = address; } /** * @see com.captaindebug.address.AddressDao#findAddress(int) */ @Override public Address findAddress( int id) { return address; } } |
Обратите внимание на простоту кода заглушки. Он должен быть легко читаемым, обслуживаемым, НЕ содержать никакой логики и нуждаться в самостоятельном модульном тестировании. После того, как код заглушки был написан, затем следует модульный тест:
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
40
41
42
|
public class ClassicAddressServiceWithStubTest { private AddressService instance; @Before public void setUp() throws Exception { /* Create the object to test */ /* Setup data that's used by ALL tests in this class */ instance = new AddressService(); } /** * Test method for * {@link com.captaindebug.address.AddressService#findAddress(int)}. */ @Test public void testFindAddressWithStub() { /* Setup the test data - stuff that's specific to this test */ Address expectedAddress = new Address( 1 , "15 My Street" , "My Town" , "POSTCODE" , "My Country" ); instance.setAddressDao( new StubAddressDao(expectedAddress)); /* Run the test */ Address result = instance.findAddress( 1 ); /* Assert the results */ assertEquals(expectedAddress.getId(), result.getId()); assertEquals(expectedAddress.getStreet(), result.getStreet()); assertEquals(expectedAddress.getTown(), result.getTown()); assertEquals(expectedAddress.getPostCode(), result.getPostCode()); assertEquals(expectedAddress.getCountry(), result.getCountry()); } @After public void tearDown() { /* * Clear up to ensure all tests in the class are isolated from each * other. */ } } |
Обратите внимание, что при написании модульного теста мы стремимся к ясности. Часто допускаемая ошибка заключается в том, что тестовый код уступает производственному коду, в результате чего он часто более грязный и неразборчивый. Рой Ошеров в «Искусстве модульного тестирования» выдвигает идею, что тестовый код должен быть более читабельным, чем производственный код. Четкие тесты должны следовать следующим основным линейным шагам:
- Создайте тестируемый объект. В приведенном выше коде это делается в методе setUp (), поскольку я использую один и тот же тестируемый объект для всех (одного) тестов.
- Настройте тест. Это делается в тестовом методе testFindAddressWithStub (), поскольку данные, используемые в тесте, являются специфическими для этого теста.
- Запустить тест
- Снеси тест. Это гарантирует, что тесты изолированы друг от друга и могут быть запущены в любом порядке.
Использование упрощенной заглушки дает два преимущества изоляции AddressService от внешнего мира и быстрых тестов.
Насколько хрупок этот вид теста? Если ваши требования меняются, то тест и заглушка меняются — не так ли все-таки хрупко?
Для сравнения, мой следующий блог переписывает этот тест с использованием EasyMock .
Ссылка: регулярные юнит-тесты и заглушки — методы тестирования 4 от нашего партнера по JCG в блоге Captain Debug
Статьи по Теме :
- Методы тестирования — не писать тесты
- Неправильное использование сквозных тестов — Методы тестирования 2
- Что следует тестировать? — Методы испытаний 3
- Модульное тестирование с использованием макетов — Методы тестирования 5
- Создание заглушек для устаревшего кода — методы тестирования 6
- Подробнее о создании заглушек для устаревшего кода — Методы тестирования 7
- Почему вы должны писать модульные тесты — методы тестирования 8
- Некоторые определения — методы тестирования 9
- Использование FindBugs для создания значительно меньшего количества ошибочного кода
- Разработка и тестирование в облаке