Статьи

Почему вы должны писать модульные тесты — методы тестирования 8

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

Недавно клиент попросил экстренную версию какого-либо кода для отображения сообщения на экране, по юридическим причинам, на соответствующих страницах своего веб-сайта.

Сценарий состоял в том, что часть информации должна отображаться на экране, если она существует в базе данных — очень простой сценарий, который может быть покрыт несколькими простыми строками кода. Однако в спешке с написанием кода разработчик не писал никаких модульных тестов, и код содержал ошибку, которую не было обнаружено, пока патч не достиг UAT. Вы можете спросить, что это за ошибка, и это было то, что мы все сделали в какой-то момент нашей карьеры: добавив нежелательную точку с запятой ‘;’ до конца строки.

Я собираюсь продемонстрировать переписанную, двойную версию кода с использованием сценария AddressService, который я использовал в моих предыдущих блогах «Методы тестирования», как показано на диаграмме UML ниже:

В этой демонстрации функциональность изменилась, но логика и структура примера кода практически не изменились. В мире AddressService логика работает так:

  1. Получить адрес из базы данных.
  2. Если адрес существует, отформатируйте его и верните полученную строку.
  3. Если адрес не существует, вернуть ноль.
  4. Если форматирование не удалось, не беспокойтесь об этом и верните ноль.

Переписанный AddressService.findAddress (…) выглядит примерно так:

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
@Component
public class AddressService {
 
  private static final Logger logger = LoggerFactory
      .getLogger(AddressService.class);
 
  private AddressDao addressDao;
 
  public String findAddressText(int id) {
 
    logger.info("In Address Service with id: " + id);
    Address address = addressDao.findAddress(id);
 
    String formattedAddress = null;
 
    if (address != null);
    try {
      formattedAddress = address.format();
    } catch (AddressFormatException e) {
      // That's okay in this business case so ignore it
    }
 
    logger.info("Leaving Address Service with id: " + id);
    return formattedAddress;
  }
 
  @Autowired
  @Qualifier("addressDao")
  void setAddressDao(AddressDao addressDao) {
    this.addressDao = addressDao;
  }
}

Вы заметили ошибку? Я не сделал, когда я рассмотрел код … На всякий случай, я прокомментировал снимок экрана ниже:

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

  • Приложение развернуто в Dev, Test и UAT.
  • Команда тестировщиков проверила, что измененный экран работает нормально и передаст изменения.
  • Другие экраны проходят регрессионное тестирование и признаны некорректными. Все неисправные экраны отмечены.
  • Срочное сообщение об ошибке поднят.
  • Отчет проходит через различные уровни управления.
  • Сообщение передается мне (и я пропускаю обед), чтобы исследовать возможную проблему.
  • Отчет направляется трем другим членам команды для расследования (четыре пары глаз лучше, чем одна)
  • Обидная точка с запятой найдена и исправлена.
  • Код повторно тестируется в dev и возвращается в систему контроля версий.
  • Приложение создается и разворачивается в Dev, Test и UAT.
  • Команда тестирования проверяет, что измененный экран работает нормально, и передает изменения.
  • Другие экраны проходят регрессионное тестирование и проходят.
  • Аварийное исправление прошло.

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

Чтобы доказать это, я написал три недостающих модульных теста, и мне потребовалось всего лишь 15 минут времени на разработку, что по сравнению с количеством потраченных человеко-часов кажется хорошим использованием времени разработчика.

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@RunWith(UnitilsJUnit4TestClassRunner.class)
public class WhyToTestAddressServiceTest {
 
  private AddressService instance;
 
  @Mock
  private AddressDao mockDao;
 
  @Mock
  private Address mockAddress;
 
  /**
   * @throws java.lang.Exception
   */
  @Before
  public void setUp() throws Exception {
 
    instance = new AddressService();
    instance.setAddressDao(mockDao);
  }
 
  /**
   * This test passes with the bug in the code
   *
   * Scenario: The Address object is found in the database and can return a
   * formatted address
   */
  @Test
  public void testFindAddressText_Address_Found() throws AddressFormatException {
 
    final int id = 1;
    expect(mockDao.findAddress(id)).andReturn(mockAddress);
    expect(mockAddress.format()).andReturn("This is an address");
 
    replay();
    instance.findAddressText(id);
    verify();
  }
 
  /**
   * This test fails with the bug in the code
   *
   * Scenario: The Address Object is not found and the method returns null
   */
  @Test
  public void testFindAddressText_Address_Not_Found() throws AddressFormatException {
 
    final int id = 1;
    expect(mockDao.findAddress(id)).andReturn(null);
 
    replay();
    instance.findAddressText(id);
    verify();
  }
 
  /**
   * This test passes with the bug in the code
   *
   * Scenario: The Address Object is found but the data is incomplete and so a
   * null is returned.
   */
  @Test
  public void testFindAddressText_Address_Found_But_Cant_Format() throws AddressFormatException {
 
    final int id = 1;
    expect(mockDao.findAddress(id)).andReturn(mockAddress);
    expect(mockAddress.format()).andThrow(new AddressFormatException());
 
    replay();
    instance.findAddressText(id);
    verify();
  }
}

И, наконец, рискуя показаться самодовольным, я должен признаться, что, хотя в этом случае ошибка была не моей, в прошлом я выпускал подобные ошибки в дикую природу — до того, как научился писать модульные тесты…
Исходный код доступен на GitHub по адресу:

мерзавец: //github.com/roghughe/captaindebug.git

Ссылка: Почему вы должны писать модульные тесты — Методы тестирования 8 от нашего партнера JCG Роджера Хьюза в блоге Captain Debug .

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