Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Тестирование мутаций — это способ оценить качество наших модульных тестов. Для тестирования мутаций средство 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.
Постепенное улучшение
Если после изменения кода модульные тесты проходят успешно, они недостаточно надежны, и мутант выжил. После тестирования мутаций пять мутантов выжили.
Давайте добавим тестовые данные для значений границ и снова запустите тестирование мутаций.
[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);
}
Тестирование мутаций помогает найти возможности для улучшения тестов, которые делают их более надежными. Это заставляет вас проверять не только «счастливый путь», но и сложные пограничные случаи, снижая вероятность ошибок в продакшене.