Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Badania mutacji to sposób oceny jakości naszych testów jednostkowych. W przypadku testowania mutacji narzędzie Stryker.NET automatycznie wykonuje mutacje w kodzie, uruchamia testy i generuje szczegółowy raport z wynikami.
Przykładowy scenariusz testowy
Rozważmy przykładową klasę PriceCalculator.cs z metodą Calculate
, która oblicza cenę, biorąc pod uwagę rabat.
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);
}
}
Poprzednia metoda jest objęta następującymi testami jednostkowymi.
[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));
}
Powyższy kod wyróżnia dwa projekty: jeden dla usługi, która działa jako PriceCalculator
, a druga to projekt testowy.
Instalowanie narzędzia globalnego
Najpierw zainstaluj Stryker.NET. W tym celu należy wykonać polecenie:
dotnet tool install -g dotnet-stryker
Aby uruchomić stryker
, wywołaj go z wiersza polecenia w katalogu, w którym znajduje się projekt testu jednostkowego:
dotnet stryker
Po uruchomieniu testów w konsoli zostanie wyświetlony raport.
Stryker.NET zapisuje szczegółowy raport HTML w katalogu StrykerOutput.
Teraz zastanów się, czym są mutanty i co "przeżył" i "zabite" oznaczają. Mutant to niewielka zmiana w kodzie, którą stryker wykonuje celowo. Pomysł jest prosty: jeśli testy są dobre, powinny wykryć zmianę i zakończyć się niepowodzeniem. Jeśli nadal przechodzą, twoje testy mogą nie być wystarczająco mocne.
W naszym przykładzie mutant zastąpi wyrażenie price <= 0
, na przykład price < 0
, po którym są uruchamiane testy jednostkowe.
Stryker obsługuje kilka rodzajów mutacji:
Typ | Opis |
---|---|
Równoważny | Równoważny operator jest używany do zastępowania operatora jego odpowiednikiem. Na przykład x < y staje się x <= y . |
Arytmetyka | Operator arytmetyczny służy do zastępowania operatora arytmetycznego jego odpowiednikiem. Na przykład x + y staje się x - y . |
Sznurek | Operator ciągu służy do zastępowania ciągu jego odpowiednikiem. Na przykład "text" staje się "" . |
Logiczny | Operator logiczny jest używany do zastępowania operatora logicznego jego odpowiednikiem. Na przykład x && y staje się x \|\| y . |
Aby uzyskać dodatkowe typy mutacji, sprawdź dokumentację Stryker.NET: Mutacje.
Ulepszanie przyrostowe
Jeśli po zmianie kodu testy jednostkowe zostaną pomyślnie zakończone, nie są one wystarczająco niezawodne, a mutant przetrwał. Po przetestowaniu mutacji, pięć mutantów przetrwa.
Dodajmy dane testowe dla wartości granic i ponownie uruchomimy testy mutacji.
[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);
}
Jak widać, po poprawieniu równoważnych mutacji, pozostały nam tylko mutacje ciągów znaków, które możemy łatwo "zabić", poprzez sprawdzenie tekstu komunikatu wyjątku.
[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);
}
Testy mutacji pomagają znaleźć możliwości poprawy testów, które sprawiają, że są bardziej wiarygodne. Wymusza to sprawdzenie nie tylko "szczęśliwej ścieżki", ale także złożonych przypadków brzegowych, co zmniejsza prawdopodobieństwo występowania błędów w produkcji.