A escrita eficaz testes de unidade com JavaScript YUI Teste

5 de janeiro de 2009 às 10:38 por Nicholas C. Zakas | Em Desenvolvimento | 6 Comentários

Um dos maiores under-the-radar movimentos em JavaScript desenvolvimento durante 2008 foi o ressurgimento do interesse em testes de unidade. YUI teste , framework YUI testes unitários, alcançou o status de GA em fevereiro e outras bibliotecas, quer introduzido os seus quadros próprios testes de unidade ou iniciados divulgando os já existentes. Como resultado, há muito mais documentação sobre a criação de testes unitários para JavaScript. Basta ter testes de unidade JavaScript não é suficiente, porém, se os testes são escritos de modo inadequado, podem levar a uma grande perda de tempo. Aprender a escrever testes de unidade efetiva JavaScript vai lhe poupar tempo e dores de cabeça no futuro.

O que você está testando?

A chave para escrever testes de unidade efetiva é entender a palavra "unidade". Em termos de testes, uma unidade é uma parte isolada de código que pode ser testada independente de outras partes do código. Em uma linguagem orientada a objetos, como JavaScript, cada método é considerado como uma unidade. Design OO adequada normalmente implica métodos bem encapsulados que servem um único propósito e são, portanto, fácil de testar.

Teste de unidade tradicional é projetado para testar a implementação de uma interface, de modo métodos privados não é testada de forma explícita. Isso é chamado de teste de caixa preta. A idéia é que você pode trocar a implementação de uma interface e os testes de unidade serão todos ainda passar porque eles são completamente agnóstica à implementação subjacente. Todos os testes sabe é um conjunto de restrições que devem ser cumpridos, pois eles não se importam como essas restrições são satisfeitas.

Testes de escrita

Como eu disse em minha palestra, testes unitários devem testar as entradas e saídas. As entradas podem ser nomeados argumentos de método ou mudanças nas variáveis ​​globalmente acessível que o método depende para funcionar corretamente. As saídas podem ser valores de retorno, as mudanças no estado de variáveis, e os erros, mesmo lançada. Para cada conjunto de insumo-produto, deve haver um teste de unidade única. Cada teste deve declarar explicitamente ", dado estas entradas, espero que estas saídas." Qualquer desvio do que a declaração é um teste que falhou.

Cada teste deve ser o mais simples possível e testar apenas um conjunto de insumo-produto; combinando conjuntos em um único teste minimiza a eficácia do teste de unidade. Por exemplo, considere o seguinte teste de uma função chamada trim() :

 var TestCase = new YAHOO.tool.TestCase ({
     name: "trim () Testa",
     testTrim: function () {
         var result1 = trim ("Olá mundo");
         YAHOO.util.Assert.areEqual ("Olá mundo", result1, "Leading espaço em branco deve ser despojado.");
         var result2 = trim ("Olá mundo");
         YAHOO.util.Assert.areEqual ("Olá mundo", result2, "espaços em branco devem ser retirados.");
     }
 }); 

Aqui, o testTrim() método do caso de teste é realmente testar dois diferentes conjuntos de insumo-produto:

  1. Cadeia de entrada tem líder espaço em branco; valor de retorno não tem espaço em branco.
  2. Cadeia de entrada tem espaços em branco; valor de retorno não tem espaços em branco.

O problema é que esses dois conjuntos têm, literalmente, nenhuma relação com o outro, mas se o conjunto de input-output primeiro deixa de produzir o resultado correto, o segundo nunca vai ser testada. Esta é uma situação em que uma falha de máscaras outro. É mais eficaz para separar esses conjuntos de entrada-saída em dois testes:

  var TestCase = new YAHOO.tool.TestCase ({
     name: "trim () Testa",
     testTrimWithLeadingWhiteSpace: function () {
         var result = trim ("Olá mundo");
         YAHOO.util.Assert.areEqual ("Olá mundo" resultado, "Leading espaço em branco deve ser despojado.");
     },
     testTrimWithTrailingWhiteSpace: function () {
         var result = trim ("Olá mundo");
         YAHOO.util.Assert.areEqual ("Olá mundo" resultado, "espaços em branco devem ser retirados.");
     }
 }); 

