Mutagenní testování

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.

Sestava konzoly Stryker

Stryker.NET uloží podrobnou sestavu HTML do adresáře StrykerOutput.

První zpráva Stryker

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);
}

Druhá zpráva Stryker

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);
}

Konečná zpráva Strykeru

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í.