18 Dec 2012

Виды тестирования (Types of testing)

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

  • Модульные тесты. Их задача - протестировать отдельный модуль логики в полной изоляции - как от окружения, так и от других модулей. Это не обязательно должен быть один класс, это может быть их небольшая агломерация - например, в случае с одним главным классом и с несколькими вспомогательными package-private классами.
    Для того, чтоб отделить тестируемый модуль (SUT, system under test) от других модулей (DOC, dependent-on class) и от внешней среды, обычно приходят на помощь mock-фреймворки, которые подменяют все остальное на манекены-обманки (перечень библиотек можно найти внизу). Например, если у нас есть парсер HTML страничек, нам не обязательно для него лезть на реальный сайт, качать страницу и отдавать, мы можем сохранить где-то эту страницу в тестах и постоянно использовать во время тестирования. В таком случае класс доступа к внешнему ресурсу мокается и возвращает нужный контент когда его просит SUT.
    Почему они важны? Потому что они могут протестировать все - каждое условие в классе, каждый цикл, каждый метод. Таких тестов как правило больше всего.
    Существует некая классификация описанная в XUnit Test Patterns (краткое пояснение можно найти в блоге Фаулера). Мое личное мнение - эти названия высосаны из пальца и использовать их не стоит. Терминология в тестировании и так путает, а тут еще появляются Dummy, Fake, Mock, которые слишком близки по семантическому смыслу, чтоб их серьезно рассматривать в качестве инструмента для общения. Да и уровень модульного тестирования и так слишком низок, чтоб моки нужно было делить на типы. На моей практике эти термины нужны были только во время собеседований.

  • Компонентные тесты. Термин используется не так часто, однако мне очень понравился. В данном случае имеется в виду компонент системы. Для этого не нужно запускать все приложение. Обычно в наших приложениях мы делим все на слои. Такие компонентые тесты могут инициализировать все слои, мокать внешние ресурсы, и тестировать целый кусок логики. Плюсы таких тестов: а) тестируют не класс, а настоящую предметную логику б) очень быстрые по сравнению с системными тестами. Идеальные компонентные тесты - те, в которых все внешние ресурсы ограничены, однако иногда проще что-то оставить, например, если это flow использующее JMS внутри единого приложения, то сам JMS можно оставить (заменив его на ActiveMQ например), а вот обращение к внешним системам уже придется мокать, иначе мы будем тестировать и их, и в таком случае слишком много времени будет тратиться на поддержку.
    Бывает компонентными тестами называют модульные. Ну и по правде говоря компонентом вы можете назвать что угодно, так что аккуратно с этим термином.

  • Интеграционные тесты (System Integration Test, SIT). Этот термин самый перегруженный из всех, интеграционным тест могут назвать в случаях: когда просто используется внешний ресурс; когда используется агломерация классов (в нашем случае это Компонентные); когда запускается все приложение и т.п. Дайте людям повод, они назовут этот тест интеграционным. Я стараюсь не использовать по возможности этот термин, однако бывают семантически подходящие случаи: это когда вы на самом деле тестируете интеграцию с другими системами. Пример: приложение А следит за обновлениями в приложении Б и дергает приложение С. Все эти системы могут общаться по разным протоколам: JAX-RS, JAX-WS, JMS, Protobuf, нам нужно протестировать это общение потому как протокол с одной стороны мог поменяться в то время как с другой стороны он остался прежним, и тогда приложение в целом не работоспособно. Собственно этим и занимаются интеграционные тесты, они тестируют точки соприкосновения систем. Заметьте, что они не должны тестировать саму бизнес логику, иначе вы, опять же, будете тестировать несколько приложений, а вам этот геморрой не нужен.

  • Системные тесты. Это тесты, иже нуждаются в запущенном приложении. Их можно как автоматизировать, так и предоставлять работу ручным тестеровщикам. Проблема этих тестов: их трудно писать и автоматизировать потому как все окружение должно быть поднято; они медленные; их трудно поддерживать, потому как любой чих может их сломать. Таких тестов должно быть мало по сравнению с модульными и компонентными, они должны тестировать основные функции приложения и не вдаваться в детали.
    Эти тесты должны быть максимально независимыми друг от друга. Например, если у вас есть сайт где можно зарегистрироваться, то в идеале каждый тест должен регистрировать нового пользователя, это важно для распараллеливания их выполнения когда окажется что они проходят полдня.


