Юнит-тестирование

Как тестировать модульные тесты

Краткое введение в мутационное тестирование

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



После публикации этой статьи я получил ответ от программного обеспечения .com, указав мне на тестирование мутаций, о котором я хочу рассказать вам сегодня. Короче говоря:

Мутационное тестирование позволяет выявить слабые тесты.

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

Теперь давайте посмотрим, что это такое!

Слабые тесты

Рассмотрим эту очень простую функцию:

def mean(x, y):
    return (x+y) / 2

Предположим, что вы хотите написать модульный тест, несмотря на простоту этой функции. Файл test_mean.py может выглядеть так:

def mean(x, y):
    return (x+y) / 2

def test_mean():
    assert mean(4, 0) == 2

Вызов pytest test_mean.py, конечно же, приведет к прохождению теста. Все зелено, и мы даже получили 100% покрытие! Пришло время перейти к следующей функции, не так ли? Нет!

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

def definitely_not_the_mean(x, y): 
    return (x-y) / 2 # test_mean still passes

Даже после этой простой мутации функции — замены плюса на минус — test_mean все равно проходит.

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

Такой тест так же хорош, как металлоискатель, который издает шум даже при поднесении дерева. Вот почему мы называем test_mean слабым тестом.

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

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

Мы можем использовать тестирование мутаций, чтобы выявить слабые тесты. Идея мутационного тестирования уже довольно старая: ее впервые предложил Ричард Липтон, будучи еще студентом, в 1971 году. Кроме того, она очень проста, на самом деле настолько проста, что я рассказал о ней уже в предыдущей главе.

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

Вы уже видели одну мутацию, изменение с + на -. Кроме того, вы можете изменить:

  • - to +
  • / в * или наоборот,
  • от ≥ до › или даже ‹,
  • ≤ до ‹ или даже ›
  • = к != или наоборот
  • добавить или вычесть 1 из целых чисел
  • добавить несколько случайных символов в строки

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

Конечно, вы можете сделать это вручную и несколько раз изменить исходный код, всегда сопровождаемый pytest . Но поскольку это неинтересно, люди создали пакет, который делает это автоматически и регистрируют результаты.

Mutmut для удобного тестирования мутаций

Anders Hovmöller aka boxed и многие другие участники создали пакет под названием mutmut, чтобы сделать всю работу за вас.

Сначала установите его через pip install mutmut . Затем вам нужно извлечь функции, которые вы хотите протестировать, в файл, назовем его function.py. Давайте также предположим, что тест находится в test_mean.py. Тогда вы можете позвонить

mutmut run --paths-to-mutate function.py

и это создаст некоторые мутации вашего файла function.py, содержащего функцию mean, которую мы хотим протестировать.

Если вы откроетеfunction.pyв редакторе, который постоянно перезагружает файл во время работы mutmut, вы даже можете увидеть, как файл изменяется в реальном времени! Но не беспокойтесь: mutmut сначала создаст копию исходного файла, прежде чем вносить изменения.

Вывод будет выглядеть следующим образом:

Здесь вы можете увидеть несколько вещей, особенно внизу:

  1. Было создано три мутанта. (примечание: от + до -, / до *, в нашем случае от 2 до 3)
  2. Двое из этих трех мутантов были убиты, а это означает, что пройденный ранее модульный тест не прошел после изменения файла, и это хорошо 🎉.
  3. Один мутант выжил, а это означает, что тест все же прошел, хотя исходный код изменился. Это плохо 🙁. Мы даже видели этого выжившего уже:
def mean(x, y): 
    return (x-y) / 2 # the surviving mutant

Вы также можете создать HTML-отчет через mutmut html и открыть его в браузере:

Дизайн мог быть и лучше, но он содержит основную информацию о выживших.

Примечание. Иногда два слабых одиночных теста могут составить один лучший набор тестов. Например, если вы добавите утверждение assert mean(-1, 1) == 0 — еще один слабый тест, поскольку мутант return (x+y) * 2 выживет — в целом ни один мутант не выдержит процедуру mutmut, что улучшит набор тестов.

Заключение

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

Мутационное тестирование предлагает структурированный способ изменения (мутации) функции. Библиотека Python mutmut даже позволяет нам автоматически изменять наш исходный код (предварительно создается резервная копия) и проверять, какие тесты все еще проходят (🙁), а какие не проходят после мутации (🎉).

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

Надеюсь, что сегодня вы узнали что-то новое, интересное и полезное. Спасибо за прочтение!

И последнее, если вы

  1. поддержите меня в написании дополнительных статей о машинном обучении и
  2. в любом случае планируйте получить подписку на Medium,

почему бы не сделать это по этой ссылке? Это бы мне очень помогло! 😊

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

Большое спасибо, если вы поддержите меня!

Если у вас есть вопросы, пишите мне в LinkedIn!