TLDR; применяй сразу тут и примеры запуска на github
Пролог
Эта статья является прообразом доклада на Dotnext 2019, где я рассказал о применении подхода мутационного тестирования в проектах на .NET Core.
Доклад вызвал неоднозначную реакцию, от "Зачем это необходимо?" до "Как я жил без этого раньше".
Для популяризации подхода я решил опубликовать прообраз доклада - черновики в нескольких частях. В конце вы найдете ссылку на видео, если просто хотите посмотреть доклад со слайдами.
Я являюсь мейтейнером одной популярной библиотеки Flurl для работы с HttpClient в виде builder. Она позволяет легко и просто составлять запрос к API.
Эта библиотека вдохновлена принципом TDD, kill фичей этой библиотеки является способность прозрачно тестировать ваши сервисы и ПО, когда они обращаются через http во внешний мир.
И библиотека на основе Flurl, которую я разрабатываю, Ecwid CLI - это тоже вдохновленный принципам TDD клиент к хранилищу данных для интернет магазинов, построенный на паттерне builder.
Примеры мутаций я буду давать на основе опыта внедрения для этих двух библиотек.
О чем пойдет речь?
Часть 1 - вы читаете эту часть
- тестирование и для чего применять еще один вид
- про принцип мутационного тестирования и анализа
- виды мутаций
Часть 3
- как применить в .NET
- когда использовать мутационное тестирование и как подготовится
Тестирование
Видов тестирования очень много и каждый разработчик сталкивается с ними каждый день.
В своей практике разработчики встречаются с ручным функциональным тестированием (вы это это делаете постоянно, когда вносите исправление), с автоматизированным тестированием изолированных частей (unit тестирование), с автоматическим интеграционным тестированием или UI тестированием.
Для чего нам необходим еще один вид тестирования? Мы не тестировщики и писать тесты, как разработчики, особо не любим.
Вопрос к вам? А кто из вас перфекционист? А кто уверен в своих тестах на все 100 процентов? Что бы прям "зуб поставить", что они словят именно то, на что рассчитаны?
А есть ли у вас покрытие тестами более 70%?
А у кого ошибки в ПО повлияют на деньги клиентов, репутацию вашей компании или, не дай бог, жизнь и здоровье людей?
Для компаний и проектов с такими высокими рисками очень важно тестирование с покрытием на всей пирамиде тестирования близким к 100%, потому что другим способом проверить соответствие требованиям не можем. Или можем?
Есть один способ - это тестирование на выбранной группе лиц, например, анонимное тестирование медикаментов на группе беременных женщин по всему миру. Кто в здравом уме согласится сделать это добровольно? А если не сказать и опросить постфактум применения?
Хотели бы вы, например, поездить на Тесле, когда Илон Маск вам выкатит апдейт, который будет чуть чуть недостетирован? Или тестирование плохо кончились?
У кого нет покрытия в 70% знайте, - или ваш бизнес камикадзе или вы камикадзе. Тут, конечно, все грешные, и проблемы с написанием тестов и полноценным тестированием есть в больших компаниях. Редко какой бизнес готов ждать.
Самая частая причина не написания тестов - это отсутствие времени, выделенного на это.
Но есть и обратный пример, когда компании пришлось полностью реализовать пирамиду тестирования, потому что иначе она просто не сможет вообще выпустить продукт.
У компании Oracle в ее главном продукте Oracle RDBMS написано порядка 40 тысяч автотестов, они прогоняются на большом кластере несколько часов и только они позволяют вносить исправления и доработки в код и сделать так, чтобы программный продукт не растекался как теплое желе при изменениях.
Про цель тестирования
Так зачем нам нужно во всех случаях тестирование как таковое?
Ради красивых цифр в code coverage? Ради бейджика, что вы умеете их разрабатывать? Для строчки в резюме “Пишу код на TDD”?
Нет
Тестирование необходимо для того, чтобы проверить соответствует ли программное обеспечение заявленным требованиям до того, как его будут использовать настоящие потребители. И провести регресс раньше наших пользователей при рефакторинге или доработке продукта.
Так как же можно протестировать или валидировать тесты? Откуда вы знаете что они написаны корректно и действительно помогают вам добиться устойчивости вашего ПО?
Вы можете проводить code-review кода тестов, посчитать code-coverage для понимания какие участки кода покрыты тестами, вы можете запускать много раз тесты в разных окружениях, в pipeline и без него, при каждом билде, вы можете отдать ревью тестов на аудит, но в конечном итоге вы будете ориентироваться на мнение человека, а люди ошибаются или невнимательны. И все эти мероприятия полностью не покажут качество ваших тестов.
Нет других способов автоматизированно проверить ваши тесты на профпригодность. Кроме как… сломать их!
Вопрос к вам? Кто может дать гарантию того, что вы откроете в редакторе ваш код, внесете в него ошибку и ваши тесты гарантированно упадут?
Единственная возможность проверить вашу систему тестов до продакшена или до того как руки аутсорсеров в него внесут изменения - это внести в нее ошибку! Намеренно и специально. И посмотреть, что получится. Этим и занимается мутационное тестирование.
Именно так частично тестируют авионику и обучают пилотов самолетов, когда на симуляторе вносят изменения в полете и тестируют систему самолет-человек на устойчивость к ошибкам и нештатным ситуациям.
Парадигмы TDD-BDD и MDD
Существуют практики по разработке и по написанию тестов, которые вам хорошо известны - это TDD и BDD, разработка через тестирование и разработка на основе поведения пользователя.
Мутационное тестирование и разработку на основе мутаций давайте обзовем MDD - Mutation Driven Development
. Оно пронизывает все слои тестирования и проверяет целиком систему на устойчивость.
Внимание ! Так же MDD можно описать как Main Depression Disaster
, что означает основное депрессивное расстройство личности - это именно то, что вас ждет, когда вы увидите, что те тесты, которые вы долго и мучительно писали, причесывали, разбивали на группы и создавали тест кейсы, никуда не годятся. Первая мысль будет - ну его… С этой депрессией необходимо бороться на практике и на этапе написания кода.
Справившись с депрессией, MDD будет для вас хорошей практикой написания кода и тестов с точки зрения применимости к ним разнообразных мутаций еще до запуска самого мутационного тестирования, оно меняет сознание разработчика так же как TDD и BDD.
Эти практики хороши тем, что даже если вы их не используете каждый день, они меняют вас, и в итоге код вы пишите более собранно, с перспективой на будущее, более качественно и лаконично.
Исторический контекст и определение мутационного тестирования
Мутационное тестирование - это преднамеренное внесение в код специальных изменений - мутаций с целью проверки программного обеспечения.
Мутационный анализ появился как термин в 1971 году и был впервые применен на практике в 80тых. Основная сложность его применений полномасштабно, как и вообще всеобъемлющего тестирования - это стоимость. Мутационное тестирование требовательно к ресурсам и требует от разработчиков много времени для разбора мутаций и анализа влияния их на конечный результат.
ПО может сопротивляться изменениям двумя способами:
в одном нам помогает сам язык - это система типов и компиляция приложения и ваша архитектура.
второе - это тесты на разных уровнях, которые проверяют соответствует ли ПО тем требованиям, которые к нему предъявляют и, если соответствия нет, система проверки должна говорить - нет.
Вопрос к вам? Как вы думаете какой тест лучше зеленый или красный?
Лучше красный, он или неверно написан или словил то, для чего был написан. В зеленый тест вы смотреть не будете никогда. Неважно работает корректно он или нет.
Все на деле просто: вы внесли ошибку, прогнали все тесты, тесты упали - отлично! Система отреагировала правильно.
Не упали - значит вы плохо написали тесты или архитектура приложения запутанная, далека от чистых функций, и вы поймаете ошибку в продакшене рано или поздно.
И хорошо если, просто очередной котик на сайте не покажется пользователю.
В итоге мутант может быть убит упавшим тестом - это убитый мутант или ни один тест не среагировал на него - это выживший мутант.
Другим языком убитая мутация это такое условие:
Убитая мутация М - такая мутация, для которой существует тест t из набора тестов Т, для которого результат выполнения на модифицированном мутацией М коде не равен начальному результату на немодифицированном коде.
Разберем самый простой пример
bool Method(bool a, bool b) {
if (a && b) { // mutate to ||
return true;
} else {
return false;
}
}
[Fact]
[InlineData(false, false)]
void Method_ReturnFalse(bool a, bool b) {
// Act
var result = Method(a, b);
// Assert
Assert.False(result);
}
Мы мутируем && оператор.
Мутант выживет или нет?
Для тогда что бы тест убил мутанта должны быть соблюдены следующие условия:
- Тест должен достигнуть кода, то есть покрытие этих строк должно быть 100% - Покрытие
- Входные данные для теста должны быть полными, чтобы мутант был убит - проверяются все граничные условия - Тестовые данные
- Значение выходных параметров должно влиять на тесты и проверяться в тестах - Достаточная Ассертированность
Вот 3 условия при котором будет убит мутант.
int Method(bool a, bool b) {
int c;
if (a && b) { // mutate to ||
c = 1;
} else {
c = 0;
}
return c;
}
[Fact]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
void Method_ReturnFalse(bool a, bool b) {
// Act
var result = Method(a, b);
// Assert
Assert.False(result);
}
Общая модель мутационного тестирования
Как работают системы, которые занимаются мутационным тестированием?
Сначала анализируется выбранный код, создается AST и генерируются все возможные мутации, отбрасываются гарантированно некорректные мутации, которые не дадут, например скомпилировать код.
Потом создается словарь мутаций, далее идет наиболее сложная часть мутационного тестирования - это выявление эквивалентных мутаций (об них мы поговорим чуть позже) - это мутации, которые невозможно убить никаким тестом.
После этого ваш код собирается со всеми мутациями N раз и результат сборки подмешивается в набор тестов.
Запускаются тесты и падают или не падают.
После этого идет сбор статистики, ее выдача в виде, который может проанализировать разработчик.
Продолжение в следующей части...
Литература
Ну а если вы любите больше смотреть, чем читать - версия конца 2019 года