Como devo escrever um teste para um método puro que não retorna nada?

13

Eu tenho um monte de classes que lidam com validação de valores. Por exemplo, uma RangeValidatorclasse verifica se um valor está dentro do intervalo especificado.

Toda classe de validador contém dois métodos is_valid(value):, que retorna Trueou Falsedepende do valor, e ensure_valid(value)que verifica um valor especificado e não faz nada se o valor for válido ou lança uma exceção específica se o valor não corresponder às regras predefinidas.

Atualmente, existem dois testes de unidade associados a este método:

  • Aquele que passa um valor inválido e garante que a exceção foi lançada.

    def test_outside_range(self):
        with self.assertRaises(demo.ValidationException):
            demo.RangeValidator(0, 100).ensure_valid(-5)
    
  • Aquele que passa um valor válido.

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

Embora o segundo teste faça seu trabalho - falha se a exceção é lançada e é bem-sucedido se ensure_validnão lança nada - o fato de não haver asserts dentro parece estranho. Alguém que lê esse código imediatamente se pergunta por que existe um teste que parece não estar fazendo nada.

Essa é uma prática atual ao testar métodos que não retornam um valor e não têm efeitos colaterais? Ou devo reescrever o teste de uma maneira diferente? Ou simplesmente faça um comentário explicando o que estou fazendo?

Arseni Mourzenko
fonte
possivelmente relacionado: Como não testar a implementação quando o método retorna nulo?
precisa saber é
11
Ponto pedante, mas se você tem uma função que não aceita argumentos (exceto uma selfreferência) e não retorna resultado, não é uma função pura.
David Arno
10
@ David David: Este não é um ponto pedante, ele vai direto ao cerne da questão: o método é difícil de testar precisamente porque é impuro.
Jörg W Mittag
@DavidArno Ponto mais pedante, você pode usar o método "não fazer nada" (supondo que "retorna nenhum resultado" seja interpretado como "retorna um tipo de unidade", que é chamado voidem muitos idiomas e tem regras tolas). Como alternativa, você pode ter um número infinito loop (que funciona mesmo quando "não retorna resultados" realmente significa que não retorna resultados).
Derek Elkins saiu de SE
Existe uma função pura do tipo unit -> unitem qualquer idioma que considera a unidade um tipo de dados padrão em vez de algo magicamente especial. Infelizmente, apenas retorna a unidade e não faz mais nada.
Phoshi

Respostas:

21

A maioria das estruturas de teste possui uma asserção explícita para "Não lança", por exemplo, Jasmine possui expect(() => {}).not.toThrow();nUnit e amigos também.

DeadMG
fonte
1
E se a estrutura de teste não tiver essa afirmação, sempre é possível criar um método local que faça exatamente a mesma coisa, tornando o código auto-explicativo. Boa ideia.
Arseni Mourzenko 31/01
7

Isso depende fortemente da linguagem e da estrutura usada. Falando em termos de NUnit, existem Assert.Throws(...)métodos. Você pode passar a eles um método lambda:

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

que é executado dentro do Assert.Throws. A chamada para o lambda provavelmente será encerrada por um try { } catch { }bloco e a asserção falhará, se uma exceção for capturada.

Se sua estrutura não fornecer esses meios, você poderá contornar isso, encerrando a chamada sozinho (estou escrevendo em 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)
{
}

Isso torna a intenção mais clara, mas desordena o código em certa medida. (É claro que você pode agrupar todas essas coisas em um método.) No final, tudo depende de você.

Editar

Como Doc Brown apontou nos comentários, a questão não foi indicar que o método lança, mas que não lança. No NUnit, há também uma afirmação para que

Assert.DoesNotThrow(() => rangeValidator.EnsureValid(-5))
Paul Kertscher
fonte
1

Basta adicionar um comentário para deixar claro por que nenhuma afirmação é necessária e por que você não a esqueceu.

Como você pode ver nas outras respostas, qualquer outra coisa torna o código mais complicado e confuso. Com o comentário lá, outros programadores saberão a intenção do teste.

Dito isto, esse tipo de teste deve ser uma exceção (sem trocadilhos). Se você estiver escrevendo algo assim regularmente, provavelmente os testes estão dizendo que o design não é o ideal.

jhyot
fonte
1

Você também pode afirmar que alguns métodos são chamados (ou não chamados) corretamente.

Por exemplo:

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

No seu teste:

emailSenderMock.VerifyIgnoreArgs(service =>
    service.Send(It.IsAny<Email>()),
    Times.Once
);
Gabriel Robert
fonte