Fast Tests Help Humans, Deep Tests Help Servers

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

Для выявления ошибок более сложного характера автоматизированные тесты превращаются в интеграционные тесты, которые включают использование внешних ресурсов в сценариях тестирования, а не их имитацию. Хотя такой подход повышает охват тестирования, он замедляет весь процесс сборки. Это подрывает саму идею автоматизированных тестов, которые должны быть средством безопасности и помогать программистам безопасно редактировать код. Разделение тестов на “быстрые” и “глубокие”, а затем разрешение людям запускать первые, а серверам - вторые, может быть хорошим решением проблемы.

Рассмотрим следующий Java-код с простым статическим методом toString():

Он читает stream байт за байтом, добавляет их в буфер и возвращает буфер клиенту. Вот JUnit5 тест, который проверяет функциональность.

Пока все идет хорошо. Тест работает, и метод кажется верным. Более того, тест выполняется очень быстро - всего 5 мс на моем ноутбуке. Однако, при более близком рассмотрении, мы можем обнаружить ошибку в методе: он не закрывает входной поток. Эта проблема не влияет на тест, так как входной поток находится в памяти и не содержит ценных ресурсов, которые могут утекать. Однако, если мы введем новый тест, это выявит эту проблему:

Когда я запускаю этот тест, возникает исключение FileNotFoundException с сообщением Too many open files. Если я уменьшу верхнее ограничение в цикле for до 10000, ошибка исчезает. Это происходит из-за того, что максимальное количество открытых файлов в Mac OS X составляет 12 288. Однако на Ubuntu это ограничение установлено в 65536. Таким образом, мой тест не обнаружит ошибку, если я запущу его на Ubuntu. Я уверен, что вы знаете, как исправить эту ошибку в методе toString().

Очевидно, что второй тест гораздо медленнее первого, занимая 650 мс на моем ноутбуке (в 130 раз медленнее!). Это просто пример теста, который помогает обнаружить ошибку, но требует много времени. Обычно интеграционные тесты проявляют такое негативное влияние на производительность, потому что они используют “внешние” ресурсы, которые медленные. Файловая система, используемая во втором тесте, является одним из таких внешних ресурсов.

650 мс могут не вызывать проблем, когда в молодом проекте всего несколько тестовых методов. Однако, с увеличением количества тестов, медленные тесты быстро становятся проблемой, так как время сборки увеличивается, что вызывает раздражение у программистов. Автоматизированные тесты, предназначенные для помощи разработчикам, превращаются в помеху. Если программисту приходится ждать несколько минут после каждого изменения кода, чтобы убедиться, что ничего не сломалось, возникает раздражение. Часто раздраженный программист может удалять эти медленные тесты.

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

Классификация тестов на быстрые и глубокие может быть решением. Первая категория включает тесты, которые мокируют как можно больше и выполняются не более 20 мс. Вторая категория состоит из тестов, которые проводят более глубокое исследование для обнаружения неуловимых ошибок, которые могут быть пропущены более быстрыми тестами. В большинстве случаев модульные тесты попадают в первую категорию, в то время как интеграционные тесты относятся ко второй. Различие между “модульными” и “интеграционными” тестами, по моему мнению, вводит в заблуждение. “Быстрые” и “глубокие” гораздо более понятные категории, так как очевидно, к какой категории относится тест. Если тест выполняется менее 20 мс, он быстрый; если нет, он глубокий.

Когда тесты разделены на быстрые и глубокие, их следует запускать в двух различных сценариях: программисты выполняют быстрые тесты во время написания кода, в то время как серверы выполняют глубокие тесты во время сборки и/или релиза программного обеспечения. В JUnit5 такую категоризацию можно достичь с помощью аннотации @Tag.

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

Вот как pom.xml может быть настроен, чтобы по умолчанию включить “быстрые” тесты:

В среде CI Maven должен быть запущен с использованием следующего флага:

Программист также может запустить “медленные” тесты на своем ноутбуке, используя тот же флаг командной строки. Однако это обычно делается только тогда, когда сервер выдает красный сигнал.

P.S. Кстати, у метода toString() есть еще одна ошибка, которую не обнаруживают ни первый, ни второй тесты. Сможете ли вы ее определить? Можете ли вы разработать тест, который выявит эту ошибку? Как бы вы классифицировали этот тест: “быстрый” или “глубокий”?

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-27 at 13:45

sixnines availability badge   GitHub stars