Existe uma maneira melhor de escrever testes de unidade do que uma série de 'AssertEquals'?

12

Aqui está um exemplo básico do que meu teste de unidade precisa ser, usando o 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>

Agora eu estava pensando que isso é um pouco repetitivo.

Poderia colocar todas as entradas / saídas em uma matriz e passar por ela.

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 isso funciona bem.

A única vantagem que posso pensar para esse segundo método é que, se você realmente não quiser usar equal, será mais fácil fazer essa alteração em um único local.

Em termos de legibilidade, não acho que seja conclusivo de qualquer maneira, embora provavelmente prefira o segundo.

Abstraindo ainda mais, você pode colocar os casos de entrada / saída em um arquivo CSV separado, o que pode facilitar a modificação.

A pergunta é: quais são as convenções gerais sobre como escrever esses tipos de testes de unidade?

Existe uma razão para você não colocá-los em matrizes?

Dwjohnston
fonte
Qualquer um destes lhe dirá qual valor falhou?
Jeffo
1
@ Jeffff - Sim - com QUnit - Se um teste falhar, a saída mostrará o valor esperado e o valor real.
precisa saber é o seguinte

Respostas:

8

Seus testes refatorados têm um cheiro: Lógica Condicional de Testes .

Os motivos pelos quais você deve evitar escrever lógica condicional em seus testes são duplos. A primeira é que isso prejudica sua capacidade de ter certeza de que seu código de teste está correto, conforme descrito no artigo vinculado de padrões do xUnit.

A segunda é que obscurece o significado dos testes. Escrevemos Métodos de Teste porque eles colocam a lógica para testar um determinado comportamento em um único local e nos permitem dar um nome descritivo (consulte o artigo original do BDD de Dan North para uma exploração do valor de bons nomes para testes). Quando seus testes estão ocultos em uma única função com um forloop, oculta o significado do código para o leitor. O leitor não apenas precisa compreender o loop, mas também desvendar mentalmente todos os diferentes comportamentos sendo testados dentro do loop.

A solução, como sempre, é subir um nível de abstração. Use uma estrutura de teste que ofereça testes parametrizados , como xUnit.NET ou Contexts (isenção de responsabilidade: escrevi Contexts). Isso permite agrupar testes de triangulação para o mesmo comportamento de maneira natural, mantendo os testes para comportamentos separados separados.

Benjamin Hodgson
fonte
Boa pergunta, a propósito
Benjamin Hodgson
1
1) Se você subir um nível de abstração, não está escondendo os mesmos detalhes que disse que estão sendo obscurecidos pelo loop for? 2) não tenho certeza se testes parametrizados são aplicáveis ​​aqui. Parece que há paralelos aqui em algum lugar, mas já tive muitas situações semelhantes aos OPs em que tinha um conjunto de dados de 10 a 20 valores e só queria executá-los através do SUT. Sim, cada valor é diferente e potencialmente testa boudaries diferentes, mas parece vir "inventar" os nomes dos testes, pois cada valor seria um exagero. Eu encontrei relação de valor / code-tamanho ideal no uso semelhante ...
DXM
... rotações. Desde que o teste falhe, a declaração imprime exatamente o que falhou, o desenvolvedor tem feedback suficiente para identificar com precisão o problema.
DXM 23/01
@DXM 1) a estrutura de teste fornece a funcionalidade de teste parametrizado. Confiamos na estrutura de teste implicitamente, para não escrevermos testes para ela. 2) testes parametrizados são exatamente para esse propósito: você está executando exatamente os mesmos passos de cada vez, mas com valores diferentes de entrada / saída. A estrutura de teste poupa a necessidade de escrever nomes para cada um, executando as diferentes entradas pelo mesmo método de teste.
Benjamin Hodgson
5

Parece que você realmente deseja um teste de unidade orientado a dados. Desde que você mencionou o uso do QUnit, encontrei um plug-in que permite testes parametrizados:

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

Não há nada ideologicamente errado em um teste orientado a dados, desde que o código de teste em si não seja condicional. Olhando para o seu código de teste, ele parece ser um candidato muito bom para um teste orientado a dados.

Exemplo de código para o README do 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
fonte
1
Concordado, parece um teste orientado a dados. Mas parece que é isso que ele já tem em seu segundo exemplo de código.
Robert Harvey
1
@RobertHarvey - Correto. Existe um termo aceito para o que ele está tentando realizar, e existe um plug-in para a estrutura de teste que está sendo usada para facilitar a gravação desses tipos de testes. Eu pensei que valia a pena anotar em uma resposta para o futuro, só isso.
Greg Burghardt
1

Você está se repetindo menos usando a matriz que é mais sustentável. Uma abordagem que eu gosto de usar é ter um método separado que organize, atue e afirme os testes, mas aceite os parâmetros de entrada com os quais estou testando, por isso tenho 1 método de teste por conjunto de entradas.

Isso me permite dizer instantaneamente quais testes / entradas estão falhando.

Kevin
fonte
0

Eu gosto da sua segunda abordagem, mas gostaria de adicionar 2 pontos

  • não use matrizes para armazenar dados testados, pois trabalhar com índices não é uma maneira limpa
  • não use forloops

`

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

Não tenho certeza sobre o qunit, mas um bom executor de testes mostrará qual sequência de entrada falhou e qual foi o resultado esperado.

tenbits
fonte