Esiste un modo migliore per scrivere unit test rispetto a una serie di "AssertEquals"?


12

Ecco un esempio di base di ciò che deve essere il mio test unitario, 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>

Ora stavo pensando che sia un po 'ripetitivo.

Potrebbe mettere tutti gli input / output in un array e collegarlo in loop.

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]);               
                }

        }
    );

E questo funziona benissimo.

L'unico vantaggio che mi viene in mente per questo secondo metodo è che se si scopre che non si desidera effettivamente utilizzare equalè più facile apportare tale modifica in un punto.

In termini di leggibilità, non credo sia conclusivo in entrambi i casi, anche se probabilmente preferisco il secondo.

Astrattandolo ulteriormente, è possibile inserire i casi di input / output in un file CSV separato, semplificando la modifica.

La domanda è: quali sono le convenzioni generali sulla scrittura di questo tipo di test unitari?

C'è un motivo per cui non dovresti metterli in array?


Uno di questi ti dirà quale valore è fallito?
JeffO,

1
@JeffO - Sì - Con QUnit - Se un test fallisce, l'output mostrerà il valore atteso e il valore effettivo.
dwjohnston,

Risposte:


8

I tuoi test refactored hanno un odore: Logica test condizionale .

I motivi per cui dovresti evitare di scrivere la logica condizionale nei tuoi test sono duplici. Il primo è che compromette la tua capacità di essere sicuro che il tuo codice di test sia corretto, come descritto nell'articolo xUnit Patterns collegato.

Il secondo è che oscura il significato dei test. Scriviamo Metodi di Test perché mettono la logica per testare un determinato comportamento in un posto e ci permettono di dargli un nome descrittivo (vedi l'articolo BDD originale di Dan North per un'esplorazione del valore di buoni nomi per i test). Quando i tuoi test sono nascosti all'interno di una singola funzione con un forciclo, oscura il significato del codice per il lettore. Non solo il lettore deve comprendere il ciclo, ma deve anche svelare mentalmente tutti i diversi comportamenti testati all'interno del ciclo.

La soluzione, come sempre, è di salire di un livello di astrazione. Usa un framework di test che ti dia test parametrizzati , come xUnit.NET o Contexts (disclaimer: ho scritto Contexts). Ciò consente di raggruppare i test di triangolazione per lo stesso comportamento in modo naturale, mantenendo separati i test per comportamenti separati.


Bella domanda, a proposito
Benjamin Hodgson il

1
1) Se sali di un livello di astrazione, non stai nascondendo quegli stessi dettagli che hai detto sono stati oscurati dal ciclo for? 2) non sono sicuri che i test con parametri siano applicabili qui. Sembra che ci siano dei parallelismi qui da qualche parte, ma ho avuto molte situazioni simili ai PO in cui avevo un set di dati di 10-20 valori e volevo solo eseguirli tutti attraverso SUT. Sì, ogni valore è diverso e potenzialmente mette alla prova diversi boudary, ma sembra effettivamente che "inventare" i nomi dei test per ogni singolo valore sarebbe eccessivo. Ho trovato un rapporto valore / codice ottimale usando simili ...
DXM,

... loop. Fino a quando il test fallisce, l'asserzione stampa esattamente ciò che è fallito, lo sviluppatore ha abbastanza feedback per individuare con precisione il problema.
DXM,

@DXM 1) il framework di test fornisce la funzionalità di test parametrizzata. Confidiamo implicitamente nel framework di test, quindi non scriviamo test per questo. 2) i test parametrizzati sono esattamente per questo scopo: stai facendo esattamente gli stessi passi ogni volta ma con valori di input / output diversi. Il framework di test consente di risparmiare la necessità di scrivere nomi per ognuno eseguendo i diversi input con lo stesso metodo di test.
Benjamin Hodgson,

5

Sembra che tu voglia davvero un Data Unit Driven Unit Test. Da quando hai menzionato l'utilizzo di QUnit, ho trovato un plug-in che abilita i test con parametri:

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

Non c'è nulla di ideologicamente sbagliato in un test guidato dai dati, purché il codice del test stesso non sia condizionato. Guardando il tuo codice di test, sembra essere un ottimo candidato per un Data Driven Test.

Codice di esempio per il README di 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);
    });

1
D'accordo, sembra un test basato sui dati. Ma sembra che sia quello che ha già nel suo secondo esempio di codice.
Robert Harvey,

1
@RobertHarvey - Corretto. Esiste un termine accettato per ciò che sta cercando di realizzare ed esiste un plug-in per il framework di test utilizzato per rendere più semplice la scrittura di questo tipo di test. Ho pensato che valesse la pena notare in una risposta per il futuro, tutto qui.
Greg Burghardt,

1

Ti stai ripetendo meno utilizzando l'array che è più gestibile. Un approccio che mi piace usare è quello di disporre di un metodo separato che disponga, agisca e asserisca i test, ma accetti i parametri di input con cui sto testando, quindi ho 1 metodo di test per set di input.

Questo mi permette di dire istantaneamente quali test / input stanno fallendo.


0

Mi piace il tuo secondo approccio, ma aggiungerei 2 punti

  • non utilizzare array per archiviare dati testati, poiché lavorare con gli indici non è un modo pulito
  • non usare forloop

`

[
    {
        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);
});

Non sono sicuro di qunit, ma un buon test runner ti mostrerà quale stringa di input non è riuscita e quale sarà il risultato atteso

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.