Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Il test delle mutazioni è un modo per valutare la qualità degli unit test. Per il test delle mutazioni, lo strumento Stryker.NET esegue automaticamente le mutazioni nel codice, esegue i test e genera un report dettagliato con i risultati.
Scenario di test di esempio
Si consideri un esempio PriceCalculator.cs classe con un Calculate metodo che calcola il prezzo, tenendo conto dello sconto.
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);
}
}
Il metodo precedente è coperto dai test unitari seguenti:
[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));
}
Il codice precedente evidenzia due progetti, uno per il servizio che funge da e PriceCalculator l'altro è il progetto di test.
Installare lo strumento globale
Prima di tutto, installare Stryker.NET. A tale scopo, è necessario eseguire il comando :
dotnet tool install -g dotnet-stryker
Per eseguire stryker, richiamarlo dalla riga di comando nella directory in cui si trova il progetto di unit test:
dotnet stryker
Dopo l'esecuzione dei test, nella console viene visualizzato un report.
Stryker.NET salva un report HTML dettagliato nella directory StrykerOutput.
Ora, considera cosa sono i mutanti e cosa significa "sopravvissuto" e "ucciso". Un mutante è una piccola modifica nel codice che Stryker apporta a scopo. L'idea è semplice: se i test sono validi, dovrebbero rilevare la modifica e fallire. Se vengono ancora superati, i test potrebbero non essere abbastanza forti.
In questo esempio, un mutante sarà la sostituzione dell'espressione price <= 0, ad esempio con price < 0, dopo il quale vengono eseguiti unit test.
Stryker supporta diversi tipi di mutazioni:
| TIPO | Descrizione |
|---|---|
| Equivalente | L'operatore equivalente viene usato per sostituire un operatore con il relativo equivalente. Ad esempio, x < y diventa x <= y. |
| Aritmetica | L'operatore aritmetico viene usato per sostituire un operatore aritmetico con il relativo equivalente. Ad esempio, x + y diventa x - y. |
| Stringa | L'operatore string viene usato per sostituire una stringa con il relativo equivalente. Ad esempio, "text" diventa "". |
| Logico | L'operatore logico viene usato per sostituire un operatore logico con il relativo equivalente. Ad esempio, x && y diventa x \|\| y. |
Per altri tipi di mutazione, vedere la documentazione Stryker.NET: Mutazioni .
Interpretare i risultati dei test delle mutazioni
Dopo aver eseguito Stryker.NET, si riceverà un report che classifica i mutanti come uccisi, sopravvissuti o timeout. Ecco come interpretare e agire su questi risultati:
- Eliminate: Si tratta di modifiche che i tuoi test hanno scoperto con successo. Un numero elevato di mutanti uccisi indica che il gruppo di test rileva in modo efficace gli errori logici.
- Sopravvissuto: queste modifiche non sono state rilevate dai test. Esaminarli per identificare le lacune nella copertura dei test o nelle asserzioni troppo deboli. Concentrarsi sull'aggiunta di unit test mirati che fallirebbero se il mutante fosse reale.
- Timeout: queste mutazioni hanno causato al codice il blocco o il superamento del tempo consentito. Ciò può verificarsi con cicli infiniti o percorsi non ottimizzati. Esaminare la logica del codice o aumentare la soglia di timeout, se necessario.
Annotazioni
Non cercare di ottenere un punteggio di mutazione del 100%. Invece, concentrarsi su aree ad alto rischio o business critical in cui i bug non rilevati sarebbero più costosi.
Aggiunta di test di mutazione al flusso di lavoro CI/CD
È possibile integrare facilmente i test delle mutazioni nei flussi di lavoro di integrazione e recapito continui. Ad esempio, Stryker.NET può essere configurato per l'esecuzione all'interno della configurazione di Azure Pipelines o GitHub Actions, consentendo di applicare soglie di qualità come parte del processo di test automatizzato.
Personalizzazione
Oltre a impostare soglie per la pipeline, Stryker.NET offre la possibilità di avere configurazioni diverse per ognuna delle esigenze del progetto. È possibile personalizzare il comportamento usando il file stryker-config.json .
{
"stryker-config": {
"ignore-mutations": [
"string",
"logical"
],
"ignore-methods": [
"*Logs"
],
"mutate": [
"!**/Migrations/*",
"!**/*.Designer.cs"
]
}
}
-
ignore-mutazioni: i tipi di mutazione da escludere dai test perché sono rumorosi o non rilevanti per la logica dell'applicazione. Verranno visualizzati nei report come
Ignored. -
ignore-methods: Puoi usarlo per saltare interi metodi in base alle loro signature. Questi elementi vengono visualizzati anche nei report come
Ignored. Nell'esempio precedente tutti i metodi che terminano in "Logs" vengono ignorati. - mutate: senza questa opzione, Stryker tenterà di modificare tutti i file nel progetto. A questo scopo, è possibile ignorare file o intere cartelle. Nell'esempio precedente tutti gli elementi all'interno di una cartella Migrations e di tutti . Designer.cs file (che in genere vengono generati automaticamente) vengono ignorati.
Per altre informazioni, vedere Stryker: Configuration.
Miglioramento incrementale
Se, dopo aver modificato il codice, i test unitari vengono superati con successo, allora non sono sufficientemente robusti e il mutante è sopravvissuto. Dopo il test delle mutazioni, cinque mutanti sopravvivono.
Aggiungere i dati di test per i valori limite ed eseguire di nuovo il test delle mutazioni.
[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);
}
Come si può notare, dopo aver corretto i mutanti equivalenti, abbiamo solo mutazioni di stringa lasciate, che possiamo facilmente "uccidere" controllando il testo del messaggio di eccezione.
[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);
}
I test di mutazione consentono di trovare opportunità per migliorare i test che li rendono più affidabili. Ti costringe a verificare non solo il "caso di successo", ma anche casi limite complessi, riducendo la probabilità di bug nell'ambiente di produzione.