È corretto avere più asserzioni in un singolo test unitario?


397

Nel commento a questo fantastico post , Roy Osherove ha menzionato il progetto OAPT progettato per eseguire ciascuna asserzione in un singolo test.

Quanto segue è scritto nella home page del progetto:

I test unitari corretti dovrebbero fallire esattamente per una ragione, ecco perché dovresti usare un assert per unit test.

E, inoltre, Roy ha scritto nei commenti:

La mia linea guida è di solito che testi un CONCEPT logico per test. puoi avere più asserzioni sullo stesso oggetto . di solito saranno lo stesso concetto in fase di test.

Penso che ci siano alcuni casi in cui sono necessarie più asserzioni (ad esempio , Asserzione di guardia ), ma in generale cerco di evitarlo. Qual è la tua opinione? Fornisci un esempio del mondo reale in cui sono davvero necessari più asserzioni .


2
Come fai a deridere senza avere più asserzioni? Ogni aspettativa al gioco è un'affermazione in sé, incluso qualsiasi ordine di chiamate imposto.
Christopher Creutzig,

15
Ho visto la filosofia del singolo assert per metodo abusata in passato. Un vecchio collega ha usato un meccanismo di eredità funky per renderlo possibile. Ha portato a molte sottoclassi (una per ramo) e molti test che hanno eseguito lo stesso processo di impostazione / smontaggio solo per verificare i diversi risultati. Era lento, difficile da leggere e un grave problema di manutenzione. Non l'ho mai convinto a tornare a un approccio più classico. Il libro di Gerard Meszaros parla di questo argomento in dettaglio.
Travis Parks,

3
Penso che come regola generale si dovrebbe cercare di ridurre al minimo il numero di asserzioni per test. Tuttavia, fintanto che il test restringe sufficientemente il problema in un punto specifico del codice, allora è un test utile.
ConditionRacer

2
Ho visto casi in cui sono stati utilizzati più RowTestassert invece di (MbUnit) / TestCase(NUnit) per testare una varietà di comportamenti limite. Usa gli strumenti adeguati per il lavoro! (Sfortunatamente, MSTest non sembra ancora avere una capacità di test in fila.)
GalacticCowboy

@GalacticCowboy È possibile ottenere funzionalità simili RowTeste TestCaseutilizzare origini dati di test . Sto usando un semplice file CSV con grande successo.
julealgon,

Risposte:


236

Non penso che sia necessariamente una cosa negativa , ma penso che dovremmo sforzarci di avere solo affermazioni singole nei nostri test. Ciò significa che scrivi molti più test e i nostri test finirebbero per testare solo una cosa alla volta.

Detto questo, direi che forse metà dei miei test in realtà ha una sola affermazione. Penso che diventi un odore di codice (test?) Solo quando hai circa cinque o più affermazioni nel tuo test.

Come risolvi più asserzioni?


