Статьи

Обычные юнит-тесты и заглушки — Методы испытаний

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

… И я высказал идею, что любой класс, который не содержит никакой логики, на самом деле не нуждается в модульном тестировании. В это я включил свой объект доступа к данным, 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;
  }
}

В книге Майкла Фезера «Эффективная работа с устаревшим кодом» говорится, что тест не является модульным тестом, если:

  1. Это говорит с базой данных.
  2. Он общается через сеть.
  3. Это касается файловой системы.
  4. Вы должны сделать специальные вещи в вашей среде (например, редактирование файлов конфигурации), чтобы запустить его.

Чтобы соблюдать эти правила, вам необходимо изолировать тестируемый объект от остальной части вашей системы, и именно здесь вступают объекты-заглушки. Объекты-заглушки — это объекты, которые вводятся в ваш объект и используются для замены реальных объектов в тестовых ситуациях. Мартин Фаулер определяет заглушки, в своем эссе « Насмешки не заглушки» :

«Заглушки обеспечивают постоянные ответы на вызовы, сделанные во время теста, обычно вообще не реагируя ни на что, кроме того, что запрограммировано для теста. Заглушки могут также записывать информацию о вызовах, такую ​​как заглушка шлюза электронной почты, которая запоминает сообщения, которые она «отправила», или, возможно, только сколько сообщений она «отправила» ».

Выбрать слово для описания заглушек очень сложно, я мог бы выбрать пустышку или подделку, но есть типы заменяющих объектов, которые известны как пустышки или подделки — также описанные Мартином Фаулером:

  • Пустые объекты передаются, но никогда не используются. Обычно они просто используются для заполнения списков параметров.
  • Ложные объекты на самом деле имеют рабочие реализации, но обычно используют некоторые ярлыки, которые делают их непригодными для производства (хороший пример — база данных в памяти).

Тем не менее, я видел другие определения термина поддельный объект, например, Рой Ошеров в своей книге «Искусство модульного тестирования» определяет поддельный объект как:

  • Подделка — это общий термин, который можно использовать для описания заглушки или фиктивного объекта … потому что оба выглядят как реальный объект.

… Поэтому я, как и многие другие, склонен называть все заменяющие объекты либо фиктивными, либо заглушками, поскольку между ними есть разница, но об этом позже.

При тестировании 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.
     */
  }
}

Обратите внимание, что при написании модульного теста мы стремимся к ясности. Часто допускаемая ошибка заключается в том, что тестовый код уступает производственному коду, в результате чего он часто более грязный и неразборчивый. Рой Ошеров в «Искусстве модульного тестирования» выдвигает идею, что тестовый код должен быть более читабельным, чем производственный код. Четкие тесты должны следовать следующим основным линейным шагам:

  1. Создайте тестируемый объект. В приведенном выше коде это делается в методе setUp (), поскольку я использую один и тот же тестируемый объект для всех (одного) тестов.
  2. Настройте тест. Это делается в тестовом методе testFindAddressWithStub (), поскольку данные, используемые в тесте, являются специфическими для этого теста.
  3. Запустить тест
  4. Снеси тест. Это гарантирует, что тесты изолированы друг от друга и могут быть запущены в любом порядке.

Использование упрощенной заглушки дает два преимущества изоляции AddressService от внешнего мира и быстрых тестов.

Насколько хрупок этот вид теста? Если ваши требования меняются, то тест и заглушка меняются — не так ли все-таки хрупко?

Для сравнения, мой следующий блог переписывает этот тест с использованием EasyMock .

Ссылка: регулярные юнит-тесты и заглушки — методы тестирования 4 от нашего партнера по JCG в блоге Captain Debug

Статьи по Теме :