Рубрики
Без рубрики

Введение в тестирование на мутации – или почему покрытие отстой

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

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

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

Автоматизированные тесты, несомненно, в настоящее время воспринимаются как основополагающие для разумной кодовой базы с низким уровнем ошибок. Вы создаете набор тестов (называемый набором тестов) и обеспечиваете ожидаемое поведение при каждом изменении (или, по крайней мере, перед каждым развертыванием), выполняя набор тестов снова и снова. Как следствие, это нормально, что команда хочет знать, насколько хороши их тесты: нужно ли им добавлять больше тестов или набор тестов уже достаточно хорош?

Чтобы помочь ответить на этот вопрос, покрытие кода (обычно сокращается до покрытия) долгое время рассматривалось как показатель де-факто при попытке оценить качество тестов. Но хороший ли это показатель?

Что покрытие

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

Покрытие кода – это измерение процента строк кода, выполненных во время набора тестов.

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

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

Почему покрытие отстой

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

public boolean biggerThanTen(int x) {
  if(x >= 10) {
    return true;
  } else {
    return false;
  }
}

и соответствующий модульный тест:

@Test
public boolean myTest() {
  assertTrue(biggerThanTen(11));
  assertFalse(biggerThanTen(9));
}

Можете ли вы догадаться, какова ценность покрытия кода? Это верно, 100%! Но вы легко можете видеть, что мы не проверяем граничное значение: что произойдет, если мы пройдем 10 в качестве аргумента? Кажется очевидным, что, несмотря на 100% охват, наш набор тестов имеет серьезный недостаток!

Но хотите ли вы увидеть еще более неприятный пример?:)

@Test
public boolean myTestV2() {
  biggerThanTen(11);
  biggerThanTen(10);
  biggerThanTen(9);
}

Этот тест охватывает все случаи и сообщает о 100% охвате, но… вы заметили, что мы совершенно забыли указать возвращаемое значение функции? И поверьте мне, это было бы не первое, что случилось бы в реальной жизни!

Тестирование на мутации

К настоящему времени вы должны быть убеждены, что покрытие кода содержит некоторые реальные проблемы. Мы видели всего 2 случая, но я почти уверен, что мы могли бы найти еще несколько.

Итак, какие у нас есть альтернативы?

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

МТ основана на двух гипотезах:

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

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

Основные понятия

Прежде чем перейти к некоторым практическим примерам, давайте просто рассмотрим некоторые основные концепции тестирования мутаций:

  • Операторы мутации/мутаторы: Мутатор – это операция, применяемая к исходному коду. Основные примеры включают замену оператора '>' на '<' , замену 'и' операторами 'или' и замену других математических операторов, например.

  • Мутанты: Мутант является результатом применения мутатора к сущности (в Java это обычно класс). Таким образом, мутант – это модифицированная версия класса, которая будет использоваться во время выполнения набора тестов.

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

  • Эквивалентные мутации: Вещи не всегда бывают белыми или черными. Зебры действительно существуют! Что касается предмета тестирования на мутации, то не все мутации интересны, потому что некоторые приведут к точно такому же поведению. Это называется эквивалентными мутациями. Эквивалентные мутации часто выявляют избыточный код, который может быть удален/упрощен. Просто посмотрите на следующий пример:

// original version
int i = 2;
if (i >= 1) {
    return "foo";
}

// mutant version
int i = 2;
if (i > 1) { // i is always 2, so changing the >= operator to > will be exactly the same
    return "foo";
}

Тестирование мутаций для Java

Для языка Java существует потрясающий фреймворк MT под названием PIT . Последние несколько месяцев я экспериментировал с этим в небольшом проекте, и он выявил несколько интересных случаев.

Почему ЭТО здорово? Прежде всего, в нем есть плагин maven (я знаю, это звучит как базовый, но большинство инструментов MT пока больше в исследовательском стиле и их сложно использовать). Во-вторых, это чрезвычайно эффективно. PIT сосредоточился на том, чтобы сделать MT пригодным для использования, и, ИМХО, он неплохо справляется.

Чтобы начать работу с PIT в проекте maven вам просто нужно добавить плагин maven:


    org.pitest
    pitest-maven
    LATEST 
 

И затем у вас есть MT, доступный в вашем проекте, запустив:

mvn org.pitest:pitest-maven:mutationCoverage

Проверьте HTML-отчет, созданный по адресу /цель/pit-отчеты/<дата>

Для данного класса вы увидите зеленые линии, указывающие на то, что мутанты были убиты, или красные линии, указывающие на отсутствие теста, охватывающего эту линию, или на то, что мутант выжил.

Проблемы с MT

Тестирование на мутации явно не идеально. Одна из главных причин того, что она еще не получила широкого распространения это факт, который требует больших вычислительных мощностей на больших кодовых базах: множество возможных мутантов, множество тестов, больше кода для компиляции и т. Д. Все это значительно увеличивает время выполнения мутаций. ЯМА борется с этой проблемой с помощью нескольких оптимизаций, касающихся:

  • Поколение мутантов
  • Вставка мутанта
  • Выбор теста
  • Инкрементный анализ