9
Come questa risposta - vale a dire è OK, ma non è, in generale, buono (-:
Murph

5
Ehh? Perché dovresti farlo? l'esecuzione del metodo è esattamente la stessa?
jgauffin,

198
Un singolo assert per unità di test è un ottimo modo per testare la capacità del lettore di scorrere su e giù.
Tom,

3
Qualche ragionamento dietro questo? Così com'è, questa risposta attuale indica semplicemente cosa dovrebbe essere, ma non perché.
Steven Jeuris,

37
In forte disaccordo. La risposta non elenca alcun vantaggio di avere una singola asserzione, e l'ovvio svantaggio è che è necessario copiare e incollare i test solo perché una risposta su Internet lo dice. Se è necessario testare più campi di un risultato o più risultati di una singola operazione, è assolutamente necessario affermarli tutti in asserzioni indipendenti, in quanto forniscono informazioni molto più utili rispetto al test in una grande asserzione BLOB. In un buon test, si verifica una singola operazione, non un singolo risultato di un'operazione.
Peter,

296

I test dovrebbero fallire per un solo motivo, ma ciò non significa sempre che ci dovrebbe essere solo Assertun'istruzione. IMHO è più importante attenersi al modello " Disponi, agisci, asserisci ".

La chiave è che hai una sola azione e quindi controlli i risultati di quell'azione usando gli assert. Ma è "Disponi, agisci, asserisci, fine del test ". Se sei tentato di continuare il test eseguendo un'altra azione e più affermazioni in seguito, esegui invece un test separato.

Sono felice di vedere più dichiarazioni di asserzione che fanno parte del test della stessa azione. per esempio

[Test]
public void ValueIsInRange()
{
  int value = GetValueToTest();

  Assert.That(value, Is.GreaterThan(10), "value is too small");
  Assert.That(value, Is.LessThan(100), "value is too large");
} 

o

[Test]
public void ListContainsOneValue()
{
  var list = GetListOf(1);

  Assert.That(list, Is.Not.Null, "List is null");
  Assert.That(list.Count, Is.EqualTo(1), "Should have one item in list");
  Assert.That(list[0], Is.Not.Null, "Item is null");
} 

Si potrebbe combinare questi in uno affermare, ma questa è una cosa diversa da insistendo sul fatto che si dovrebbe o mosto . Non c'è alcun miglioramento nel combinarli.

ad esempio, il primo potrebbe essere

Assert.IsTrue((10 < value) && (value < 100), "Value out of range"); 

Ma questo non è meglio: il messaggio di errore è meno specifico e non ha altri vantaggi. Sono sicuro che puoi pensare ad altri esempi in cui la combinazione di due o tre (o più) affermazioni in una grande condizione booleana rende più difficile la lettura, più difficile da modificare e più difficile da capire perché non è riuscita. Perché farlo solo per il gusto di una regola?

NB : Il codice che sto scrivendo qui è C # con NUnit, ma i principi saranno validi con altre lingue e framework. Anche la sintassi può essere molto simile.


32
La chiave è che hai una sola azione e quindi controlli i risultati di quell'azione usando gli assert.
Amitābha,

1
Penso che sia anche interessante avere più di un Assert, se Arrange è costoso.
Rekshino,

1
@Rekshino, se organizza è dispendioso in termini di tempo, possiamo condividere il codice di organizzazione, ad esempio inserendo il codice di organizzazione nella routine di inizializzazione del test.
Shaun Luttin,

2
Quindi, se paragone alla risposta di Jaco, l '"unica affermazione" diventa "solo un gruppo di affermazioni" che ha più senso per me.
Walfrat,

2
Questa è una buona risposta, ma non sono d'accordo sul fatto che una singola affermazione non sia migliore. L'esempio di asserzione negativa non è migliore, ma ciò non significa che una singola asserzione non sarebbe migliore se eseguita correttamente. Molte librerie consentono asserzioni / abbinamenti personalizzati in modo che qualcosa possa essere creato se non già presente. Ad esempio, Assert.IsBetween(10, 100, value)secondo me le stampe Expected 8 to be between 10 and 100 sono meglio di due affermazioni separate. Si può certamente sostenere che non è necessario, ma di solito vale la pena considerare se è facile ridurre a una singola affermazione prima di farne un intero set.
Thor84no,

85

Non ho mai pensato che più di un'affermazione fosse una brutta cosa.

Lo faccio tutto il tempo:

public void ToPredicateTest()
{
    ResultField rf = new ResultField(ResultFieldType.Measurement, "name", 100);
    Predicate<ResultField> p = (new ConditionBuilder()).LessThanConst(400)
                                                       .Or()
                                                       .OpenParenthesis()
                                                       .GreaterThanConst(500)
                                                       .And()
                                                       .LessThanConst(1000)
                                                       .And().Not()
                                                       .EqualsConst(666)
                                                       .CloseParenthesis()
                                                       .ToPredicate();
    Assert.IsTrue(p(ResultField.FillResult(rf, 399)));
    Assert.IsTrue(p(ResultField.FillResult(rf, 567)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 400)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 666)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 1001)));

    Predicate<ResultField> p2 = (new ConditionBuilder()).EqualsConst(true).ToPredicate();

    Assert.IsTrue(p2(new ResultField(ResultFieldType.Confirmation, "Is True", true)));
    Assert.IsFalse(p2(new ResultField(ResultFieldType.Confirmation, "Is False", false)));
}

Qui utilizzo più asserzioni per assicurarmi che condizioni complesse possano essere trasformate nel predicato previsto.

Sto testando solo un'unità (il ToPredicatemetodo), ma sto coprendo tutto ciò a cui riesco a pensare nel test.


