¿Existe una mejor manera de escribir pruebas unitarias que una serie de 'AssertEquals'?

12

Aquí hay un ejemplo básico de lo que debe ser mi prueba de unidad, usando qunit:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>

<link rel="stylesheet" href="qunit/qunit-1.13.0.css">
<script src = "qunit/qunit-1.13.0.js"></script>
<script src = "../js/fuzzQuery.js"></script>

<script>

test("Fuzz Query Basics", function()
        {
            equal(fuzzQuery("name:(John Smith)"), "name:(John~ Smith~)");
            equal(fuzzQuery("name:Jon~0.1"), "name:Jon~0.1");
            equal(fuzzQuery("Jon"), "Jon~");
            //etc

        }
    );

</script>
</head>
<body>
    <div id="qunit"></div>
</body>
</html>

Ahora estaba pensando que esto es un poco repetitivo.

Podría poner todas las entradas / salidas en una matriz y recorrerla.

test("Fuzz Query Basics", function()
        {
            var equals = [
                           ["name:(John Smith)", "name:(John~ Smith~)"],
                           ["name:Jon~0.1", "name:Jon~0.1"],
                           ["Jon", "Jon~"]
                           ];

            for (var i = 0; i<equals.length; i++)
                {
                    equal(fuzzQuery(equals[i][0]), equals[i][1]);               
                }

        }
    );

Y esto funciona bien.

La única ventaja que se me ocurre para este segundo método es que si resulta que en realidad no quieres usarlo equal, es más fácil hacer ese cambio en un solo lugar.

En términos de legibilidad, no creo que sea concluyente de ninguna manera, aunque probablemente prefiera el segundo.

Para resumirlo más, podría colocar los casos de entrada / salida en un archivo CSV separado, lo que podría facilitar su modificación.

La pregunta es: ¿cuáles son las convenciones generales sobre cómo escribir este tipo de pruebas unitarias?

¿Hay alguna razón por la que no debería ponerlos en matrices?

dwjohnston
fuente
¿Alguno de estos le dirá qué valor falló?
JeffO
1
@JeffO - Sí - Con QUnit - Si una prueba falla, la salida mostrará el valor esperado y el valor real.
dwjohnston

Respuestas:

8

Sus pruebas refactorizadas tienen olor: lógica de prueba condicional .

Las razones por las que debe evitar escribir lógica condicional en sus pruebas son dobles. La primera es que socava su capacidad de estar seguro de que su código de prueba es correcto, como se describe en el artículo de xUnit Patterns vinculado.

El segundo es que oscurece el significado de las pruebas. Escribimos Métodos de prueba porque ponen la lógica para probar un comportamiento dado en un lugar y nos permiten darle un nombre descriptivo (vea el artículo original de Dan North BDD para una exploración del valor de los buenos nombres para las pruebas). Cuando sus pruebas están ocultas dentro de una sola función con un forbucle, oscurece el significado del código para el lector. El lector no solo tiene que comprender el ciclo, sino que también tiene que desentrañar mentalmente todos los diferentes comportamientos que se prueban dentro del ciclo.

La solución, como siempre, es subir un nivel de abstracción. Use un marco de prueba que le proporcione pruebas parametrizadas , como xUnit.NET o Contexts do (descargo de responsabilidad: escribí Contexts). Esto le permite agrupar las pruebas de triangulación para el mismo comportamiento de manera natural, mientras mantiene separadas las pruebas para comportamientos separados.

Benjamin Hodgson
fuente
Buena pregunta, por cierto
Benjamin Hodgson
1
1) Si sube un nivel de abstracción, ¿no está ocultando esos mismos detalles que dijo que están ocultos por el ciclo for? 2) no estoy seguro de que las pruebas parametrizadas sean aplicables aquí. Parece que hay paralelos aquí en alguna parte, pero he tenido muchas situaciones similares a los OP en los que tenía un conjunto de datos de 10-20 valores y solo quería ejecutarlos a todos a través de SUT. Sí, cada valor es diferente y potencialmente prueba diferentes boudaries, pero parece que en realidad los nombres de prueba "inventar" para cada valor sería una exageración. Encontré una relación óptima de valor / tamaño de código al usar similares ...
DXM
... bucles. Siempre y cuando la prueba falle, la afirmación imprime exactamente lo que falló, el desarrollador tiene suficientes comentarios para identificar con precisión el problema.
DXM
@DXM 1) el marco de prueba proporciona la funcionalidad de prueba parametrizada. Confiamos en el marco de prueba implícitamente, por lo que no escribimos pruebas para él. 2) las pruebas parametrizadas son exactamente para este propósito: está haciendo exactamente los mismos pasos cada vez pero con diferentes valores de entrada / salida. El marco de prueba le ahorra la necesidad de escribir nombres para cada uno al ejecutar las diferentes entradas a través del mismo método de prueba.
Benjamin Hodgson el
5

Parece que realmente quieres una prueba de unidad controlada por datos. Como mencionó el uso de QUnit, encontré un complemento que permite pruebas parametrizadas:

https://github.com/AStepaniuk/qunit-parameterize

No hay nada ideológicamente incorrecto con una prueba basada en datos, siempre que el código de prueba en sí no sea condicional. Mirando su código de prueba, parece ser un muy buen candidato para una prueba dirigida por datos.

Código de ejemplo para el archivo README de GitHub:

QUnit
    .cases([
        { a : 2, b : 2, expectedSum : 4 },
        { a : 5, b : 5, expectedSum : 10 },
        { a : 40, b : 2, expectedSum : 42 }
    ])
    .test("Sum test", function(params) {
        var actualSum = sum(params.a, params.b);
        equal(actualSum, params.expectedSum);
    });
Greg Burghardt
fuente
1
De acuerdo, parece una prueba basada en datos. Pero parece que eso es lo que ya tiene en su segundo ejemplo de código.
Robert Harvey
1
@RobertHarvey - Correcto. Hay un término aceptado para lo que está tratando de lograr, y existe un complemento para el marco de prueba que se utiliza para facilitar la escritura de este tipo de pruebas. Pensé que valía la pena señalar una respuesta para el futuro, eso es todo.
Greg Burghardt
1

Se repite menos utilizando la matriz que es más fácil de mantener. Un enfoque que me gusta usar es tener un método separado que organiza, actúa y afirma las pruebas, pero acepta los parámetros de entrada con los que estoy probando, por lo que tengo 1 método de prueba por conjunto de entrada.

Esto me permite decir instantáneamente qué prueba / entradas están fallando.

Kevin
fuente
0

Me gusta tu segundo enfoque, pero agregaría 2 puntos

  • no use matrices para almacenar datos probados, ya que trabajar con índices no es una forma limpia
  • no use forbucles

``

[
    {
        process: "name:(John Smith)",
        result: "name:(John~ Smith~)"
    },
    {
        process: "name:Jon~0.1", 
        result: "name:Jon~0.1"
    },
    {
        process: "Jon", 
        result: "Jon~"
    }
]
.forEach(function(data){

    var result = fuzzQuery(data.process);
    equal(result, data.result);
});

No estoy seguro acerca de qunit, pero un buen corredor de prueba le mostrará qué cadena de entrada falló y cuál fue el resultado esperado

tenbits
fuente