Test unitario metodi nulli?


170

Qual è il modo migliore per testare l'unità di un metodo che non restituisce nulla? In particolare in c #.

Quello che sto davvero cercando di testare è un metodo che prende un file di registro e lo analizza per stringhe specifiche. Le stringhe vengono quindi inserite in un database. Nulla che non sia mai stato fatto prima, ma essendo MOLTO nuovo di TDD, mi chiedo se è possibile testarlo o è qualcosa che non viene realmente testato.


55
Per favore non usare il termine "TDD" se non è quello che stai facendo. Stai facendo Unit Testing, non TDD. Se stessi facendo TDD, non avresti mai una domanda come "come testare un metodo". Il test esisterebbe prima, e poi la domanda sarebbe: "come far passare questo test?" Ma se stavi facendo TDD, il tuo codice verrebbe scritto per il test (non viceversa), e alla fine finiresti per rispondere alla tua domanda. Il tuo codice verrebbe formattato diversamente a causa di TDD e questo problema non si verificherebbe mai. Sto solo chiarendo.
Suamere

Risposte:


151

Se un metodo non restituisce nulla, è una delle seguenti

  • imperativo - O stai chiedendo all'oggetto di fare qualcosa per se stesso ... ad esempio, cambia stato (senza aspettarti alcuna conferma .. si presume che sarà fatto)
  • informativo - semplicemente notificando a qualcuno che è successo qualcosa (senza aspettarsi azione o risposta) rispettivamente.

Metodi imperativi: è possibile verificare se l'attività è stata effettivamente eseguita. Verifica se il cambio di stato è effettivamente avvenuto. per esempio

void DeductFromBalance( dAmount ) 

può essere verificato verificando se il saldo post questo messaggio è effettivamente inferiore al valore iniziale di dAmount

Metodi informativi - sono rari come membri dell'interfaccia pubblica dell'oggetto ... quindi non sono normalmente testati dall'unità. Tuttavia, se è necessario, è possibile verificare se la gestione deve essere eseguita su una notifica. per esempio

void OnAccountDebit( dAmount )  // emails account holder with info

può essere testato verificando se l'e-mail viene inviata

Pubblica ulteriori dettagli sul tuo metodo attuale e le persone saranno in grado di rispondere meglio.
Aggiornamento : il tuo metodo sta facendo 2 cose. In realtà lo dividerei in due metodi che ora possono essere testati in modo indipendente.

string[] ExamineLogFileForX( string sFileName );
void InsertStringsIntoDatabase( string[] );

String [] può essere facilmente verificato fornendo al primo metodo un file fittizio e le stringhe previste. Il secondo è leggermente complicato .. puoi usare un Mock (google o cercare stackoverflow su framework di derisione) per imitare il DB o colpire il DB effettivo e verificare se le stringhe sono state inserite nella giusta posizione. Dai un'occhiata a questa discussione per alcuni buoni libri ... Consiglierei Pragmatic Unit Test se sei in crisi.
Nel codice sarebbe usato come

InsertStringsIntoDatabase( ExamineLogFileForX( "c:\OMG.log" ) );

1
hey gishu, buona risposta. L'esempio che hai dato ... non sono più Test di integrazione ...? e se è così, la domanda rimane: come si fa veramente a testare i metodi del vuoto ... forse è impossibile?
Andy,

2
@andy - dipende dalla tua definizione di "test di integrazione". I metodi imperativi di solito cambiano stato, quindi puoi essere verificato da un unit test che interroga lo stato dell'oggetto. I metodi informativi possono essere verificati da un unit test che collega un finto ascoltatore / collaboratore per garantire che il soggetto del test emetta la giusta notifica. Penso che entrambi possano essere ragionevolmente testati tramite test unitari.
Gishu,

@andy il database può essere deriso / separato da un'interfaccia accessor permettendo così all'azione di essere testata dai dati passati all'oggetto finto.
Peter Geiger,

62

Prova i suoi effetti collaterali. Ciò comprende:

  • Fa qualche eccezione? (In caso affermativo, verificalo. In caso contrario, prova alcuni casi angolari che potrebbero non essere accurati: gli argomenti null sono la cosa più ovvia.)
  • Gioca bene con i suoi parametri? (Se sono mutabili, li muta quando non dovrebbe e viceversa?)
  • Ha l'effetto giusto sullo stato dell'oggetto / tipo su cui lo stai chiamando?