46
Le affermazioni multiple sono errate a causa del rilevamento degli errori. Se hai fallito il tuo primo Assert.IsTrue, altri assert non verranno eseguiti e non otterrai alcuna informazione da essi. D'altra parte, se avessi 5 prove invece di 1 con 5 affermazioni, potresti ottenere qualcosa di utile
Sly,

6
Lo consideri ancora negativo se tutti gli asserti testano lo stesso tipo di funzionalità? Come sopra, l'esempio verifica i condizionali e se uno di questi fallisce, dovresti risolverlo. Ti importa che potresti perdere le ultime 2 affermazioni se una precedente fallisce?
rabbia

106
Risolvo i miei problemi uno alla volta. Quindi il fatto che il test possa fallire più di una volta non mi dà fastidio. Se li dividessi avrei gli stessi errori, ma tutti in una volta. Trovo più facile sistemare le cose un passo alla volta. Devo ammettere che, in questo caso, le ultime due affermazioni potrebbero essere probabilmente rifattorizzate nel proprio test.
Matt Ellen,

19
Il tuo caso è molto rappresentativo, ecco perché NUnit ha l'attributo aggiuntivo TestCase - nunit.org/?p=testCase&r=2.5
Restuta

10
Il framework di test di Google C ++ ha ASSERT () ed EXPECT (). ASSERT () si interrompe in caso di errore mentre EXPECT () continua. È molto utile quando si desidera convalidare più di una cosa nel test.
Ratkok,

21

Quando utilizzo il test unitario per convalidare il comportamento di alto livello, inserisco assolutamente più asserzioni in un singolo test. Ecco un test che sto effettivamente utilizzando per un codice di notifica di emergenza. Il codice che viene eseguito prima del test mette il sistema in uno stato in cui se viene eseguito il processore principale, viene inviato un allarme.

@Test
public void testAlarmSent() {
    assertAllUnitsAvailable();
    assertNewAlarmMessages(0);

    pulseMainProcessor();

    assertAllUnitsAlerting();
    assertAllNotificationsSent();
    assertAllNotificationsUnclosed();
    assertNewAlarmMessages(1);
}

Rappresenta le condizioni che devono esistere in ogni fase del processo per poter essere sicuro che il codice si comporti come mi aspetto. Se una singola asserzione fallisce, non mi interessa che le restanti non vengano nemmeno messe in fuga; poiché lo stato del sistema non è più valido, quelle successive affermazioni non mi direbbero nulla di prezioso. * Se assertAllUnitsAlerting()fallito, allora non saprei cosa fare del assertAllNotificationSent()successo O del fallimento fino a quando non avrò determinato cosa stava causando l'errore precedente e corretto.

(* - Ok, potrebbero essere potenzialmente utili nel debug del problema. Ma le informazioni più importanti, che il test ha fallito, sono già state ricevute.)


Quando lo fai dovresti usare meglio i framework di test con test dipendenti, è meglio (ad esempio testng supporta questa funzione)
Kemoda,

8
Scrivo anche test di questo tipo in modo da poter essere sicuro di ciò che il codice sta facendo e dichiarare i cambiamenti, non penso che si tratti di un test unitario, ma di un test di integrazione.
mdma,

Quali sono le tue opinioni sul refactoring in un assertAlarmStatus (int numberOfAlarmMessages) ;?
Borjab

1
Le tue affermazioni renderebbero nomi di test molto carini.
Bjorn,

È meglio consentire l'esecuzione del test, anche con input non validi. Ti dà più informazioni in quel modo (e specialmente se passa ancora quando non te l'aspettavi).
CurtainDog

8

Un altro motivo per cui penso che più asserzioni in un metodo non sia negativo è descritto nel seguente codice:

class Service {
    Result process();
}

class Result {
    Inner inner;
}

class Inner {
    int number;
}

Nel mio test voglio semplicemente provare che service.process()restituisca il numero corretto nelle Inneristanze di classe.

Invece di testare ...

@Test
public void test() {
    Result res = service.process();
    if ( res != null && res.getInner() != null ) Assert.assertEquals( ..., res.getInner() );
}

sto facendo

@Test
public void test() {
    Result res = service.process();
    Assert.notNull(res);
    Assert.notNull(res.getInner());
    Assert.assertEquals( ..., res.getInner() );
}

