Мутационное тестирование включает многократный запуск вашего набора тестов, изменение кода приложения различными способами, чтобы увидеть, улавливают ли тесты изменения. Если поведение вашего приложения можно изменить, не вызывая сбоя тестового примера, то это конкретное поведение должно быть не очень тщательно протестировано.

Пример. Рассмотрим эту простую функцию, чтобы определить, является ли число положительным.

Предположим, мы «мутируем» код, заменяя > на >=, и набор тестов все еще проходит. Это будет означать, что наш набор тестов не проверяет, возвращает ли функция false, когда n = 0 — важный пограничный случай.

Что, если мы заменим > на <, эффективно изменив его вывод? Если тесты по-прежнему пройдены, мы знаем, что эта функция почти не тестировалась. Наши стандартные инструменты покрытия могут сказать нам, что эти строки были выполнены нашим набором тестов, но если мы не поймаем эту важную мутацию, это будет означать, что выходное значение не проверяется какими-либо утверждениями.

Это действительно ключевое различие между двумя измерениями:

  • Покрытие помогает понять, где вам нужно больше тестовых случаев для проверки непроверенного кода.
  • Мутационное тестирование помогает вам понять, где вам нужно больше утверждений для проверки правильности тестируемого кода, хотя это также может потребовать добавления дополнительных тестовых случаев.

Мутационное тестирование на практике

Я установил Stryker mutator и опробовал его на небольшом побочном проекте. Как только я понял, как переварить отчет, я потратил некоторое время на добавление тестовых случаев и утверждений, чтобы улучшить оценку мутаций и почувствовать преимущества тестирования мутаций.

Вот мои впечатления:

Отчеты содержат больше информации.

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

В приведенном выше отчете вы можете увидеть общую оценку мутации файла, за которой следует код, аннотированный подробностями тестового запуска. Каждая красная метка представляет собой изменение кода, которое не привело к сбою теста, и может быть расширена, чтобы показать точную мутацию. Мутация #10, например, была мутацией StringLiteral, заменяющей 'F' на "".

Вы можете поиграть с отчетом самостоятельно, чтобы прочувствовать его.

Это занимает много времени.

Мутационное тестирование, реализованное компанией Stryker, требует много времени. Я запустил инструмент в другом проекте с несколькими тысячами строк кода и приличным набором тестов, и мне потребовалось больше часа, чтобы запустить полный набор мутаций. Это включало создание более 2500 различных мутаций и запуск полного набора тестов почти столько же раз.

Это определенно не то, что я хочу запускать при каждом запросе на включение!

"Идеально" может быть не лучшей целью

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

Давайте посмотрим, как будет выглядеть «идеально» в приведенном выше коде.

Начнем с того, что набор тестов, который создал приведенный выше отчет, выглядит следующим образом:

Чтобы уловить все мутации, описанные в отчете, нам в основном нужно иметь утверждение для каждой из этих возможных оценок и еще одно, чтобы отловить пограничный случай, когда оценка равна одному из пороговых значений оценки:

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

Итак, какая является правильной целью?

Это сложный вопрос, и, как и на все сложные вопросы, ответ, вероятно, будет "Это зависит от обстоятельств".

Это зависит от кодовой базы, стиля кодирования, культуры тестирования вашей команды и рисков, связанных с отправкой не полностью протестированного кода в конкретный проект.

Одна вещь, которую я здесь усвоил, это то, что не все типы мутаций одинаково важны.

Используя приведенный выше пример в качестве руководства, я думаю, что тесты, проверяющие логику, более полезны, чем те, которые проверяют структуры данных и постоянные значения. Если бы мы хотели доверять литералам, объявленным в нашем коде, мы могли бы обновить наш файл stryker.conf.js, чтобы исключить следующие вещи: excludedMutations: ['ArrayLiteral', 'BooleanLiteral', 'ObjectLiteral', 'StringLiteral']. Я думаю, что тестирование с этими настройками все еще может помочь обнаружить непроверенную логику, не заставляя разработчиков писать слишком многословные и ненадежные тесты.

Чего я не вижу в Stryker, так это какого-то способа аннотировать код, чтобы указать, что определенный фрагмент кода не нужно изменять определенным образом, а-ля /* istanbul ignore next */. Что-то подобное позволило бы нам обдумывать, с какими мутациями мы готовы жить, и при этом соответствовать высокому порогу оценки мутаций. (Будучи частью исходного кода, эта аннотация будет проверена на предмет соответствия стандартам и целям команды.)

Вывод

На данный момент я, вероятно, не готов интегрировать мутационное тестирование во все свои проекты, но я рад, что потратил некоторое время, чтобы узнать об этом.

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

Первоначально опубликовано на сайте revelry.co 12 марта 2019 г.