** Я перешел со среднего уровня. Новый адрес: kodare.net **

Переходите к разделу "Насколько это может быть сложно?" если вас не волнует фон.

Что такое мутационное тестирование?

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

Примеры мутаций: изменение «‹ »на« ‹=». Если вы не проверили точное граничное условие в своих тестах, у вас может быть 100% покрытие кода, но вы не пройдете тестирование на мутации.

Фон

Я хотел опробовать тестирование мутаций для библиотек, которые я создаю на Python, поэтому я посмотрел, что было доступно. У меня было несколько идей о способах радикального ускорения тестирования мутаций на основе того, что делает pytest-testmon, и добавление некоторых моих собственных идей поверх.

Поиск в Google показал мне две альтернативы:

  • Mutpy: простая система, разработанная как диссертация. Больше не поддерживается.
  • Космический Луч: активно развивается.

Оба они относятся только к Python 3, что немного грустно, потому что я все еще использую Python 2 для работы, по крайней мере, еще на год. Но я мог бы смириться с этим, поскольку библиотеки - это Python 2 и 3.

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

Затем я посмотрел на mutpy. Этот код радикально меньше и проще, но после некоторых попыток его рефакторинга, чтобы сделать его еще проще, я подумал про себя:

Как трудно это может быть?

Оказывается, не так уж и плохо! В основном строительные блоки уже доступны.

Я решил, что мне абсолютно нужна функция, которой не хватало как в Cosmic Ray, так и в mutpy: возможность применить мутацию к исходному файлу и не испортить весь файл. Cosmic Ray и mutpy используют Pythons, встроенные в библиотеку AST, но у них есть досадное свойство не представлять форматирование в AST, что делает невозможным просто сбросить AST и получить исходный файл. Что тогда, если я не могу использовать собственный AST Pythons? Откройте для себя baron, независимо разработанную ›библиотеку Python-› AST, специально созданную, чтобы иметь возможность путешествовать туда и обратно без изменения исходных файлов. К сожалению, Baron не поддерживает весь синтаксис Python 3, но похоже, что люди над этим работают.

[EDIT: после этой статьи я заменил Baron на Parso, и теперь я полностью поддерживаю Python 3!]

Мой план битвы был таким:

  1. Создайте функцию изменения, которая получает исходный код и может изменять все, что имеет значение (чтобы вы могли получить количество доступных мутаций) или конкретную мутацию, указанную индексом.
  2. Сделайте перехватчик импорта, чтобы файл, который вы хотите изменить, мутировал в памяти на пути с диска. Это позволит распараллелить.
  3. Плагин Pytest, который устанавливает ловушку импорта и позволяет указать, какую мутацию вы хотите.
  4. Создайте небольшую программу командной строки, которая выполняет мутации и проверяет результаты тестов. Он также должен иметь возможность применить конкретную мутацию на диске, чтобы, когда вы найдете интересную, вы можете очень легко увидеть, что это был за мутант.

Пункт 1 был довольно простым: в основном мне нужно было убедиться, что все типы узлов AST либо не были мутированы (потому что это не имеет смысла), либо мутировали самым неприятным способом, который я мог придумать. На этом этапе я просмотрел код множества крупных проектов с открытым исходным кодом (например, django и numpy). На этом этапе я обнаружил несколько ошибок синтаксического анализа в baron, но ничего, что могло бы повлиять на код, который я хотел запустить для тестирования мутаций. Я просто сообщил об ошибках и двинулся дальше.

Пункт 2 был неприятным. Оказывается, система ловушек импорта в python - дерьмо. Поведение по умолчанию для загрузки из файловой системы отсутствует в списке перехватчиков, потому что оно где-то находится в коде C, поэтому вы не можете основывать на нем импортер. Это было бы нормально, если бы дизайн был в порядке, но, к сожалению, система ловушек импорта работает следующим образом: Python запрашивает одну ловушку импорта за раз для импорта модуля. Звучит просто и часто - это хорошо, но на самом деле импорт состоит из следующих шагов:

  1. Найдите исходный файл
  2. Прочтите исходный файл
  3. Преобразуйте исходный файл из текста в работающий модуль

И все импортеры должны делать ВСЕ шаги. Таким образом, импортер zip-файла должен выполнять те же действия, что и по умолчанию, и он не может просто вызывать загрузчик по умолчанию, потому что он не существует в виде кода Python. И это также означает, что если я хочу выполнить перехват между шагами 2 и 3 на ВСЕХ импортерах, я должен повторно реализовать всех импортеров.

Это явно отстой (и может быть даже невозможно для систем со своими собственными пользовательскими импортерами), но еще хуже то, что правильная реализация ловушки импорта - это большой нетривиальный код, который чертовски сложно разобраться. Предположительно, это несколько лучше в python 3 с importlib, поэтому я нашел backport его для python 2, но он был сломан. Мне удалось обойти сбои, но, в конце концов, я не смог заставить мою ловушку импорта работать и с этим. Я тоже просил помощи на Reddit, но безрезультатно.

После нескольких часов борьбы с этой битвой я сдался (пока) и просто перешел на дисковую мутацию. Это не здорово, потому что его нельзя запускать параллельно, но, по крайней мере, он работает и очень прост. Он также очень гибок в отношении того, какой инструмент запуска тестов вы используете, поскольку вам не нужны какие-либо плагины, которые нужно было бы создавать для pytest, носа, unittest и т. Д., Один за другим. Отказ от этого означает, что пункт 3 становится спорным, так что это здорово.

По сути, мне все равно следовало сделать это первым, потому что это очень хорошо, если у меня есть: P

Пункт 4 был довольно простым. Самым сложным было выяснить, как красиво выводить на консоль постоянные обновления прогресса: P

Так где же я сейчас стою?

Мы запустили mutmut на tri.declarative и tri.struct на работе и обнаружили несколько вещей, которые мы не тестировали так тщательно, как мы думали, даже несмотря на то, что у нас было 100% покрытие на наших тестах. Для tri.declarative он также обнаружил кусок мертвого кода и ошибку при правильном создании множественного числа в сообщении об ошибке. Он явно улучшил наш набор тестов, хотя не обнаружил никаких ошибок.

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

Что дальше?

У меня еще есть над чем поработать. Использование pytest-testmon все еще в моем списке, как и решение системы ловушек импорта для включения распараллеливания. Только эти вещи должны дать мне возможность проводить тесты на порядки быстрее. Цель состоит в том, чтобы сохранить суперпростую систему, которая у меня есть сейчас, чтобы всегда была простая для отладки и адаптации система, которую можно было бы использовать для странных сценариев или отладки.

У меня также есть некоторые идеи для pytest-testmon, например, возможность хранить центральную базу данных, совместно используемую между разработчиками, которая также может использоваться для mutmut, поэтому, если кто-то уже пытался запустить конкретную мутацию для определенной версии файла, вы этого не сделаете. придется.

Хакерский полдень - это то, с чего хакеры начинают свои дни. Мы часть семьи @AMI. Сейчас мы принимаем заявки и рады обсудить рекламные и спонсорские возможности.

Чтобы узнать больше, прочтите нашу страницу о нас, поставьте лайк / напишите нам в Facebook или просто tweet / DM @HackerNoon.

Если вам понравился этот рассказ, мы рекомендуем прочитать наши Последние технические истории и Современные технические истории. До следующего раза не воспринимайте реалии мира как должное!