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

12

Ho un sacco di classi che trattano la convalida dei valori. Ad esempio, una classe RangeValidator controlla se un valore rientra nell'intervallo specificato.

Ogni classe di validatori contiene due metodi: is_valid(value) , che restituisce True o False a seconda del valore e ensure_valid(value) che verifica un valore specificato e non fa nulla se il valore è valido, o genera un'eccezione specifica se il valore non corrisponde alle regole predefinite.

Al momento esistono due test unitari associati a questo metodo:

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

    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 lanciata l'eccezione, e succede se ensure_valid non lancia nulla - il fatto che non ci sia assert s all'interno sembra strano. Qualcuno che legge tale codice si chiederebbe immediatamente perché c'è un test che appare per non fare nulla.

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

    
posta Arseni Mourzenko 31.01.2017 - 22:29
fonte

4 risposte

22

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

    
risposta data 31.01.2017 - 22:53
fonte
7

Questo dipende strongmente dalla lingua e dal framework usato. Parlando in termini di NUnit , ci sono metodi Assert.Throws(...) . Puoi passare loro un metodo lambda:

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

che viene eseguito all'interno di Assert.Throws . Molto probabilmente la chiamata al lambda sarà racchiusa da un blocco try { } catch { } e l'asserzione fallirà se viene rilevata un'eccezione.

Se il tuo framework non fornisce questi mezzi, puoi lavorarci intorno, 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)
{
}

Ciò rende l'intenzione più chiara, ma ingombra il codice in una certa misura. (Naturalmente puoi racchiudere tutte queste cose in un metodo.) Alla fine spetterà a te.

Modifica

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

Assert.DoesNotThrow(() => rangeValidator.EnsureValid(-5))
    
risposta data 01.02.2017 - 09:17
fonte
1

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

Ad esempio:

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

Nel tuo test:

emailSenderMock.VerifyIgnoreArgs(service =>
    service.Send(It.IsAny<Email>()),
    Times.Once
);
    
risposta data 01.02.2017 - 13:37
fonte
0

Aggiungi un commento per chiarire il motivo per cui 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). Se ti ritrovi a scrivere qualcosa del genere regolarmente, probabilmente i test ti dicono che il design non è ottimale.

    
risposta data 01.02.2017 - 08:52
fonte

Leggi altre domande sui tag