2
E questa è una buona cosa, non dovresti avere alcuna logica condizionale nei tuoi test. Rende il test più complesso e meno leggibile. E immagino che Roy abbia delineato nel suo post sul blog che affermazioni multiple su un oggetto vanno bene per la maggior parte del tempo. Quindi quelli che hai sono solo dichiarazioni di guardia ed è giusto averli.
Restuta,

2
I tuoi Assert.notNullsono ridondanti, il tuo test fallirà con un NPE se sono nulli.
Sara

1
Inoltre, il tuo primo esempio (con il if) passerà se resènull
sara,

1
@kai notNull è ridondante, sono d'accordo, ma sento che è più pulito avere l'asserzione (e se non sono pigro anche con un messaggio adeguato) invece dell'eccezione ...
Betlista,

1
Beh, un'affermazione fallita genera anche un'eccezione, e in entrambi i casi ottieni un collegamento diretto alla riga esatta che l'ha lanciata con una traccia dello stack di accompagnamento, quindi personalmente preferirei non ingombrare il test con i presupposti che verranno comunque verificati. Preferirei un oneliner à la Assert.assertEquals(..., service.process().getInner());, possibile con variabili estratte se la linea diventa "troppo lunga"
sara,

6

Penso che ci siano molti casi in cui la scrittura di più asserzioni è valida all'interno della regola secondo cui un test dovrebbe fallire solo per un motivo.

Ad esempio, immagina una funzione che analizza una stringa di date:

function testParseValidDateYMD() {
    var date = Date.parse("2016-01-02");

    Assert.That(date.Year).Equals(2016);
    Assert.That(date.Month).Equals(1);
    Assert.That(date.Day).Equals(0);
}

Se il test ha esito negativo è a causa di un motivo, l'analisi non è corretta. Se sostenessi che questo test può fallire per tre diversi motivi, IMHO sarebbe troppo fine nella definizione di "un motivo".


3

Non conosco alcuna situazione in cui sarebbe una buona idea avere più asserzioni all'interno del metodo [Test] stesso. Il motivo principale per cui alle persone piace avere più asserzioni è che stanno cercando di avere una classe [TestFixture] per ogni classe sottoposta a test. Invece, puoi suddividere i test in più classi [TestFixture]. Ciò consente di vedere diversi modi in cui il codice potrebbe non aver reagito nel modo previsto, anziché solo quello in cui la prima asserzione ha avuto esito negativo. In questo modo hai almeno una directory per classe testata con molte classi [TestFixture] all'interno. Ogni classe [TestFixture] verrebbe nominata in base allo stato specifico di un oggetto che verrà testato. Il metodo [SetUp] porterà l'oggetto nello stato descritto dal nome della classe. Quindi hai più metodi [Test] che asseriscono cose diverse che ti aspetteresti essere vere, dato lo stato corrente dell'oggetto. Ogni metodo [Test] prende il nome dalla cosa che sta affermando, tranne forse potrebbe essere chiamato dopo il concetto anziché solo una lettura inglese del codice. Quindi ogni implementazione del metodo [Test] richiede solo una singola riga di codice in cui sta affermando qualcosa. Un altro vantaggio di questo approccio è che rende i test molto leggibili in quanto diventa abbastanza chiaro cosa stai testando e cosa ti aspetti solo guardando i nomi delle classi e dei metodi. Ciò si ridimensionerà anche quando inizi a realizzare tutti i casi limite che desideri testare e quando trovi i bug. tranne forse potrebbe essere chiamato con il nome del concetto anziché solo una lettura inglese del codice. Quindi ogni implementazione del metodo [Test] richiede solo una singola riga di codice in cui sta affermando qualcosa. Un altro vantaggio di questo approccio è che rende i test molto leggibili in quanto diventa abbastanza chiaro cosa stai testando e cosa ti aspetti solo guardando i nomi delle classi e dei metodi. Ciò si ridimensionerà anche quando inizi a realizzare tutti i casi limite che desideri testare e quando trovi i bug. tranne forse potrebbe essere chiamato con il nome del concetto anziché solo una lettura inglese del codice. Quindi ogni implementazione del metodo [Test] richiede solo una singola riga di codice in cui sta affermando qualcosa. Un altro vantaggio di questo approccio è che rende i test molto leggibili in quanto diventa abbastanza chiaro cosa stai testando e cosa ti aspetti solo guardando i nomi delle classi e dei metodi. Ciò si ridimensionerà anche quando inizi a realizzare tutti i casi limite che desideri testare e quando trovi i bug. e cosa ti aspetti solo guardando i nomi delle classi e dei metodi. Ciò si ridimensionerà anche quando inizi a realizzare tutti i casi limite che desideri testare e quando trovi i bug. e cosa ti aspetti solo guardando i nomi delle classi e dei metodi. Ciò si ridimensionerà anche quando inizi a realizzare tutti i casi limite che desideri testare e quando trovi i bug.