Este código agora devidamente testa o trim() função de entrada-saída conjuntos, mantendo-os separados.

Testes de unidade são sempre escritas como se o código que está sendo testado funciona corretamente. Bom design de software envolve mapear estes conjuntos de insumo-produto com antecedência para que você saiba exatamente o que o resultado deve ser em cada caso. Desta forma, testes de unidade tornar-se um tipo de documento de requisitos técnicos, além de código real.

Afirmações eficazes

Uma das partes mais importantes de escrever testes de unidade é a definição adequada afirmação. Cada afirmação especifica uma condição que, se não cumpridas, indica que a funcionalidade não está se comportando adequadamente. É importante usar apenas como afirmações quantas forem necessárias para testar adequadamente a saída de código. Afirmações demais podem levar a falhas falso enquanto muito poucos podem levar a falsos passes.

No exemplo anterior, cada teste contém uma afirmação simples, porque isso é tudo que é necessário. Eu sei exatamente o valor que a ser devolvido e assim que eu testar especificamente para isso. Os testes podem ambos parecem muito simples, mas fazer o trabalho. Novamente, não há nenhuma regra sobre o número de afirmações que fazem um bom teste, apenas certifique-se que você está testando a cada saída esperada do código para a entrada dada.

Para tornar mais coerente falhas nos testes, você deve incluir uma mensagem de falha com cada afirmação. Teste em YUI, este é sempre o último argumento de qualquer método afirmação. A mensagem de falha deve dizer-lhe que deveria ter acontecido, não o que aconteceu. Alguns exemplos:

 / / Falha Bad mensagem
 YAHOO.util.Assert.areEqual ("Olá mundo", resultado, "O resultado não foi" Olá mundo ");

 / / Falha Boa mensagem
 YAHOO.util.Assert.areEqual ("Olá mundo" resultado, "Leading espaço em branco deve ser despojado.");

Observe a diferença entre as mensagens de falha maus e bons: o mau lhe diz o que aconteceu e para o bem lhe diz o que era esperado. Ao executar seus testes, uma falha já indica que algo inesperado aconteceu, portanto não há necessidade de simplesmente repetir que algo inesperado aconteceu. É mais útil saber o que deveria ter acontecido porque é uma representação exata de sua exigência. Ao adotar essa abordagem, as falhas acabam sendo uma lista de exigências não cumpridas que você pode ir para trás sobre e avaliar.

Trabalhando com o DOM

JavaScript é exclusivo para outras línguas em que ele freqüentemente tem vínculos com o ambiente, o DOM. Métodos que interagem fortemente com o DOM são difíceis de teste de unidade, porque todo o ambiente deve ser configurado para que o método para executar completamente. Para complicar ainda mais é a tendência de JavaScript para ser desencadeada por uma ação do usuário como um clique do mouse. YUI teste fornece simulação de eventos para ajudar na criação de testes para métodos que são dependentes de interação DOM, no entanto, isso começa a cruzar para a área de testes funcionais.

Testes funcionais, ao invés de teste de unidade, foi projetado para testar a experiência do usuário com o produto, em vez de input-output sets para o código. Se você está querendo testar se a interface de usuário responde de uma maneira específica, devido à interação do usuário, então você realmente quer escrever alguns testes funcionais, em vez de testes de unidade. YUI teste pode ser usado para escrever alguns testes funcionais básicos, mas a mais popular ferramenta (e muito bom) para tais testes é Selenium .

A melhor maneira de determinar se algo é um teste de unidade é de perguntar se ele pode ser escrito antes do código que ele é projetado para testar realmente existe. Testes de unidade, como parte de test-driven development, são na verdade deveria estar escrito antes do código atual, como forma de orientar os esforços de desenvolvimento. Testes funcionais, por outro lado, não pode existir antes do tempo porque eles são tão ligados à interface do usuário e como isso muda em resposta à interação do usuário.

Estruturação hierarquias teste

YUI Test, assim como outras estruturas de teste da unidade, suporta uma hierarquia de casos de teste e suites de teste. Cada conjunto de teste pode conter suites outro teste, bem como casos de teste; casos de teste só pode conter testes reais (métodos que começam com a palavra "teste"). A melhor maneira de organizar sua hierarquia de teste é seguir um padrão muito simples:

  • Criar um conjunto de testes para cada objeto que você está indo para testar.
  • Criar um caso de teste para cada método de um objeto que você está indo para testar e adicioná-lo ao conjunto do objeto de teste.
  • Criar um teste em cada caso de teste para cada conjunto de insumo-produto.

Desta forma, sua hierarquia de teste espelha o código que você está testando e é mais fácil descobrir onde novos testes devem ser criados.

Executar os testes!

Talvez a parte mais importante de testes de unidade é executar os testes com freqüência. Teste só é eficaz quando feito em uma base regular. No mínimo, você deve estar executando os testes de unidade antes de fazer check-in alterações ao controle de origem. Idealmente, você também executar os testes automaticamente em uma base regular para validar as alterações depois de terem sido cometidos ao controle de origem. Isto é como você vai ter o maior benefício dos testes de unidade: o discernimento rápido, e espero que a prevenção, de regressões.

Mais informações

Compartilhar e ampliar: Bookmark with del.icio.us | digg it! | reddit!

6 Comentários

  1. São os testes YUI executado automaticamente em uma base regular? Se você também pode compartilhar qualquer informação sobre como fazer isso? Estou particularmente interessado em saber como você lida com a desova e fechar o browser processos utilizados para executar os testes no durante uma compilação automatizada, e também como você coletar e agregar os resultados dos testes para a compilação.

    Comentário por Simon - 05 de janeiro de 2009 #

  2. A melhor solução que eu vi thusfar é usar Selenium para gerenciar o ciclo de vida do navegador para o teste. Você pode configurá-lo para ser executado periodicamente e então monitorar a página para os resultados.

    Comentário por Nicholas C. Zakas - 7 de janeiro de 2009 #

  3. Primeiro de tudo, obrigado por escrever este blog maravilhoso. Gostaria de comentar sobre o seguinte:

    Em uma linguagem orientada a objetos, como JavaScript, cada método é considerado uma unidade

    Pessoalmente, prefiro pensar de uma classe como uma unidade. A classe fornece-me um pouco de comportamento e eu quero explicar que o comportamento através de meus testes. Indo a um nível método parece que estamos de perfuração para a implementação em algum sentido.

    testes de unidade devem testar as entradas e saídas

    Eu preferiria se o que você qualifica o seu estilo de teste de unidade como o estado de testes de unidade base. Há uma escola diferente de teste de unidade que acredita em testes de interação baseado. Se a sua não tanto sobre a saída em termos de valores de retorno, as mudanças no estado de variáveis, ou erros lançada. O foco está na interação (chamadas de método) com o seu colaboradores.

    Teste em YUI, mensagem de falha é sempre o último argumento de qualquer método afirmação

    Eles parecem ter quebrado a convenção xUnit. na mensagem de falha xUnit é sempre o primeiro argumento.

    Você afirma:

    //Bad failure message
    YAHOO.util.Assert.areEqual("Hello world", result, "The result wasn't 'Hello world'");

    / / Falha Boa mensagem
    YAHOO.util.Assert.areEqual ("Olá mundo" resultado, "Leading espaço em branco deve ser despojado.");

    Enquanto eu concordo com você que o segundo exemplo é melhor do que o primeiro, mas acho que a mensagem de falha no segundo caso, é redundante. Se você olhar para o método de ensaio inteiro abaixo,

    testTrimWithLeadingWhiteSpace: function(){
    var result = trim(" Hello world");
    YAHOO.util.Assert.areEqual("Hello world", result, "Leading white space should be stripped.");
    }

    O nome do método já diz que líder espaço em branco deve ser aparado ou retirados. Neste caso específico eu não daria qualquer mensagem de falha. Se as melhores práticas da sua empresa o seu para dar uma, então eu preferiria a sua mensagem de falha como "espaço em branco à esquerda não foi despojado"

    Testes funcionais, por outro lado, não pode existir antes do tempo porque eles são tão ligados à interface do usuário e como isso muda em resposta à interação do usuário.

    Isso nem sempre é verdade. Em muitos casos você pode realmente escrever seus testes funcionais ou de aceitação antes de criar qualquer código. Referem-se a aceitação Test Driven Development

    Criar um conjunto de testes para cada objeto que você está indo para testar

    Por isso é importante? Eu não vejo como isso vai ajudar?

    Criar um caso de teste para cada método de um objeto que você está indo para testar e adicioná-lo ao conjunto do objeto de teste.

    Eu preferiria criar uma classe de teste por entidade importante no meu código de produção e escrever um método de teste por comportamento cumpre classe. Às vezes eu poderia ter mais de um teste para um método e às vezes nenhum. Eu não tenho certeza se eu iria criar uma diretriz dizendo cada método deve ter um teste.

    Comentário por Naresh Jain - 11 jan 2009 #

  4. Oi Naresh,

    Obrigado por seus comentários perspicazes. De fato, existem muitas escolas de pensamento em torno metodologia de teste, e você tocou em alguns deles. Eu apenas gostaria de esclarecer alguns pontos.

    Tornando a mensagem falha de asserção uma declaração de que deveria estar acontecendo não é redundante, é explicativo do que aconteceu. O nome do teste e da mensagem de falha podem ser semelhantes, e neste caso são, pois o teste é tão simples, no entanto, eles podem ser muito diferentes se houver um certo número de afirmações contidas no teste.

    Minha recomendação para a criação de suites e casos de teste é fazer com que as pessoas começaram. Eu não estou dizendo que todos os métodos devem ter um teste, eu estou dizendo que todos os métodos devem ter um caso de teste que contém testes que exploram cada conjunto de insumo-produto que você tem. O ponto é ter uma estrutura lógica que mapeia diretamente para os objetos que você está programando.

    Espero que este esclarece alguns dos pontos; obrigado por seus comentários.

    Comentário por Nicholas C. Zakas - 12 de janeiro de 2009 #

  5. Oi Nicholas,
    Recentemente comecei a explorar yuitest para testes unitários de código JS em um portal existente que herdamos de apoio e já está usando alguma biblioteca yui 2. obrigado por todos os vídeos e blogs que você colocar para fora.
    1. há exemplos / amostras de yuitest sendo usado em cenários mais prático / realista como diferentes contextos, extensa dom exemplos usgae etc no local yuitest são muito simplistas.
    2. razão que eu peço é porque o conteúdo das páginas é geralmente dinâmico e se baseia em algumas pré-condições .. é o elemento de contêiner muda mesmo, mas de conteúdo. é aconselhável stub htmls diferentes para cada cenário?
    3. poderia haver alguns scripts que utilizam grande quantidade de DOM. deve html inteiro como html original ser apagados em tais casos.

    Comentário por kaanta - 08 de dezembro de 2010 #

  6. Oi Kaanta,

    Todos os exemplos que eu posso compartilhar estão atualmente na documentação ou incluído como parte do download YUI (verifique o diretório de testes no arquivo ZIP).

    Se você está testando um monte de interação em uma página, então eu costumo sugerir carregamento Teste YUI para a página real e executar os testes lá. Dessa forma, você não precisa se preocupar com apagando algumas peças da página para que as coisas funcionem.

    Comentário por Nicholas C. Zakas - 08 de dezembro de 2010 #

Desculpe, o formulário de comentários está fechado no momento.

Hosted by Yahoo!

Copyright © 2006-2012 Yahoo! Inc. Todos os direitos reservados. Política de Privacidade - Termos de Serviço

Alimentado por WordPress sobre Yahoo! Web Hosting .