Статьи

Неправильное использование сквозных тестов — Методы тестирования 2


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

… и описание очень распространенной техники тестирования:
не писать тесты и делать все вручную.

Сегодняшний блог рассказывает о другой практике, которая, по моему мнению, является неоптимальной. В этом сценарии разработчики используют JUnit для написания тестов, но пишут их после завершения написания кода без какой-либо изоляции классов. Это действительно «сквозной» (интеграционный) тест, выдаваемый за юнит-тест.

Хотя вчера я сказал, что я только тестирую класс AddressService, тестирование с использованием этого метода начинается с загрузки базы данных с некоторыми тестовыми данными и последующего захвата AddressController для вызова тестируемого метода. AddressController вызывает AddressService, который затем вызывает AddressDao для получения и возврата запрошенных данных.

@RunWith(UnitilsJUnit4TestClassRunner.class)
@SpringApplicationContext("servlet-context.xml")
@Transactional(TransactionMode.DISABLED)
public class EndToEndAddressServiceTest {

  @SpringBeanByType
  private AddressController instance;

  /**
   * Test method for
   * {@link com.captaindebug.address.AddressService#findAddress(int)}.
   */
  @Test
  public void testFindAddressWithNoAddress() {

    final int id = 10;
    BindingAwareModelMap model = new BindingAwareModelMap();

    String result = instance.findAddress(id, model);
    assertEquals("address-display", result);

    Address resultAddress = (Address) model.get("address");
    assertEquals(Address.INVALID_ADDRESS, resultAddress);
  }

  /**
   * Test method for
   * {@link com.captaindebug.address.AddressService#findAddress(int)}.
   */
  @Test
  @DataSet("FindAddress.xml")
  public void testFindAddress() {

    final int id = 1;
    Address expected = new Address(id, "15 My Street", "My Town",
        "POSTCODE", "My Country");

    BindingAwareModelMap model = new BindingAwareModelMap();

    String result = instance.findAddress(id, model);
    assertEquals("address-display", result);

    Address resultAddress = (Address) model.get("address");
    assertEquals(expected.getId(), resultAddress.getId());
    assertEquals(expected.getStreet(), resultAddress.getStreet());
    assertEquals(expected.getTown(), resultAddress.getTown());
    assertEquals(expected.getPostCode(), resultAddress.getPostCode());
    assertEquals(expected.getCountry(), resultAddress.getCountry());
  }
}

Приведенный выше код использует
Unitils как для загрузки тестовых данных в базу данных, так и для загрузки классов в контексте Spring. Я нахожу Доулса полезным инструментом, который берет на себя тяжелую работу по написанию подобных тестов, а необходимость в настройке такого масштабного теста — тяжелая работа.

Этот вид теста должен быть написан после завершения кода; это НЕ тестовая разработка (которую вы поймете из предыдущих блогов, я большой поклонник), и это не юнит-тест. Одна из проблем, возникающих при написании теста
после кода, заключается в том, что разработчики, которые должны его выполнять, рассматривают его как рутинную работу, а не как часть разработки, а это означает, что он часто спешит и не выполняется в самых изящных стилях кодирования.

Вам также понадобится определенная инфраструктура для кодирования с использованием этого метода, так как база данных требует настройки, которая может быть или не быть на вашем локальном компьютере, и, следовательно, вам может потребоваться подключение к сети для запуска теста. Тестовые данные либо хранятся в тестовых файлах (как в этом случае), которые загружаются в базу данных при запуске теста, либо постоянно хранятся в базе данных. Если изменение требования вызывает изменение в тесте, то файлы базы данных обычно требуют обновления вместе с тестовым кодом, что вынуждает вас обновить тест по крайней мере в двух местах.

Еще одна большая проблема с такого рода тестами, помимо отсутствия изоляции тестируемых, заключается в том, что они могут быть очень медленными, иногда требующими нескольких секунд для выполнения. Шейн Уорден в своей книге «Искусство гибкой разработки» утверждает, что юнит-тесты должны выполняться со скоростью «сотни в секунду». Уорден также ссылается на книгу Майкла Фезера « Эффективная работа с устаревшим кодом», чтобы дать
хорошее определение того, что такое юнит-тест или нет:

тест не является юнит-тестом, если:

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

… теперь мне это нравится.


… хотя я не обязательно согласен с пунктом три. Одним из основных принципов хорошего кода модульного тестирования является
удобочитаемость . Аргументы метода, которые передаются тестируемым объектам, иногда имеют большой размер, особенно если они представляют собой XML. В этом случае я думаю, что более прагматично отдавать предпочтение удобочитаемости теста и хранить данные такого размера в файле данных, а не в качестве закрытой статической конечной строки, поэтому я придерживаюсь только третьего пункта, где это практически возможно.

Модульные тесты могут быть суммированы с использованием
аббревиатуры
FIRST : Быстрый, Независимый, Повторяемый, Самопроверка и Своевременность, в то время как Рой Ошеров в своей книге
« Искусство модульного тестирования» резюмирует хороший модульный тест как: «автоматизированный фрагмент кода, который вызывает тестируемый метод или класс, а затем проверяет некоторые предположения о логическом поведении этого метода или класса. Модульный тест почти всегда пишется с использованием среды модульного тестирования. Его можно легко написать и быстро запустить. Он полностью автоматизирован, надежен, читаем и ремонтопригодны».

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

Учитывая это, моя вводная предпосылка, когда я сказал, что техника «неоптимальна», не совсем верна; нет ничего плохого в тестах «от конца до конца», каждый проект должен иметь некоторые вместе с некоторыми обычными интеграционными тестами, но эти типы тестов не должны заменять или называться
модульными тестами, что часто бывает.

Определив, что такое юнит-тест, мой следующий блог исследует, что вы должны тестировать и почему …