Юнит-тесты

15 лет назад никто не слышал про юнит-тесты. Код писался один раз, потом проверялся вручную QA и методами пристального вглядывания. Все жили в гармонии и мире. Но тут пришли юнит-тесты и мир разделился на два враждующих лагеря: на тех, кто пишет тесты и тех, кто нет.

Спойлер: я за авто-тесты.

В интернете очень много аргументов как за, так и против. Я решил собрать свой собственный список аргументов за юнит-тесты.

1

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

@Test
public void testSuccess() throws Exception {
	ObservationRequest req = createRequest();
	int times = (int) ((req.getEndTimeMillis() - req.getStartTimeMillis()) / 1000);
	config.setProperty("rotator.enabled", true);
	service = new RotatorService(config, predict, new ScheduleFixedTimesTheadPoolFactory(times), new SteppingClock(req.getStartTimeMillis(), 1000));
	service.start();
	assertNotNull(service.schedule(req, req.getStartTimeMillis()));
	try (BufferedReader r = new BufferedReader(new InputStreamReader(RotatorService.class.getClassLoader().getResourceAsStream("expected/rotctrld-requests.txt"), StandardCharsets.UTF_8))) {
		String curLine = null;
		int i = 0;
		while ((curLine = r.readLine()) != null) {
			assertPosition(curLine, requestHandler.getRequests().get(i));
			i++;
		}
		assertEquals(i, requestHandler.getRequests().size());
	}
}

Я очень часто слышу следующий диалог:

  • А вот тут давайте мы напишем юнит-тесты.
  • Но мы же не сможем ими покрыть А, Б, В!
  • Точно, это будет слишком сложно и нам понадобится сложная инфраструктура. Давайте вернёмся к ним в следующий раз, когда у нас будет больше времени.

Я крайне не согласен с таким подходом. Тесты не должны покрывать всевозможные случаи, А, Б и В. Основное их назначение - хотя бы раз выполнить написанный код. Сколько раз я видел случаи, когда разработчик написал код и ни разу его не выполнил. Наличие теста - это отличное доказательство, что код хотя бы раз выполнился, и совсем глупых ошибок там нет.

2

Даже маленькие библиотеки нужно тестировать. Особенно маленькие библиотеки. Обычно они имеют чётко обозначенный интерфейс, который можно удобно протестировать юнит-тестами.

3

Написание тестов заставляет задуматься о том, как структурирован код. Обычно, разработчик сфокусирован на том, чтобы код работал как нужно. Написание же тестов, позволяет понять то, как код будет использоваться. Я прохладно отношусь к TDD. Но в этой методологии есть зерно правды - надо писать код и тесты в одно и то же время. Это позволяет сильно сэкономить на интеграции кода с другими компонентами.

Ещё при написании тестов начинаешь задумываться: “А что ещё можно проверить?”, “Ничего я не забыл?”. Это позволяет посмотреть на код с другой стороны и придумать пару странных сценариев использования, которые лучше протестировать. Сюда же входят всевозможные проверки на входные параметры и граничные условия.

4

Тесты позволяют править ошибки реализации. Допустим все тесты проходят успешно, но выясняется, что код всё-таки работает неправильно. В таком случае его реализация меняется, и тесты запускаются ещё раз. Ошибок быть не должно.

Этот пункт можно расширить: тесты позволяют обновлять зависимые библиотеки. Допустим, такая ситуация: код зависит от сторонней библиотеки. В этой библиотеке была найдена ошибка безопасности. Если есть тесты, то можно просто обновить версию зависимой библиотеки и прогнать тесты. Ошибок быть не должно. Это очень удобно использовать в связке с автоматическими системами генерации pull request. Например, github недавно научился сканировать зависимые библиотеки на разные уязвимости и автоматически создавать pull request с обновлёнными версиями библиотек. Если есть тесты, то такие pull request автоматически собираются и тестируются. Если нет ошибок, то можно безопасно обновляться.

Выводы

Пишите тесты. А если не знаете как, то спросите меня. Я знаю.