Di solito ciò significa che l'ultima riga di codice all'interno del metodo [SetUp] deve memorizzare un valore di proprietà o un valore di ritorno in una variabile di istanza privata di [TestFixture]. Quindi potresti affermare più cose diverse su questa variabile di istanza da diversi metodi [Test]. Potresti anche fare affermazioni su quali diverse proprietà dell'oggetto sotto test sono impostate ora che si trova nello stato desiderato.

A volte è necessario fare affermazioni lungo il percorso mentre si sta testando l'oggetto nello stato desiderato per assicurarsi che non si sia incasinato prima di riportare l'oggetto nello stato desiderato. In tal caso, tali asserzioni aggiuntive dovrebbero apparire all'interno del metodo [SetUp]. Se qualcosa va storto nel metodo [SetUp], sarà chiaro che qualcosa non ha funzionato nel test prima che l'oggetto raggiungesse lo stato desiderato che si intendeva testare.

Un altro problema che potresti riscontrare è che potresti provare un'eccezione che ti aspetti di essere generata. Questo può indurti a non seguire il modello sopra. Tuttavia, può ancora essere ottenuto rilevando l'eccezione all'interno del metodo [SetUp] e memorizzandola in una variabile di istanza. Ciò ti consentirà di affermare cose diverse sull'eccezione, ognuna nel proprio metodo [Test]. È quindi possibile inoltre affermare altre cose sull'oggetto sottoposto a test per assicurarsi che non vi siano effetti collaterali indesiderati dovuti all'eccezione generata.

Esempio (questo sarebbe suddiviso in più file):

namespace Tests.AcctTests
{
    [TestFixture]
    public class no_events
    {
        private Acct _acct;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
        }

