Come devo scrivere un test per un metodo puro che non restituisce nulla?


13

Ho un sacco di classi che si occupano della validazione dei valori. Ad esempio, una RangeValidatorclasse controlla se un valore è compreso nell'intervallo specificato.

Ogni classe di validazione contiene due metodi is_valid(value):, che restituisce Trueo Falsedipende dal valore e ensure_valid(value)che verifica un valore specificato e non fa nulla se il valore è valido oppure genera un'eccezione specifica se il valore non corrisponde alle regole predefinite.

Esistono attualmente due test unitari associati a questo metodo:

  • Quello che passa un valore non valido e garantisce che sia stata generata l'eccezione.

    def test_outside_range(self):
        with self.assertRaises(demo.ValidationException):
            demo.RangeValidator(0, 100).ensure_valid(-5)
    
  • Quello che passa un valore valido.

    def test_in_range(self):
        demo.RangeValidator(0, 100).ensure_valid(25)
    

Anche se il secondo test fa il suo lavoro - fallisce se viene generata l'eccezione e riesce se ensure_validnon getta nulla - il fatto che non ci sia assertdentro sembra strano. Qualcuno che legge tale codice si chiederebbe immediatamente perché esiste un test che sembra non fare nulla.

È una pratica corrente quando si testano metodi che non restituiscono un valore e non hanno effetti collaterali? O dovrei riscrivere il test in modo diverso? O semplicemente metti un commento che spiega cosa sto facendo?



11
Punto pedante, ma se hai una funzione che non accetta argomenti (salva come selfriferimento) e non restituisce alcun risultato, non è una funzione pura.
David Arno,

10
@DavidArno: Questo non è un punto pedante, va dritto al nocciolo della domanda: il metodo è difficile da testare proprio perché è impuro.
Jörg W Mittag,

@DavidArno Punto più pedante, potresti avere il metodo "non fare nulla" (supponendo che "restituisce nessun risultato" sia interpretato come "restituisce un tipo di unità" che viene chiamato voidin molte lingue e ha regole sciocche). In alternativa, potresti avere un infinito loop (che funziona anche quando "non restituisce alcun risultato" significa in realtà non restituisce risultati.
Derek Elkins ha lasciato SE

Esiste una pura funzione di tipo unit -> unitin qualsiasi linguaggio che considera l'unità un tipo di dati standard anziché qualcosa di magicamente speciale. Sfortunatamente, restituisce solo unità e non fa nient'altro.
Phoshi,

Risposte:


21

La maggior parte dei framework di test ha un'affermazione esplicita per "Non getta", ad esempio Jasmine expect(() => {}).not.toThrow();e nUnit e anche gli amici ne hanno uno.


1
E se il framework di test non ha tale affermazione, è sempre possibile creare un metodo locale che fa esattamente la stessa cosa, rendendo il codice autoesplicativo. Buona idea.
Arseni Mourzenko,

7

Ciò dipende fortemente dalla lingua e dalla struttura utilizzate. Parlando in termini di NUnit, ci sono Assert.Throws(...)metodi. Puoi passare loro un metodo lambda:

Assert.Throws(() => rangeValidator.EnsureValid(-5))

che viene eseguito all'interno di Assert.Throws. La chiamata al lambda sarà molto probabilmente racchiusa in un try { } catch { }blocco e l'asserzione non riuscirà , se viene rilevata un'eccezione.

Se il tuo framework non fornisce questi mezzi, potresti aggirarlo, avvolgendo la chiamata da solo (sto scrivendo in C #):

// assert that the method does not fail
try
{
    rangeValidator.EnsureValid(50);
}
catch(Exception e)
{
    Assert.IsTrue(false, $"Exception: {e.Message}");
}

// assert that the method does fail
try
{
    rangeValidator.EnsureValid(50);
    Assert.IsTrue(false, $"Method is expected to throw an exception");
}
catch(Exception e)
{
}

Questo rende l'intenzione più chiara, ma ingombra il codice in una certa misura. (Certo che puoi avvolgere tutte queste cose in un metodo.) Alla fine dipenderà da te.

modificare

Come ha sottolineato Doc Brown nei commenti, il problema non è stato quello di indicare che il metodo lancia, ma che non lo lancia. In NUnit c'è anche un'affermazione per questo

Assert.DoesNotThrow(() => rangeValidator.EnsureValid(-5))

1

Aggiungi un commento per chiarire perché non è necessaria alcuna affermazione e perché non l'hai dimenticato.

Come puoi vedere nelle altre risposte, qualsiasi altra cosa rende il codice più complicato e disordinato. Con il commento lì, altri programmatori conosceranno l'intento del test.

Detto questo, questo tipo di test dovrebbe essere un'eccezione (nessun gioco di parole previsto). Se ti ritrovi a scrivere qualcosa del genere regolarmente, probabilmente i test ti dicono che il design non è ottimale.


1

Si potrebbe anche affermare che alcuni metodi sono chiamati (o non chiamati) correttamente.

Per esempio:

public void SendEmail(User user)
     ... construct email ...
     _emailSender.Send(email);

Nel tuo test:

emailSenderMock.VerifyIgnoreArgs(service =>
    service.Send(It.IsAny<Email>()),
    Times.Once
);
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.