Test e asserzioni di unità

6

Supponiamo di avere un metodo che convalida un oggetto prima di salvarlo in un DB. Restituisce indietro KVP qualcosa del tipo:

public KeyValuePair<bool, List<FooErrorReason>> ValidateABar(Bar b)
{
    var reasons = new List<FooErrorReason>();
    if (string.IsNullOrEmpty(bar.Name)) 
    {
        reasons.Add(new FooErrorReason { PropName = "Name", Message = "The name was missing" };
    }
    if (string.IsNullOrEmpty(bar.OtherName)) 
    {
        reasons.Add(new FooErrorReason { PropName = "OtherName", Message = "The other name was missing" };
    }
    return new KeyValuePair<bool, List<FooErrorReason>(reasons.Count == 0, reasons);
}

Quindi ora voglio testarlo, per una barra mancante. Nome, l'errore viene restituito. La mia domanda è sul make up del test, riguardo a cosa a Assert .

public void ValidateABar_Fails_For_Missing_Name()
{
    //arrange
    var b = new Bar { OtherName = "Other" };

    //act
    var result = ValidateABar(b);

    //assert
    Assert.IsFalse(result.Key);
    Assert.AreEqual(1, result.Value.Count);
    Assert.IsTrue(result.Value.Any(x => x.PropName.Equals("Name")));
}

Ho il 3% di chiamate diAssert e ha un odore di codice a questo proposito, ma non riesco a convincermi in alcun modo.

Mi sto chiedendo 'Cosa vuoi testare?' e credo che dovrei provare che questo non è stato possibile, ma non sono riuscito in modo specifico per la proprietà del test.

È sufficiente usare solo

Assert.IsTrue(result.Value.Any(x => x.PropName.Equals("Name")));

O dovrei anche controllare che non ci siano altri errori che ho rilevato lungo la strada?

Ad esempio, qualcuno ha aggiunto un'altra regola di convalida senza che io ne sia a conoscenza, penserei che vorrei che i miei test mi parlassero di questo la prossima volta che li ho eseguiti. Ad esempio, supponiamo che qualcuno abbia cambiato il metodo come segue:

public KeyValuePair<bool, List<FooErrorReason>> ValidateABar(Bar b)
{
    var reasons = new List<FooErrorReason>();
    if (string.IsNullOrEmpty(bar.Name)) 
    {
        reasons.Add(new FooErrorReason { PropName = "Name", Message = "The name was missing" };
    }
    if (string.IsNullOrEmpty(bar.OtherName)) 
    {
        reasons.Add(new FooErrorReason { PropName = "OtherName", Message = "The other name was missing" };
    }

    //added this!
    if (string.IsNullOrEmpty(bar.ThirdName)) 
    {
        reasons.Add(new FooErrorReason { PropName = "ThirdName", Message = "The third name was missing" };
    }
    return new KeyValuePair<bool, List<FooErrorReason>(reasons.Count == 0, reasons);
}

Il test ora fallirebbe e vedrei che questa regola è stata aggiunta e modifico il mio test di conseguenza (e presumibilmente scriverò un altro test per coprire la nuova regola).

Sto prendendo anche la 'una sola regola Assert per test' letteralmente?

    
posta glosrob 03.01.2013 - 14:03
fonte

2 risposte

6

Stai prendendo una sola affermazione per test letteralmente, ma non troppo seriamente. È una buona regola da seguire.

Tuttavia, ci sono casi, come questo, in cui non è possibile affermare completamente la tua asserzione con letteralmente un metodo Assert. Quello che stai cercando di affermare è che la risposta sta mostrando un messaggio di errore. Assert.AreEqual non ti consente realmente di farlo, a meno che tu non scriva un comparatore di uguaglianza per il tuo oggetto risposta.

Un'altra opzione consiste nell'utilizzare la sintassi Assert.That e creare il proprio oggetto IResolveConstraint come comparatore. Con un piccolo sforzo, potresti avere qualcosa di semplice come

Assert.That(response, Fails.With.Message("Blah"));

Ma davvero, ne vale la pena? Quello che hai è giusto e sta affermando che la tua risposta è corretta. Questa è una affermazione.

In altri casi, in realtà non avrai alcuna chiamata ai metodi Assert, perché la cosa che stai asserendo è che un metodo deriso è stato chiamato correttamente. Questa è ancora un'affermazione, anche se non è un Assert, di per sé.

Ad esempio, supponiamo tu abbia un metodo Save, che invia dati a un repository (non importa se è SQL, file system, oggetto mock, ecc.) e quindi non restituisce nulla.

public void Save()
{
     // How do I test this method with an Assert call???
     FooRepository.Save(this.Name, this.OtherName.Trim());
}

Quello che non si vuole fare è impostare un'aspettativa su un metodo di simulazione e quindi asserire la risposta dal metodo sotto test. Sarebbero due asserzioni in un test (anche se solo una chiamata Assert) e, se una cosa va storta, sarà più difficile capire esattamente cosa.

In un caso del genere, scrivi due test:

  • uno per verificare che il metodo sia chiamato correttamente
  • uno per affermare il contenuto di qualsiasi risposta

In quest'ultimo, semplicemente stub il metodo, in modo che passi indipendentemente dal fatto che il metodo sia chiamato, a patto che la risposta sia corretta.

TL; DR: Non confondere l'affermazione di una cosa con una chiamata a un metodo Assert. Non sono la stessa cosa.

    
risposta data 03.01.2013 - 14:41
fonte
5

onestamente la regola "un assert per test" è sopravvalutata, una regola migliore sarebbe "on act per test" e avere un odore quando hai più di un asserzione che significa che l'azione fa troppo

hai un metodo con un output composto, quindi ha senso testare tutte le parti dell'output. Fare questo in un singolo test significa che quando è richiesto un nuovo campo devi solo cambiare 1 test invece di 3

Credo che sia possibile aggiungere una stringa all'assert da visualizzare quando fallisce, o almeno si dovrebbe vedere la linea dove fallisce nello stacktrace. in questo modo puoi restringere ciò che è andato storto

inoltre il primo relfex che dovresti avere dovrebbe essere quello di aggiungere un breakpoint prima del primo asser ed eseguire nuovamente il test e vedere quale output hai ottenuto

    
risposta data 03.01.2013 - 14:22
fonte

Leggi altre domande sui tag