        [Test]
        public void balance_0() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_0
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(0m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_negative
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(-0.01m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }
}

Come gestite l'input TestCase in questo caso?
Simon Gillbee,

Quindi, nella mia attuale organizzazione, abbiamo oltre 20.000 test unitari molto simili a quello che mostri. Questo è un incubo Gran parte del codice di impostazione del test viene copiato / incollato, causando un'impostazione del test errata e test non validi che superano. Per ogni [Test]metodo, quella classe viene ri-istanziata e il [SetUp]metodo viene eseguito nuovamente. Ciò uccide .NET Garbage Collector e causa l'esecuzione molto lenta dei test: 5+ minuti in locale, 20+ minuti sul server di compilazione. I test da 20K dovrebbero essere eseguiti in circa 2-3 minuti. Non consiglierei affatto questo stile di test, specialmente per una suite di test di grandi dimensioni.
fourpastmidnight

@fourpastmidnight la maggior parte di ciò che hai detto sembra una critica valida, ma il punto su come copiare e incollare il codice di installazione e quindi essere sbagliato, non è un problema di struttura ma di programmatori irresponsabili (che potrebbero essere il risultato di manager irresponsabili o di un ambiente negativo più dei cattivi programmatori). Se le persone copiano e incollano semplicemente il codice e sperano che sia corretto e non si preoccupino di comprenderlo, in qualsiasi contesto per qualsiasi motivo, devono essere addestrati a non farlo o lasciarsi andare se non possono essere allenato. Ciò va contro ogni buon principio della programmazione.
still_dreaming_1

Ma in generale, sono d'accordo, questo è un eccesso eccessivo che porterà a molti gonfiori / bagagli / duplicazioni che porteranno a tutti i tipi di problemi. Ero pazzo e raccomandavo cose come questa. Questo è quello che spero di poter dire ogni giorno di me stesso il giorno prima perché ciò significa che non smetto mai di trovare modi migliori di fare le cose.
still_dreaming_1

@ still_dreaming_1 wrt "programmatori irresponsabili": concordo sul fatto che questo comportamento sia un grosso problema. Tuttavia, questo tipo di struttura di test invita davvero questo tipo di comportamento, nel bene e nel male. A parte le cattive pratiche di sviluppo, la mia principale obiezione a questo modulo è che uccide davvero le prestazioni dei test. Non c'è niente di peggio di una suite di test a esecuzione lenta. Una suite di test a esecuzione lenta significa che le persone non eseguiranno i test localmente e saranno persino tentati di saltarli su build intermedie - di nuovo, un problema di persone, ma succede - il che può essere evitato assicurandoti di avere test di esecuzione veloci per iniziare con.
fourpastmidnight

2

Avere più asserzioni nello stesso test è solo un problema quando il test fallisce. Quindi potrebbe essere necessario eseguire il debug del test o analizzare l'eccezione per scoprire quale affermazione fallisce. Con un'affermazione in ogni test è di solito più facile individuare ciò che è sbagliato.

Non riesco a pensare a uno scenario in cui sono realmente necessarie più asserzioni , dal momento che puoi sempre riscriverle come più condizioni nella stessa asserzione. Potrebbe essere preferibile, ad esempio, disporre di più passaggi per verificare i dati intermedi tra i passaggi anziché rischiare che i passaggi successivi si arrestino in modo anomalo a causa di input errati.


1
Se stai combinando più condizioni in una singola asserzione, allora in caso di fallimento tutto quello che sai è che uno ha fallito. Con più asserzioni conosci specificamente alcuni di essi (quelli fino al fallimento incluso). Valuta di verificare che un array restituito contenga un singolo valore: controlla che non sia nullo, quindi ha esattamente un elemento e quindi il valore di quell'elemento. (A seconda della piattaforma) il solo controllo immediato del valore potrebbe dare una deduzione nulla (meno utile dell'asserzione nulla non riuscita) e non verificare la lunghezza dell'array.
Richard,

@Richard: ottenere un risultato e quindi estrarre qualcosa da quel risultato sarebbe un processo in più fasi, quindi l'ho trattato nel secondo paragrafo della risposta.
Guffa,

2
Regola empirica: se si hanno più asserzioni in un test, ognuna dovrebbe avere un messaggio diverso. Quindi non hai questo problema.
Anthony,

E l'utilizzo di un runner per test di qualità come NCrunch ti mostrerà esattamente su quale linea ha fallito il test, sia nel codice di test che nel codice in test.
fourpastmidnight

2

Se il test fallisce, non saprai se anche le seguenti affermazioni si interromperanno. Spesso, ciò significa che ti mancheranno preziose informazioni per capire l'origine del problema. La mia soluzione è usare un'asserzione ma con diversi valori:

String actual = "val1="+val1+"\nval2="+val2;
assertEquals(
    "val1=5\n" +
    "val2=hello"
    , actual
);

Ciò mi consente di vedere tutte le asserzioni fallite contemporaneamente. Uso diverse righe perché la maggior parte degli IDE visualizzerà le differenze di stringa in una finestra di dialogo di confronto fianco a fianco.


2

Se hai più asserzioni in una singola funzione di test, mi aspetto che siano direttamente rilevanti per il test che stai conducendo. Per esempio,

@Test
test_Is_Date_segments_correct {

   // It is okay if you have multiple asserts checking dd, mm, yyyy, hh, mm, ss, etc. 
   // But you would not have any assert statement checking if it is string or number,
   // that is a different test and may be with multiple or single assert statement.
}

Fare molti test (anche quando ritieni che sia probabilmente un eccesso) non è una brutta cosa. Puoi sostenere che avere i test vitali e essenziali è più importante. Quindi, quando affermi, assicurati che le tue affermazioni siano posizionate correttamente piuttosto che preoccuparti troppo di più asserzioni. Se hai bisogno di più di uno, utilizzane più di uno.


1

