Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Mutační testování je způsobém, jak vyhodnotit kvalitu našich jednotkových testů. V případě testování s mutací nástroj Stryker.NET automaticky provádí v kódu změny, spouští testy a generuje podrobnou sestavu s výsledky.
Ukázkový testovací scénář
Vezměte v úvahu ukázkovou PriceCalculator.cs třídu s metodou Calculate , která vypočítá cenu s ohledem na slevu.
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);
}
}
Předchozí metoda je pokryta následujícími jednotkovými testy.
[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));
}
Předchozí kód zvýrazní dva projekty, jeden pro službu, která funguje jako PriceCalculator a druhý je testovací projekt.
Instalace globálního nástroje
Nejprve nainstalujte Stryker.NET. K tomu je potřeba spustit příkaz:
dotnet tool install -g dotnet-stryker
Chcete-li stryker spustit, spusťte jej z příkazového řádku v adresáři, kde se nachází jednotkový testový projekt.
dotnet stryker
Po spuštění testů se v konzoli zobrazí zpráva.
Stryker.NET uloží podrobnou sestavu HTML do adresáře StrykerOutput.
Teď zvažte, co jsou mutanti a co znamená "přežít" a "zabito". Mutant je malá změna ve vašem kódu, kterou Stryker dělá úmyslně. Myšlenka je jednoduchá: pokud jsou vaše testy dobré, měly by zachytit změnu a selhat. Pokud i přesto projdou, vaše testy možná nejsou dostatečně silné.
V našem příkladu bude mutant nahrazením výrazu price <= 0 například výrazu price < 0, po kterém se spustí jednotkové testy.
Stryker podporuje několik typů mutací:
| Typ | Popis |
|---|---|
| Ekvivalent | Ekvivalentní operátor slouží k nahrazení operátoru jeho ekvivalentem. Například x < y se stane x <= y. |
| Aritmetika | Aritmetický operátor se používá k nahrazení aritmetického operátoru jeho ekvivalentem. Například x + y se stane x - y. |
| Řetězec | Řetězcový operátor slouží k nahrazení řetězce jeho ekvivalentem. Například "text" se stane "". |
| Logický | Logický operátor slouží k nahrazení logického operátoru jeho ekvivalentem. Například x && y se stane x \|\| y. |
Další typy mutací najdete v Stryker.NET: dokumentaci k mutacím .
Interpretace výsledků mutačního testování
Po spuštění Stryker.NET obdržíte sestavu, která kategorizuje mutanty jako zabité, přeživší nebo vypršel časový limit. Tady je postup, jak interpretovat a reagovat na tyto výsledky:
- Zabito: Jedná se o změny, které vaše testy úspěšně zachytily. Velký počet zabitých mutantů značí, že vaše testovací sada efektivně detekuje chyby logiky.
- Přežilo: Tyto změny nebyly zachyceny vašimi testy. Pečlivě je zkontrolujte a identifikujte mezery v pokrytí testů nebo slabá tvrzení, které jsou příliš slabé. Zaměřte se na přidání cílených jednotkových testů, které by selhaly, kdyby mutant byl skutečný.
- Vypršení časového limitu: Tyto mutace způsobily, že váš kód se zasekne nebo překročily povolený čas. K tomu může dojít u nekonečných smyček nebo neoptimalizovaných cest. Prozkoumejte logiku kódu nebo v případě potřeby zvyšte prahovou hodnotu časového limitu.
Poznámka:
Nenahánějte 100% skóre mutací. Místo toho se zaměřte na vysoce rizikové nebo důležité obchodní oblasti, kde by nedetekované chyby byly nákladnější.
Přidání mutačního testování do pracovního postupu CI/CD
Testování mutací můžete bezproblémově integrovat do pracovních postupů kontinuální integrace a doručování. Například Stryker.NET je možné nakonfigurovat tak, aby běžela v rámci nastavení Služby Azure Pipelines nebo GitHub Actions, což vám umožní vynutit prahové hodnoty kvality v rámci procesu automatizovaného testování.
Přizpůsobení
Kromě nastavení prahových hodnot pro váš kanál nabízí Stryker.NET možnost mít různé konfigurace pro každou z vašich potřeb projektu. Chování můžete přizpůsobit pomocí souboru stryker-config.json.
{
"stryker-config": {
"ignore-mutations": [
"string",
"logical"
],
"ignore-methods": [
"*Logs"
],
"mutate": [
"!**/Migrations/*",
"!**/*.Designer.cs"
]
}
}
-
ignore-mutations: Typy mutací, které se mají vyloučit z testování, protože způsobují šum nebo nejsou relevantní pro logiku vaší aplikace. Zobrazí se ve vašich sestavách jako
Ignored. -
ignore-methods: Pomocí této možnosti můžete přeskočit celé metody na základě jejich signatur. V vašich sestavách se také zobrazují jako
Ignored. V předchozím příkladu jsou všechny metody končící na "Logs" ignorovány. - mutate: Pokud tuto možnost nepoužijete, Stryker se pokusí modifikovat všechny soubory ve vašem projektu. Díky tomu můžete ignorovat soubory nebo celé složky. V předchozím příkladu všechno uvnitř složky Migrations a vše . Designer.cs soubory (které jsou obvykle automaticky generované) jsou ignorovány.
Další informace naleznete v tématu Stryker: Konfigurace.
Přírůstkové vylepšování
Pokud po změně kódu úspěšně projdou testy jednotek, nejsou dostatečně robustní a mutant přežil. Po testování mutantů přežijí pět mutantů.
Pojďme přidat testovací data pro hraniční hodnoty a znovu spustit mutační testování.
[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 vidíte, po opravě ekvivalentních mutantů nám zůstaly pouze řetězcové mutace, které můžeme snadno odstranit kontrolou textu výjimkové zprávy.
[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);
}
Testování pomocí mutací pomáhá najít příležitosti ke zlepšení testů, díky kterým jsou spolehlivější. Nutí vás kontrolovat nejen "ideální scénář", ale také složité hraniční případy, což snižuje pravděpodobnost chyb v produkčním prostředí.