Здесь становится понятна большая приспособленность термина "стеклянный ящик". Тестирование подобным способом происходит, в основном, сразу после написания тестируемого кода. Это, если хотите, правило хорошего тона в программировании. При объектно-ориентированном подходе, данная схема выглядит предпочтительной, т.к. позволяет абстрагироваться от окружения и тестировать отдельные сущности программы. Строго говоря, абстрагироваться от окружения позволяет не столько сам метод, сколько применяемые технологии. Здесь стоит упомянуть о методе, называемом "модульное тестирование".
Модульное тестирование применяется, если структура приложение позволяет условно разделить его на модули — отдельные целостные элементы. Данные модули могут представлять собой как отдельные классы, так и отдельные библиотеки классов. Конкретный подход к тестированию определяется внутри компании на основе данных о структуре приложения, среды написания, имеющихся технических и людских ресурсов определенного качества, а также сложившимися историческими предпочтениями. Не смотря на широкую вариативность разбиения общей структуры приложения на модули, существует общая особенность, присущая таким выделенным элементам. Это — интерфейс. Интерфейс модуля есть совокупность каналов с ограничениями, предназначенных для общения с внешним миром. В классах, например, роль интерфейса могут выполнять публичные методы. Учитывая эти факты, можно спрогнозировать потенциальные проблемы при модульной стратегии тестирования. Основная проблема заключается в следующем. Ценность модуля, как элемента приложения, проявляется лишь в момент работы внутри остальных элементов, составляющих вместе с рассматриваемым цельное приложение. Как быть в случае, если интеграция тестируемого (тут необходимо держать в голове презумпцию нестабильности еще не оттестированного модуля) элемента в общую систему слишком дорогое "удовольствие", либо окружение для него еще вообще не разработано? В этом случае применяют технологию заглушек. Для тестируемого модуля, создается искусственное окружение, эмулирующее работу реального. В качестве бытового аналога можно рассмотреть ситуацию с "фальшивым рулем" в автошколе. Для того чтобы научить человека пользоваться рулем, не обязательно создавать специально для него автомобиль. Можно обойтись "малой кровью", предоставив ему интерфейс — "фальшивый руль", прикрученный к столу. Используя его можно "оттестировать" насколько ловко обучаемый владеет искусством маневрирования.
После того, как отдельные модули были оттестированны, необходимо проверить, насколько логика совместной их работы соответствтует запланированной? Тут мы подошли к стратегии интеграционного тестирования. Для интеграционного тестирования также справедливо и целесообразно использование механизма заглушек. Данный вид тестирования подразделяется на нисходящий и восходящий. Для пояснения этих терминов давайте рассмотрим java-приложение, позволяющее бронировать билеты в кинотеатр. Рассмотрим его в разрезе уровней реализации (модулей): пользовательский интерфейс, интерфейс сервера к пользователю, интерфейс сервера к базе данных, драйвер базы данных. При нисходящем тестировании, сперва мы бы составили и "прогнали" набор тестов для пользовательского интерфейса, поставив заглушки на "нижних" входах/выходах. Заглушки бы возвращали заранее определенные ответы, детерминированность которых принималась нами как допустимое приближение функциональности, позволяющее с достаточной степенью уверенности оценить работу верхнего уровня приложения. Далее, "снизу" мы бы присоединили сервер обработки запросов, "воткнув" заглушку на уровень общения с драйвером базы данных. При восходящем тестировании мы бы проверили сперва драйвер базы данных, а затем последовательно присоединяли бы модули "сверху". Выбор между нисходящим и восходящим тестированием обуславливается не столько порядком реализации модулей (хотя влияние данного фактора также не стоит отвергать) сколько прогнозированием мест появления багов. Идея состоит в том, что более весомые ошибки необходимо локализовывать на ранних стадиях разработки.
При разработке тестового сценария для тестирования методом белого ящика, необходимо определить критерии допустимости покрытия. Делается это для того чтобы выявить степень покрытия приложения тесткейсами, а в дальнейшем сделать вывод о том, насколько качественный продукт подготовила компания на данной стадии его производства. Существует несколько видов подобных критериев, но наиболее известны и признанны три из них: покрытие команд, покрытие условий и покрытие путей. Остановимся подробнее на каждом из них:
1. Покрытие команд.
Данный критерий говорит нам о том, что необходимо разработать такой набор тесткейсов, которые затронут КАЖДЫЙ оператор в программе хотя бы один раз. Рассмотрим для примера java код простейшего класса, имеющего нетривиальную логику. В суть этой логики вникать не имеет никакого смысла. Главное — это то, что в тестируемом классе имеется набор потенциальных путей, возможных для прохождения. Рассматриваемый критерий позволяет нам выделить минимальный набор тесткейсов (пар значений для входных данных), удовлетворяющих критерию покрытия команд. Количество тесткейсов в данном случае равно одному. Эта пара значений: value_1=5, value_2=6 и соответствующий ожидаемый результат: 11.
2 public int testMethod(int value_1, int value_2){
3 int result=-1;
4 if (value_1==5){
5 result=0;
6 }
7 if(value_1!=value_2){
8 result=value_1+value_2;
9 }
10 return result;
11 }
12 }
Видно, что при таких значениях, каждый оператор выполняется один раз. Используемый подход является наименее предпочтительным, особенно в случае разветвленной логики. В этом случае он не гарантирует корректный поиск ошибок, связанных с выбором вариантов дальнейшего движения.
2. Покрытие условий
Данный критерий более силён по сравнению с предыдущим. Он предполагает, что тесты отвечающие ему, проходят через каждое условие в коде хотя бы один раз. В нашем конкретном примере, тест, отвечающий критерию покрытия команд, отвечает и критерию покрытия условий, т.к. значения проверяются в обоих боках if.
3. Покрытие путей. Для того чтобы составить набор тестов, отвечающих данному критерию, полезно нарисовать блок-схему либо граф выполнения программы. После построения схемы, написанные тесты в совокупности должны провести тестируемый код по всем "стрелочкам". Относительно нашего примера таких тесткейсов будет 4. Если рассматривать их в виде "[(входное-значение-1, входное-значение-2), ожидаемый результат]: покрываемый путь", то описание тестового набора будет выглядеть следующим образом:
[(5,5),0]: 2-3-4-5-7-10
[(5,4),9]: 2-3-4-5-7-8-10
[(4,4),-1]: 2-3-4-7-10
[(4,5),9]: 2-3-4-7-8-10
Вы в праве задать вопрос: "Почему мы используем цифру 4 а не 8? Или не 11. Или не -3". Для ответа на этот вопрос в теории тестирование введено понятие классов эквивалентности. Классы эквивалентности — это классы входных данных, любой элемент из которых при успешном завершении теста на его основе, гарантирует успешность тестов и на остальных элементах из данного класса эквивалентности. В нашем конкретном примере, мы разбили все входные данные на два класса: первый состоит из одной лишь цифры 5, а второй — из всех остальных цифр. Вопрос методики разбиения на классы так же сугубо субъективен и зависит от множества факторов.
Рассмотренные подходы не предназначены для поиска ошибок, связанных с невыполнением ограничений, наложенных спецификациями или иных ограничений. Для этого следует разрабатывать индивидуальные тесты с учетом установленных нюансов. Выбор в пользу тестового набора, удовлетворяющего тому или иному критерию обуславливается многими факторами и является предметом обсуждения внутри компании. Стоит отметить, что для проверки качества тестового набора, часто, проводят т.н. мутационное тестирование. Для этого в код вносят заведомо ошибочные участки Читать далее