Программное обеспечение очень сложное. Даже самые простые программы могут иметь сотни строк кода с обширными и сложными взаимозависимостями. Кроме того, код быстро развивается по мере добавления новых функций, а в старых функциях исправлены ошибки. Одним из способов решения этих проблем является создание надежного модульного и функционального пакета тестирования, но эти тесты проходят успешно пропорционально объему кода и функциональности, которые они охватывают. Ниже мы рассмотрим некоторые общие метрики, используемые для оценки успешности модульных тестов, и рассмотрим лучшие практики для каждого из них в AngularJS.
Покрытие кода
Покрытие кода, как показатель модульного тестирования, просто проверяет, сколько строк кода в вашем проекте было обнаружено при запуске пакета модульных тестов. Такие инструменты, как Karma, предлагают статистику покрытия кода в качестве встроенной функции набора тестов, с радостью создавая отчет о покрытии по завершении тестового прогона. Общая мудрость заключается в том, что чем выше процент кода, который вы охватили в своих модульных тестах, тем более надежным будет ваш набор тестов.
Однако при тестировании покрытия кода есть общая проблема. А именно, есть тривиальный пример, показывающий, что 100% -ное покрытие кода не охватывает все ошибки в системе. Взять, к примеру, следующий код:
var custom_divisor = function(op1, op2, modulus) {
sum = 0;
if(op1 > 0)
{
op1 = op1%modulus;
}
op1 = op1/op2;
return op1;
};
Приведенный выше код может обеспечить 100% покрытие модульных тестов с помощью очень простого набора входных данных. Подача 1 для каждого аргумента ударит по каждому утверждению в вышеприведенной функции, но все же полностью пропустит очень очевидную ошибку. Одна из стратегий, чтобы смягчить это, известна как Покрытие филиала.
Охват филиала
Покрытие филиалов расширяется за счет покрытия кода путем отслеживания количества филиалов, присутствующих в вашем коде. Взять, к примеру, следующий оператор if-else:
if(i < 0) {
j += 1;
}
else if (j < 0){
i+=1
}
Этот код имеет три ветви, которые необходимо проверить:
1 — первый оператор if является истинным
2 — первый оператор if является ложным, а else-if является истинным
3 — первый оператор if является ложным, а else-if является ложным.
Это тестирует каждую ветвь в коде, которая, будучи построена на метрике высокого процентного охвата кода, может добавить дополнительные гарантии надежности в ваш набор тестов. Такие инструменты, как Karma, предлагают встроенное измерение охвата филиала за счет использования Стамбула.
Однако, опять же, охват ветвления не достаточен, чтобы поразить каждую ошибку в вашей программе. Возьмем, к примеру, ту же самую функцию примера сверху:
var custom_divisor = function(op1, op2, modulus) {
sum = 0;
if(op1 > 0)
{
op1 = op1%modulus;
}
op1 = op1/op2;
return op1;
};
Мы можем легко достичь полного охвата ветвления и кода с помощью двух тестов:
1 — Результат теста, когда op1, op2 и модуль все равны 1
2 — Результат теста, когда op2 и модуль равны 1, но op1 равен -1
Однако вышеупомянутое еще раз не может выявить критическую ошибку в делении. Чтобы наконец-то уловить эту ошибку, нам нужно взглянуть на покрытие входного домена.
Входной охват домена
Покрытие входного домена попадает в последнюю доступную ручку для тестирования интерфейсов функций. Вкратце, оно охватывает тестирование всего входного домена функции. Допустим, у нас есть аргумент для функции op1, и этот op1 имеет тип integer. Полное покрытие входного домена охватывает все возможные значения от INT_MIN до INT_MAX. Поскольку каждый аргумент функции будет проверен с каждым потенциальным значением, в вашем коде будет найдена практически любая возможная ошибка с одним потоком выполнения.
Однако проблема с этим подходом должна быть до боли очевидной — существует более четырех миллиардов возможных значений! Добавление четырех миллиардов тестов в любой набор тестов явно неэффективно, и это только для одной переменной. Для полного охвата входных данных необходимо протестировать все возможные перестановки аргументов — для трех операндов типа int вам потребуется куб из четырех миллиардов тестов!
В поисках счастливой среды
Так что, если мы не можем зависеть от 100% покрытия кода ИЛИ от 100% покрытия филиала и если тестирование входной области невозможно, как мы можем гарантировать, что наш код полностью протестирован? Печальная реальность заключается в том, что в конечном итоге вы не можете гарантировать, что ваш код будет полностью свободен от ошибок только за счет модульного тестирования. Оставляя в стороне огромное количество тестов, необходимых для достижения полного охвата кода и ветвлений, в большинстве случаев входная область просто слишком широка, чтобы обеспечить абсолютную уверенность в вашем наборе тестов.
Решение здесь состоит в том, чтобы расслабиться на догматизме, когда дело доходит до модульного тестирования. 100% тестовое покрытие — замечательная цель, но если потребуется еще одна неделя, чтобы перейти от 95% тестового покрытия до 100%, и, как мы продемонстрировали выше, 100% охват даже не гарантированно отлавливает все ошибки, не так ли? стоит потратить это время на тестирование, а не на разработку новых функций? Вместо этого, немного планирования и размышления должны быть вложены в каждый тест. В самом деле, попробуйте протестировать весь код в функции и попытаться поразить все ветви, но используйте здравый смысл, чтобы определить, когда — или даже если — ваш набор для тестирования становится слишком большим. Ограничьте свое тестирование общими значениями и парой выбросов, которые могут вызвать проблемы в вашем коде, и вы сможете обнаруживать подавляющее большинство ошибок в вашем программном обеспечении — даже без полного охвата тестированием.Соедините это с тщательным ручным контролем качества вашего приложения, и ваш код будет максимально безопасным.
Вывод
Модульное тестирование является жизненно важной практикой для разработки стабильного программного обеспечения. Однако оценить успех вашего набора тестов — трудная задача. Как видно выше, наиболее очевидные метрики — охват кода и ветки — не дают полной гарантии того, что ваш код полностью без ошибок. Во второй части нашей серии модульного тестирования мы рассмотрим некоторые передовые практики, которые позволят смягчить многие проблемы тестирования и помогут вам разработать более надежный пакет модульного тестирования.