Другая проблема заключается в том, что MT может легко создавать мутантов, из-за которых ваши тесты будут неинтересными, особенно если вы не выполняете модульные тесты по книге. Например, если вы используете базу данных H2, MT может изменить значения конфигурации, которые приведут к сбою соединения. Издевательства также могут быть проблематичными.

К счастью, ОН позволяет указать, какие классы и методы не следует мутировать, и поддерживает все основные фреймворки java-макетов. Но другие инструменты MT могут быть не такими надежными, как PIT.

Экстремальная мутация

Экстремальная мутация – это еще одна стратегия тестирования мутаций, направленная на упрощение и увеличение скорости MT. Он характеризуется заменой всей логики метода аннулируемым блоком: в java у нас не было бы кода для методов void , простого возвращающего null; оператора для методов, возвращающих объекты, или возвращающих некоторые константы.

Основаниями для экстремальной мутации являются следующие:

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

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

public Optional getSomething(String baseStr) {
        if(baseStr.length() > 10) {
            baseStr += " -> this had more than 10 chars";
        }

        if(baseStr.startsWith("Why")) {
            baseStr += ", and it was probably a question";
        }

        if (baseStr.contains("")) {
            return Optional.empty();
        } else {
            return Optional.of(baseStr);
        }
}

метод – это хороший уровень абстракции, позволяющий рассуждать о коде и наборе тестов; экстремальная мутация генерирует гораздо меньше мутантов, чем стратегия по умолчанию/классическая; экстремальная мутация – хороший предварительный анализ для усиления набора тестов перед запуском мелкозернистой мутации. В следующем фрагменте показан исходный метод до того, как экстремальная мутация превратит его в: eing mutated: perators.

public Optional getSomething(String baseStr) {
  return Optional.empty();
}

метод – это хороший уровень абстракции для рассуждений о коде и наборе тестов; экстремальная мутация генерирует гораздо меньше мутантов, чем стратегия по умолчанию/классическая; экстремальная мутация – хороший предварительный анализ для усиления набора тестов перед запуском мелкозернистой мутации. В следующем фрагменте показан исходный метод до того, как экстремальная мутация превратит его в: eing Mutat для PIT есть движок, который реализует экстремальную мутацию: декарт ред: авторы. . метод – это хороший уровень абстракции для рассуждений о коде и наборе тестов; экстремальная мутация генерирует гораздо меньше мутантов, чем стратегия по умолчанию/классическая; экстремальная мутация – хороший предварительный анализ для усиления набора тестов перед запуском мелкозернистой мутации. В следующем фрагменте показан исходный метод до того, как экстремальная мутация превратит его в: eing Mutatвы должны полностью попробовать его в своем проекте как гораздо более быструю альтернативу . Для PIT есть движок, который реализует экстремальную мутацию:

метод – это хороший уровень абстракции для рассуждений о коде и наборе тестов; экстремальная мутация генерирует гораздо меньше мутантов, чем стратегия по умолчанию/классическая; экстремальная мутация – хороший предварительный анализ для усиления набора тестов перед запуском мелкозернистой мутации. В следующем фрагменте показан исходный метод до того, как экстремальная мутация превратит его в: eing Mutatвы должны полностью попробовать его в своем проекте как || гораздо более быстрый альтернативный вариант || . Для PIT есть движок, который реализует экстремальную мутацию: || декарт ред: операторы.

метод – это хороший уровень абстракции для рассуждений о коде и наборе тестов; экстремальная мутация генерирует гораздо меньше мутантов, чем стратегия по умолчанию/классическая; экстремальная мутация – хороший предварительный анализ для усиления набора тестов перед запуском мелкозернистой мутации. В следующем фрагменте показан исходный метод до того, как экстремальная мутация превратит его в: eing Mutatвы должны полностью попробовать его в своем проекте, поскольку || гораздо более быстрое покрытие alteCode имеет некоторые недостатки, которые не позволяют ему быть источником истины относительно эффективности набора тестов. Вывод родной || . Для PIT есть движок, который реализует экстремальную мутацию: || декарт ред: операторы. Тем не менее, это действует как красный флаг: если у вас покрытие 10%, вы явно недостаточно тестируете свой код (если только у вас нет тонны шаблонного кода, что также является красным флагом).

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

Несмотря на проблемы, связанные с этой концепцией, такие инструменты, как PIT, создают решения и делают MT надежным решением для оценки надежности набора тестов.

Если вы заинтересованы в тестировании MT на других языках программирования, взгляните на:

Список других полезных ресурсов:

И презентация, которую я провел в Pixels Camp 2019: https://speakerdeck.com/pedrorijo91/mutation-testing-pixels-camp-2019

Оригинал: “https://dev.to/pedrorijo91/an-intro-to-mutation-testing-or-why-coverage-sucks-3anp”