Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Mutationstests sind eine Möglichkeit, die Qualität unserer Komponententests zu bewerten. Bei Mutationstests führt das tool Stryker.NET automatisch Mutationen in Ihrem Code durch, führt Tests aus und generiert einen detaillierten Bericht mit den Ergebnissen.
Beispieltestszenario
Betrachten Sie ein Beispiel PriceCalculator.cs Klasse mit einer Calculate Methode, die den Preis unter Berücksichtigung des Rabatts berechnet.
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);
}
}
Die vorstehende Methode wird von den folgenden Komponententests abgedeckt:
[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));
}
Im vorherigen Code werden zwei Projekte hervorgehoben, eines für den Dienst, der als ein PriceCalculator und das andere das Testprojekt fungiert.
Installieren des globalen Tools
Installieren Sie zunächst Stryker.NET. Dazu müssen Sie den Befehl ausführen:
dotnet tool install -g dotnet-stryker
Führen Sie stryker über die Befehlszeile im Verzeichnis aus, in dem sich das Unit-Test-Projekt befindet.
dotnet stryker
Nachdem die Tests ausgeführt wurden, wird ein Bericht in der Konsole angezeigt.
Stryker.NET speichert einen detaillierten HTML-Bericht im StrykerOutput-Verzeichnis.
Überlegen Sie nun, was Mutanten sind und was "überlebt" und "getötet" bedeutet. Ein Mutant ist eine kleine Änderung in Ihrem Code, die Stryker absichtlich vornimmt. Das Konzept ist einfach: Wenn Ihre Tests gut sind, sollten sie die Änderung erkennen und zu einem Fehler führen. Wenn sie immer noch bestehen, sind Ihre Tests möglicherweise nicht stark genug.
In unserem Beispiel wird ein Mutant als Ersatz des Ausdrucks price <= 0 verwendet, z. B. durch price < 0, nach dem Komponententests ausgeführt werden.
Stryker unterstützt verschiedene Arten von Mutationen:
| Typ | BESCHREIBUNG |
|---|---|
| Äquivalent | Der Äquivalent-Operator wird verwendet, um einen Operator durch seinen Äquivalent zu ersetzen. Beispielsweise wird x < y zu x <= y. |
| Arithmetik | Der arithmetische Operator wird verwendet, um einen arithmetischen Operator durch seine Entsprechung zu ersetzen. Beispielsweise wird x + y zu x - y. |
| Schnur | Der Zeichenfolgenoperator wird verwendet, um eine Zeichenfolge durch ihre Entsprechung zu ersetzen. Beispielsweise wird "text" zu "". |
| Logisch | Der logische Operator wird verwendet, um einen logischen Operator durch seine Entsprechung zu ersetzen. Beispielsweise wird x && y zu x \|\| y. |
Weitere Mutationstypen finden Sie in der Dokumentation Stryker.NET: Mutationen .
Interpretieren von Mutationstestergebnissen
Nachdem Sie Stryker.NET ausgeführt haben, erhalten Sie einen Bericht, der Mutanten als getötet, überlebt oder timeout kategorisiert. Hier erfahren Sie, wie Sie diese Ergebnisse interpretieren und bearbeiten:
- Getötet: Dies sind Änderungen, die Ihre Tests erfolgreich abgefangen haben. Eine hohe Anzahl getöteter Mutanten weist darauf hin, dass Ihre Testsuite logikfehler effektiv erkennt.
- Überlebt: Diese Änderungen wurden von Ihren Tests nicht erfasst. Überprüfen Sie sie, um Lücken bei der Testabdeckung oder Assertionen zu identifizieren, die zu schwach sind. Konzentrieren Sie sich auf das Hinzufügen von gezielten Komponententests, die fehlschlagen würden, wenn es sich um einen echten Mutanten handeln würde.
- Timeout: Diese Mutationen haben dazu geführt, dass Ihr Code hängen oder die zulässige Zeit überschreitet. Dies kann mit unendlichen Schleifen oder nicht optimierten Pfaden geschehen. Untersuchen Sie die Codelogik, oder erhöhen Sie bei Bedarf den Timeoutschwellenwert.
Hinweis
Verfolgen Sie keine 100% Mutationsrate. Konzentrieren Sie sich stattdessen auf bereiche mit hohem Risiko oder geschäftskritischen Bereichen, in denen nicht erkannte Fehler am kostspieligsten wären.
Hinzufügen von Mutationstests zum CI/CD-Workflow
Sie können Mutationstests nahtlos in Ihre kontinuierlichen Integrations- und Übermittlungsworkflows integrieren. Beispielsweise können Stryker.NET so konfiguriert werden, dass sie in Ihrer Azure-Pipelines- oder GitHub-Aktionen-Einrichtung ausgeführt werden, sodass Sie Qualitätsschwellenwerte als Teil Ihres automatisierten Testprozesses erzwingen können.
Anpassung
Neben dem Festlegen von Schwellenwerten für Ihre Pipeline bietet Stryker.NET die Möglichkeit, für jedes Projekt unterschiedliche Konfigurationen zu haben. Sie können das Verhalten mithilfe der stryker-config.json Datei anpassen.
{
"stryker-config": {
"ignore-mutations": [
"string",
"logical"
],
"ignore-methods": [
"*Logs"
],
"mutate": [
"!**/Migrations/*",
"!**/*.Designer.cs"
]
}
}
-
Ignore-Mutationen: Mutationstypen, die von Tests ausgeschlossen werden sollen, da sie für Ihre Anwendungslogik laut oder nicht relevant sind. Sie werden in Ihren Berichten als
Ignoredangezeigt. -
ignore-methods: Sie können diese Option nutzen, um gesamte Methoden basierend auf ihren Signaturen zu überspringen. Diese erscheinen auch in Ihren Berichten als
Ignored. Im vorherigen Beispiel werden alle Methoden, die auf "Logs" enden, ignoriert. - mutieren: Ohne diese Option versucht Stryker, alle Dateien in Ihrem Projekt zu mutieren. Damit können Sie Dateien oder ganze Ordner ignorieren. Im vorherigen Beispiel werden alle Elemente innerhalb eines Migrations-Ordners und alle .Designer.cs-Dateien (die normalerweise automatisch generiert werden) ignoriert.
Weitere Informationen finden Sie unter Stryker: Configuration.
Inkrementelle Verbesserung
Wenn nach dem Ändern Ihres Codes die Unit-Tests erfolgreich sind, dann sind sie nicht ausreichend robust und der Mutant überlebt. Nach Mutationstests überleben fünf Mutanten.
Fügen wir Testdaten für Grenzwerte hinzu und führen mutationstests erneut aus.
[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);
}
Wie Sie sehen können, haben wir nach der Korrektur der entsprechenden Mutanten nur Zeichenfolgenmutationen übrig, die wir einfach "töten" können, indem wir den Text der Ausnahmemeldung überprüfen.
[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);
}
Mutationstests helfen, Möglichkeiten zu finden, um Tests zu verbessern, die sie zuverlässiger machen. Es zwingt Sie, nicht nur den "glücklichen Weg" zu überprüfen, sondern auch komplexe Grenzfälle, wodurch die Wahrscheinlichkeit von Fehlern in der Produktion reduziert wird.