Naturalmente, c'è un limite a quanto più è possibile verificare. In genere non è possibile verificare con ogni possibile input, ad esempio. Prova pragmaticamente - abbastanza per darti la certezza che il tuo codice è progettato in modo appropriato e implementato correttamente e abbastanza da fungere da documentazione supplementare per ciò che un chiamante potrebbe aspettarsi.


31

Come sempre: prova cosa dovrebbe fare il metodo!

Dovrebbe cambiare lo stato globale (uhh, odore di codice!) Da qualche parte?

Dovrebbe chiamare in un'interfaccia?

Dovrebbe generare un'eccezione quando viene chiamato con parametri errati?

Non dovrebbe fare eccezione se chiamato con i parametri giusti?

Dovrebbe ...?


11

Tipi di ritorno vuoti / Subroutine sono vecchie notizie. Non ho creato un tipo di ritorno Void (a meno che non fossi estremamente pigro) da circa 8 anni (Dal momento della risposta, quindi solo un po 'prima che questa domanda fosse posta).

Invece di un metodo come:

public void SendEmailToCustomer()

Crea un metodo che segua il paradigma int.TryParse () di Microsoft:

public bool TrySendEmailToCustomer()

Forse non ci sono informazioni che il tuo metodo deve restituire per l'utilizzo a lungo termine, ma restituire lo stato del metodo dopo aver eseguito il suo lavoro è di grande utilità per il chiamante.

Inoltre, bool non è l'unico tipo di stato. Esistono diverse volte in cui una subroutine precedentemente creata può effettivamente restituire tre o più stati diversi (buono, normale, cattivo, ecc.). In quei casi, useresti semplicemente

public StateEnum TrySendEmailToCustomer()

Tuttavia, mentre Try-Paradigm risponde in qualche modo a questa domanda su come testare un ritorno vuoto, ci sono anche altre considerazioni. Ad esempio, durante / dopo un ciclo "TDD", dovresti essere "Refactoring" e notare che stai facendo due cose con il tuo metodo ... infrangendo così il "Principio di responsabilità singola". Quindi dovrebbe essere curato prima. In secondo luogo, potresti aver identificato una dipendenza ... stai toccando i dati "persistenti".

Se si stanno eseguendo le operazioni di accesso ai dati nel metodo in questione, è necessario eseguire il refactoring in un'architettura a più livelli o più livelli. Ma possiamo supporre che quando dici "Le stringhe vengono quindi inserite in un database", in realtà intendi che stai chiamando un livello di logica aziendale o qualcosa del genere. Ya, lo supponiamo.

Quando il tuo oggetto viene istanziato, ora capisci che il tuo oggetto ha dipendenze. Questo è quando è necessario decidere se si intende eseguire l'iniezione di dipendenza sull'oggetto o sul metodo. Ciò significa che il tuo costruttore o il metodo in questione necessita di un nuovo parametro:

public <Constructor/MethodName> (IBusinessDataEtc otherLayerOrTierObject, string[] stuffToInsert)

Ora che è possibile accettare un'interfaccia dell'oggetto livello business / dati, è possibile deriderlo durante i test delle unità e non avere dipendenze o timori di test di integrazione "accidentale".

Quindi, nel tuo codice live, passi un IBusinessDataEtcoggetto REALE . Ma nel tuo Test unitario, passi un IBusinessDataEtcoggetto MOCK . In quel Mock, puoi includere Proprietà Non-Interface simili int XMethodWasCalledCounto qualcosa il cui stato (i) viene aggiornato quando vengono chiamati i metodi dell'interfaccia.

Quindi il tuo test unit esaminerà i tuoi metodi in questione, eseguirà la logica che hanno e chiamerà uno o due o un set selezionato di metodi nel tuo IBusinessDataEtcoggetto. Quando esegui le tue asserzioni alla fine del test unitario, hai un paio di cose da testare ora.

  1. Lo stato della "subroutine" che ora è un metodo Try-Paradigm.
  2. Lo stato del tuo IBusinessDataEtcoggetto Mock .

Per ulteriori informazioni sulle idee di Iniezione delle dipendenze a livello di costruzione ... per quanto riguarda i test unitari ... esaminare i modelli di progettazione del costruttore. Aggiunge un'interfaccia e una classe in più per ogni interfaccia / classe corrente che possiedi, ma sono molto minuscole e forniscono enormi ENTRAMENTI funzionalità per un migliore Unit Test.


Il primo pezzo di questa grande risposta serve come straordinario consiglio generale per tutti i programmatori principianti / intermedi.
pimbrouwers,

non dovrebbe essere qualcosa del genere public void sendEmailToCustomer() throws UndeliveredMailException?
A.Emad

1
@ A.Emad Buona domanda, ma no. Controllare il flusso del codice basandosi sul lancio di eccezioni è una cattiva pratica nota da tempo. Tuttavia, nei voidmetodi, specialmente nei linguaggi orientati agli oggetti, è stata l'unica vera opzione. La più antica alternativa di Microsoft è il Try-Paradigm che discuto, e paradigmi in stile funzionale come Monads / Maybes. Pertanto, i comandi (in CQS) possono comunque restituire preziose informazioni sullo stato invece di fare affidamento sul lancio, che è simile a un GOTO(che sappiamo essere cattivo). il lancio (e il goto) sono lenti, difficili da eseguire il debug e non è una buona pratica.
Suamere,

Grazie per aver chiarito questo. È specifico per C # o è una cattiva pratica in generale generare eccezioni anche in linguaggi come Java e C ++?
A.Emad

9

Prova questo:

[TestMethod]
public void TestSomething()
{
    try
    {
        YourMethodCall();
        Assert.IsTrue(true);
    }
    catch {
        Assert.IsTrue(false);
    }
}

1
Questo non dovrebbe essere necessario ma può essere fatto come tale
Nathan Alard,

Benvenuto in StackOverflow! Ti consigliamo di aggiungere alcune spiegazioni al tuo codice. Grazie.
Aurasphere,

2
Il ExpectedAttributeè stato progettato per rendere questo test in modo più chiaro.
Martin Liversage,

8

Puoi anche provarlo in questo modo:

[TestMethod]
public void ReadFiles()
{
    try
    {
        Read();
        return; // indicates success
    }
    catch (Exception ex)
    {
        Assert.Fail(ex.Message);
    }
}

1
Questo è il modo più semplice suppongo.
Navin Pandit,

5

avrà qualche effetto su un oggetto .... query per il risultato dell'effetto. Se non ha alcun effetto visibile, non vale la pena test unitario!


4

Presumibilmente il metodo fa qualcosa e non ritorna semplicemente?

Supponendo che sia così, allora:

  1. Se modifica lo stato dell'oggetto proprietario, è necessario verificare che lo stato sia stato modificato correttamente.
  2. Se accetta un oggetto come parametro e lo modifica, è necessario verificare che l'oggetto sia stato modificato correttamente.
  3. Se genera eccezioni in alcuni casi, verifica che tali eccezioni vengano generate correttamente.
  4. Se il suo comportamento varia in base allo stato del proprio oggetto o di qualche altro oggetto, preimpostare lo stato e testare il metodo ha l'Ithrough corretto attraverso uno dei tre metodi di test sopra).

Se ci fai sapere cosa fa il metodo, potrei essere più specifico.


3

Usa Rhino Mock per impostare quali chiamate, azioni ed eccezioni potrebbero essere previste. Supponendo che tu possa deridere o stubare parti del tuo metodo. Difficile da sapere senza conoscere alcuni dettagli qui sul metodo o sul contesto.


1
La risposta a come unit test non dovrebbe mai essere quella di ottenere uno strumento di terze parti che lo fa per te. Tuttavia, quando una persona sa come testare l'unità, è accettabile utilizzare uno strumento di terze parti per renderlo più semplice.
Suamere,

2

Dipende da cosa sta facendo. Se ha parametri, passa in beffe che potresti chiedere in seguito se sono stati chiamati con il giusto set di parametri.


Concordato: verificare il comportamento delle beffe testando il metodo sarebbe unidirezionale.
Jeff Schumacher,

0

Qualunque sia l'istanza che stai usando per chiamare il metodo void, puoi semplicemente usare,Verfiy

Per esempio:

Nel mio caso _Logè l'istanza ed LogMessageè il metodo da testare:

try
{
    this._log.Verify(x => x.LogMessage(Logger.WillisLogLevel.Info, Logger.WillisLogger.Usage, "Created the Student with name as"), "Failure");
}
Catch 
{
    Assert.IsFalse(ex is Moq.MockException);
}

I Verifytiri sono un'eccezione a causa del fallimento del metodo che il test fallirebbe?

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.