** Я перешел со среднего уровня. Новый адрес: 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!]
Мой план битвы был таким:
- Создайте функцию изменения, которая получает исходный код и может изменять все, что имеет значение (чтобы вы могли получить количество доступных мутаций) или конкретную мутацию, указанную индексом.
- Сделайте перехватчик импорта, чтобы файл, который вы хотите изменить, мутировал в памяти на пути с диска. Это позволит распараллелить.
- Плагин Pytest, который устанавливает ловушку импорта и позволяет указать, какую мутацию вы хотите.
- Создайте небольшую программу командной строки, которая выполняет мутации и проверяет результаты тестов. Он также должен иметь возможность применить конкретную мутацию на диске, чтобы, когда вы найдете интересную, вы можете очень легко увидеть, что это был за мутант.
Пункт 1 был довольно простым: в основном мне нужно было убедиться, что все типы узлов AST либо не были мутированы (потому что это не имеет смысла), либо мутировали самым неприятным способом, который я мог придумать. На этом этапе я просмотрел код множества крупных проектов с открытым исходным кодом (например, django и numpy). На этом этапе я обнаружил несколько ошибок синтаксического анализа в baron, но ничего, что могло бы повлиять на код, который я хотел запустить для тестирования мутаций. Я просто сообщил об ошибках и двинулся дальше.
Пункт 2 был неприятным. Оказывается, система ловушек импорта в python - дерьмо. Поведение по умолчанию для загрузки из файловой системы отсутствует в списке перехватчиков, потому что оно где-то находится в коде C, поэтому вы не можете основывать на нем импортер. Это было бы нормально, если бы дизайн был в порядке, но, к сожалению, система ловушек импорта работает следующим образом: Python запрашивает одну ловушку импорта за раз для импорта модуля. Звучит просто и часто - это хорошо, но на самом деле импорт состоит из следующих шагов:
- Найдите исходный файл
- Прочтите исходный файл
- Преобразуйте исходный файл из текста в работающий модуль
И все импортеры должны делать ВСЕ шаги. Таким образом, импортер 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.
Если вам понравился этот рассказ, мы рекомендуем прочитать наши Последние технические истории и Современные технические истории. До следующего раза не воспринимайте реалии мира как должное!