Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Тестирование мутаций — это способ оценить качество наших модульных тестов. Для тестирования мутаций средство Stryker.NET автоматически выполняет изменения в коде, выполняет тесты и создает подробный отчет с результатами.
Пример тестового сценария
Рассмотрим пример класса PriceCalculator.cs с методом Calculate, который вычисляет цену, учитывая скидку.
public class PriceCalculator
{
public decimal CalculatePrice(decimal price, decimal discountPercent)
{
if (price <= 0)
{
throw new ArgumentException("Price must be greater than zero.");
}
if (discountPercent < 0 || discountPercent > 100)
{
throw new ArgumentException("Discount percent must be between 0 and 100.");
}
var discount = price * (discountPercent / 100);
var discountedPrice = price - discount;
return Math.Round(discountedPrice, 2);
}
}
Приведенный выше метод рассматривается следующими модульными тестами:
[Fact]
public void ApplyDiscountCorrectly()
{
decimal price = 100;
decimal discountPercent = 10;
var calculator = new PriceCalculator();
var result = calculator.CalculatePrice(price, discountPercent);
Assert.Equal(90.00m, result);
}
[Fact]
public void InvalidDiscountPercent_ShouldThrowException()
{
var calculator = new PriceCalculator();
Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(100, -1));
Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(100, 101));
}
[Fact]
public void InvalidPrice_ShouldThrowException()
{
var calculator = new PriceCalculator();
Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(-10, 10));
}
Предыдущий код выделяет два проекта, один для службы, которая выступает в качестве PriceCalculator, а другая — тестовый проект.
Установка глобального инструмента
Сначала установите Stryker.NET. Для этого необходимо выполнить команду:
dotnet tool install -g dotnet-stryker
Чтобы запустить stryker, вызовите его из командной строки в каталоге, где находится проект модульного теста:
dotnet stryker
После выполнения тестов отчет отображается в консоли.
Stryker.NET сохраняет подробный HTML-отчет в каталоге StrykerOutput.
Теперь рассмотрим, что такое мутанты, а также что означают "выжило" и "убито". Мутант — это небольшое изменение в коде, которое Stryker вносит намеренно. Идея проста: если ваши тесты хороши, они должны обнаружить изменения и провалиться. Если они по-прежнему проходят, ваши тесты могут быть недостаточно эффективными.
В нашем примере мутант — это замена выражения price <= 0на price < 0, например, после чего выполняются модульные тесты.
Stryker поддерживает несколько типов мутаций:
| Тип | Описание |
|---|---|
| Эквивалент | Эквивалентный оператор используется для замены оператора его эквивалентом. Например, x < y преобразуется в x <= y. |
| Арифметика | Арифметический оператор используется для замены арифметического оператора эквивалентом. Например, x + y преобразуется в x - y. |
| Струна | Оператор строки используется для замены строки его эквивалентом. Например, "text" преобразуется в "". |
| Логический | Логический оператор используется для замены логического оператора эквивалентом. Например, x && y преобразуется в x \|\| y. |
Дополнительные типы мутаций см. в документации Stryker.NET: Mutations.
Интерпретация результатов тестирования мутаций
После выполнения Stryker.NET вы получите отчет, который классифицирует мутантов как убитых, выживших или превышен временной лимит. Вот как интерпретировать и действовать с этими результатами:
- Устраненные: это изменения, которые ваши тесты успешно обнаружили. Большое количество убитых мутантов означает, что набор тестов эффективно обнаруживает ошибки логики.
- Выжили: эти изменения не были пойманы вашими тестами. Просмотрите их, чтобы определить пробелы в охвате тестов или утверждениях, которые слишком слабы. Сосредоточьтесь на добавлении целевых модульных тестов, которые могли бы завершиться ошибкой, если бы мутант был настоящим.
- Время ожидания: эти изменения привели к зависаю или превышению допустимого времени. Это может произойти с бесконечными циклами или неоптимизированными путями. Изучите логику кода или увеличьте пороговое значение времени ожидания при необходимости.
Замечание
Не гоните за 100% оценки мутаций. Вместо этого сосредоточьтесь на высоких рисках или критически важных для бизнеса областях, где незамеченные ошибки будут наиболее дорогостоящими.
Добавление тестирования мутаций в рабочий процесс CI/CD
Вы можете легко интегрировать тестирование мутаций в рабочие процессы непрерывной интеграции и доставки. Например, Stryker.NET можно настроить для запуска в настройке Azure Pipelines или GitHub Actions, что позволяет применять пороговые значения качества в рамках процесса автоматического тестирования.
Настройка
Помимо настройки пороговых значений для конвейера, Stryker.NET предоставляет возможность наличия различных конфигураций для каждого из ваших потребностей в проекте. Поведение можно настроить с помощью файла stryker-config.json.
{
"stryker-config": {
"ignore-mutations": [
"string",
"logical"
],
"ignore-methods": [
"*Logs"
],
"mutate": [
"!**/Migrations/*",
"!**/*.Designer.cs"
]
}
}
-
игнорировать мутации: типы мутаций, которые следует исключить из тестирования, так как они шумны или не относятся к логике приложения. Они будут отображаться в отчетах как
Ignored. -
ignore-methods: это можно использовать для пропуска целых методов на основе их сигнатур. Они также отображаются в отчетах как
Ignored. В предыдущем примере все методы, заканчивающиеся на "Logs", игнорируются. - mutate: без этого параметра Stryker попытается изменить все файлы в проекте. С помощью этого можно игнорировать файлы или целые папки. В предыдущем примере все элементы в папке Migrations и все . Designer.cs файлы (которые обычно создаются автоматически) игнорируются.
Дополнительные сведения см. в разделе Stryker: Configuration.
Постепенное улучшение
Если после изменения кода модульные тесты проходят успешно, они недостаточно надежны, и мутант выжил. После тестирования мутаций пять мутантов выжили.
Давайте добавим тестовые данные для значений границ и снова запустите тестирование мутаций.
[Fact]
public void InvalidPrice_ShouldThrowException()
{
var calculator = new PriceCalculator();
// changed price from -10 to 0
Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(0, 10));
}
[Fact] // Added test for 0 and 100 discount
public void NoExceptionForZeroAnd100Discount()
{
var calculator = new PriceCalculator();
var exceptionWhen0 = Record.Exception(() => calculator.CalculatePrice(100, 0));
var exceptionWhen100 = Record.Exception(() => calculator.CalculatePrice(100, 100));
Assert.Null(exceptionWhen0);
Assert.Null(exceptionWhen100);
}
Как видно, после исправления эквивалентных мутантов у нас осталось только строковые мутации, которые можно легко "убить", проверив текст сообщения исключения.
[Fact]
public void InvalidDiscountPercent_ShouldThrowExceptionWithCorrectMessage()
{
var calculator = new PriceCalculator();
var ex1 = Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(100, -1));
Assert.Equal("Discount percent must be between 0 and 100.", ex1.Message);
var ex2 = Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(100, 101));
Assert.Equal("Discount percent must be between 0 and 100.", ex2.Message);
}
[Fact]
public void InvalidPrice_ShouldThrowExceptionWithCorrectMessage()
{
var calculator = new PriceCalculator();
var ex = Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(0, 10));
Assert.Equal("Price must be greater than zero.", ex.Message);
}
Тестирование мутаций помогает найти возможности для улучшения тестов, которые делают их более надежными. Это заставляет вас проверять не только «счастливый путь», но и сложные пограничные случаи, снижая вероятность ошибок в продакшене.