L'obiettivo del test unitario è quello di fornire quante più informazioni possibili su ciò che non funziona, ma anche di aiutare a individuare con precisione i problemi più importanti per primi. Quando sai logicamente che un'asserzione fallirà dato che un'altra asserzione fallisce o in altre parole c'è una relazione di dipendenza tra il test, allora ha senso farli rotolare come più asserzioni all'interno di un singolo test. Ciò ha il vantaggio di non sporcare i risultati del test con evidenti fallimenti che avrebbero potuto essere eliminati se avessimo salvato la prima asserzione all'interno di un singolo test. Nel caso in cui questa relazione non esista, la preferenza sarebbe naturalmente quella di separare queste asserzioni in singoli test perché altrimenti la ricerca di questi errori richiederebbe più iterazioni di esecuzioni di test per risolvere tutti i problemi.

Se poi si progettano anche le unità / classi in modo tale da dover scrivere test eccessivamente complessi, ciò comporta un minore onere durante i test e probabilmente promuove una progettazione migliore.


1

Sì, è bene avere più asserzioni purché un test fallito ti fornisca informazioni sufficienti per poter diagnosticare l'errore. Questo dipenderà da cosa stai testando e quali sono le modalità di errore.

I test unitari corretti dovrebbero fallire esattamente per una ragione, ecco perché dovresti usare un assert per unit test.

Non ho mai trovato tali formulazioni utili (che una classe dovrebbe avere un motivo per cambiare è un esempio di un adagio così inutile). Si consideri un'asserzione che due stringhe sono uguali, questo è semanticamente equivalente all'asserire che la lunghezza delle due stringhe è la stessa e che ogni carattere dell'indice corrispondente è uguale.

Potremmo generalizzare e dire che qualsiasi sistema di asserzioni multiple potrebbe essere riscritto come una singola asserzione, e ogni singola asserzione potrebbe essere scomposta in una serie di asserzioni più piccole.

Quindi, concentrati solo sulla chiarezza del codice e sulla chiarezza dei risultati del test e lascia che guidino il numero di asserzioni che usi piuttosto che viceversa.


0

La risposta è molto semplice: se si verifica una funzione che modifica più di un attributo, dello stesso oggetto o anche due oggetti diversi e la correttezza della funzione dipende dai risultati di tutte queste modifiche, si desidera affermare che ognuna di queste modifiche è stata eseguita correttamente!

Mi viene l'idea di un concetto logico, ma la conclusione inversa direbbe che nessuna funzione deve mai cambiare più di un oggetto. Ma è impossibile da attuare in tutti i casi, nella mia esperienza.

Prendi il concetto logico di una transazione bancaria: nella maggior parte dei casi, prelevare un importo da un conto bancario DEVE includere l'aggiunta di tale importo a un altro conto. Non vuoi MAI separare queste due cose, formano un'unità atomica. Potresti voler fare due funzioni (ritira / aggiungiMoney) e quindi scrivere due diversi test unitari - in aggiunta. Ma queste due azioni devono aver luogo all'interno di una transazione e vuoi anche assicurarti che la transazione funzioni. In tal caso, semplicemente non è sufficiente assicurarsi che i singoli passaggi abbiano avuto successo. Devi controllare entrambi i conti bancari, nel tuo test.

Potrebbero esserci esempi più complessi che non verrebbero testati in un unit test, in primo luogo, ma invece in un test di integrazione o accettazione. Ma quei confini sono fluidi, IMHO! Non è così facile decidere, è una questione di circostanze e forse di preferenze personali. Prelevare denaro da uno e aggiungerlo a un altro account è ancora una funzione molto semplice e sicuramente un candidato per i test unitari.


-1

Questa domanda è collegata al classico problema del bilanciamento tra il codice degli spaghetti e quello delle lasagne.

Avere più asserzioni potrebbe facilmente entrare nel problema degli spaghetti in cui non hai idea di cosa sia il test, ma avere una singola asserzione per test potrebbe rendere il tuo test ugualmente illeggibile con più test in una grande lasagna rendendo la scoperta di quale test fa ciò che è impossibile .

Ci sono alcune eccezioni, ma in questo caso mantenere la pendola nel mezzo è la risposta.


-3

Non sono nemmeno d'accordo con il "fallimento per un solo motivo" in generale. Ciò che è più importante è che i test sono brevi e leggono chiaramente.

Ciò non è sempre realizzabile e quando un test è complicato un nome (lungo) descrittivo e testare meno cose ha più senso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.