¿Cómo debo escribir una prueba para un método puro que no devuelve nada?

13

Tengo un montón de clases que se ocupan de la validación de valores. Por ejemplo, una RangeValidatorclase verifica si un valor está dentro del rango especificado.

Cada clase de validador contiene dos métodos: is_valid(value)que devuelve Trueo Falsedepende del valor, y ensure_valid(value)que busca un valor específico y no hace nada si el valor es válido o arroja una excepción específica si el valor no coincide con las reglas predefinidas.

Actualmente hay dos pruebas unitarias asociadas con este método:

  • El que pasa un valor no válido y asegura que se lanzó la excepción.

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

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

Aunque la segunda prueba hace su trabajo, falla si se lanza la excepción y tiene éxito si ensure_validno arroja nada, el hecho de que no haya asserts adentro parece extraño. Alguien que lea dicho código se preguntará de inmediato por qué hay una prueba que parece no estar haciendo nada.

¿Es esta una práctica actual cuando se prueban métodos que no devuelven un valor y no tienen efectos secundarios? ¿O debería reescribir la prueba de una manera diferente? ¿O simplemente poner un comentario explicando lo que estoy haciendo?

Arseni Mourzenko
fuente
11
Punto pedante, pero si tiene una función que no toma argumentos (salvo para una selfreferencia) y no devuelve ningún resultado, no es una función pura.
David Arno
10
@DavidArno: Este no es un punto pedante, va directo al corazón de la pregunta: el método es difícil de probar precisamente porque es impuro.
Jörg W Mittag el
@DavidArno Más punto pedante, podría tener el método "no hacer nada" (suponiendo que "no devuelve resultados" se interpreta como "devuelve un tipo de unidad" que se llama voiden muchos idiomas y tiene reglas tontas). Alternativamente, podría tener un infinito loop (que funciona incluso cuando "no devuelve ningún resultado" realmente significa que no devuelve ningún resultado.
Derek Elkins dejó SE
Hay una función pura de tipo unit -> uniten cualquier idioma que considera la unidad como un tipo de datos estándar en lugar de algo mágicamente especial. Desafortunadamente, solo devuelve la unidad y no hace nada más.
Phoshi

Respuestas:

21

La mayoría de los marcos de prueba tienen una afirmación explícita para "No arroja", por ejemplo, Jasmine tiene expect(() => {}).not.toThrow();ynUnit y sus amigos también tienen una.

DeadMG
fuente
1
Y si el marco de prueba no tiene tal afirmación, siempre es posible crear un método local que haga exactamente lo mismo, haciendo que el código se explique por sí mismo. Buena idea.
Arseni Mourzenko
7

Esto depende en gran medida del lenguaje y el marco utilizado. Hablando en términos de NUnit, hay Assert.Throws(...)métodos. Puedes pasarles un método lambda:

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

que se ejecuta dentro de Assert.Throws. La llamada a la lambda probablemente estará envuelta por un try { } catch { }bloque y la afirmación falla, si se detecta una excepción.

Si su marco no proporciona estos medios, puede solucionarlo, ajustando la llamada usted mismo (estoy escribiendo en 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)
{
}

Esto hace que la intención sea más clara, pero satura el código en cierta medida. (Por supuesto, puede envolver todo esto en un método). Al final, dependerá de usted.

Editar

Como Doc Brown señaló en los comentarios, el problema no ha sido indicar que el método arroja, sino que no arroja. En NUnit también hay una afirmación para eso

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

Simplemente agregue un comentario para aclarar por qué no se necesita ninguna afirmación y por qué no la olvidó.

Como puede ver en las otras respuestas, cualquier otra cosa hace que el código sea más complicado y desordenado. Con el comentario allí, otros programadores sabrán la intención de la prueba.

Dicho esto, este tipo de prueba debería ser una excepción (sin juego de palabras). Si te encuentras escribiendo algo así regularmente, entonces probablemente las pruebas te digan que el diseño no es óptimo.

jhyot
fuente
1

También podría afirmar que algunos métodos se llaman (o no se llaman) correctamente.

Por ejemplo:

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

En tu prueba:

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