Еще несколько терминов, хоть и ортогональных вышеприведенным (потому как они одновременно могут быть и системными например), однако очень используемых:

  • Sanity тесты - базовые системные тесты, которые определяют а работоспособно ли приложение вообще. Удобно использовать при каждом деплое дабы автоматизированно определить запустилось ли приложение удачно. Включает в себя проверку только самых-самых базовых функций системы. Вот один из примеров реализации написанный на питоне - он просто опрашивает стартовую страницу, если та отвечает HTTP 200/201, значит приложение стартовало, если же там коды ошибок, значит доставка сайта упала и сборку в Jenkins’e нужно помечать красным.

  • Smoke тесты - судя по классификации ISTQB - это то же самое, что и Sanity. Однако многие выделяют их в разные категории. Бывает говорят, что Smoke - это регрессионное тестирование главных функций дабы определить имеет ли смысл дальше тестировать или приложение уже серьезно повреждено (начинает дымиться), в то время как Sanity говорит работает ли приложение вообще. Другие трактуют эти термины в точности наоборот (хотя семантически приведенный вариант больше похож на правду). В своей команде вы просто определитесь сами что вы как называете и имейте в виду что вас могут неверно понять.

  • Story Acceptance Tests. Когда вы сформировали требования, вы можете определить тесты (и иногда даже сразу их написать), которые будут определять удовлетворяет ли функционал заведенным требованиям. Очень круто если заказчик их сам пишет, однако такая возможность редка. Если такие тесты не проходят, значит фича не является реализованной. Очень удобно такие тесты писать в виде Given some state, when doing something new, then we find a new state.

  • User Acceptance Tests - это когда вы садите настоящих пользователей (даете доступ заказчику) и просите покликать, ожидая какую-то обратную связь. Это тот этап, когда вы можете определить а то ли вообще хотел заказчик.

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

  • Нагрузочные тесты. Определяют как ваше приложение ведет себя под нагрузкой и собственно какую нагрузку они могут выдержать. Достаточно сложные тесты, особенно учитывая, что сама нагрузка - вещь многоликая. Можно тестировать пропускную способность (напр., количество транзакций в секунду), максимальную нагрузку (когда приложение уходит в себя насовсем), однако чаще всего нас будет интересовать capacity (дам конфетку тому кто переведет это на русский :)) - количество одновременных транзакций при которых приложение будет себя вести удовлетворительно (для этого вы ставите изначально порог, к примеру, каждая страница не должна загружаться дольше чем 1 сек).

  • Тесты на выносливость (Longevity tests) - это подтип нагрузочных тестов. Отличается тем, что вы просто помещаете ваше приложение под постоянную среднюю нагрузку эдак на месяц и не перезагружаете его. Такие тесты помогают определить, например, есть ли у вас утечки памяти.


Обзор библиотек для тестирования:

  • JUnit, TestNG для запуска тестов и проверки результатов. Очень понравился Groovy JUnit, о нем можно послушать на одной из JTalks Тех Сессий.

  • Mockito, EasyMock - самые популярные библиотеки для создания моков

  • PowerMock - позволяет подменять static, final, private методы, такая библиотека помогает в legacy системах, трудно поддающихся рефакторингу

  • Awaitility - позволяет удобно работать в случаях, когда нужно ждать ответ некоторое время. Сразу оговорюсь, что это очень не эффективно, намного эффективней использовать какие-то callback’и.

  • Rest Assured, Restito - для написания тестов, работающих с REST API. Первый может тестировать REST сервер, то бишь слать запросы вашему сервису; а второй может запускать mock-сервис и тестировать как ваш REST клиент шлет запросы другим системам.

  • JBehave, Cucumber (Ruby) - фреймворки позволяющие описывать тесты в виде Given/When/Then (так называемые Specification By Example). По опыту с первым скажу, что вещь на практике не такая удобная, более подробно в Рецензии на JBehave.