Pelo que entendi, o objetivo dos testes de unidade é testar unidades de código isoladamente . Isso significa que:
- Eles não devem quebrar nenhuma alteração de código não relacionada em nenhum outro local da base de código.
- Apenas um teste de unidade deve ser interrompido por um bug na unidade testada, em oposição aos testes de integração (que podem ser interrompidos em montões).
Tudo isso implica que toda dependência externa de uma unidade testada deve ser ridicularizada. E quero dizer todas as dependências externas , não apenas as "camadas externas", como rede, sistema de arquivos, banco de dados, etc.
Isso leva a uma conclusão lógica, que praticamente todos os testes de unidade precisam zombar . Por outro lado, uma rápida pesquisa no Google sobre zombaria revela vários artigos que afirmam que "zombar é um cheiro de código" e deve ser evitada na maior parte (embora não completamente).
Agora, para a pergunta (s).
- Como os testes de unidade devem ser escritos corretamente?
- Onde exatamente está a linha entre eles e os testes de integração?
Atualização 1
Por favor, considere o seguinte pseudo-código:
class Person {
constructor(calculator) {}
calculate(a, b) {
const sum = this.calculator.add(a, b);
// do some other stuff with the `sum`
}
}
Um teste que testa o Person.calculate
método sem zombar da Calculator
dependência (dado que Calculator
é uma classe leve que não acessa "o mundo exterior") pode ser considerado um teste de unidade?
fonte
Respostas:
Martin Fowler no teste de unidade
O que Kent Beck escreveu em Test Driven Development, por exemplo
Qualquer afirmação de "o ponto dos testes de unidade é" dependerá fortemente de qual definição de "teste de unidade" está sendo considerada.
Se sua perspectiva é que seu programa é composto de muitas unidades pequenas que dependem uma da outra, e se você se restringir a um estilo que testa cada unidade isoladamente, muitas duplas de teste são uma conclusão inevitável.
O conselho conflitante que você vê vem de pessoas que operam sob um conjunto diferente de suposições.
Por exemplo, se você estiver escrevendo testes para apoiar os desenvolvedores durante o processo de refatoração, e dividir uma unidade em duas é uma refatoração que deve ser suportada, então algo precisa dar. Talvez esse tipo de teste precise de um nome diferente? Ou talvez precisemos de um entendimento diferente de "unidade".
Você pode comparar:
Eu acho que é a pergunta errada a fazer; é novamente uma discussão sobre rótulos , quando acredito que realmente nos importamos com propriedades .
Quando estou introduzindo alterações no código, não me importo com o isolamento de testes - já sei que "o erro" está em algum lugar da minha pilha atual de edições não verificadas. Se eu executar os testes com freqüência, limite a profundidade dessa pilha e encontrar o erro é trivial (no caso extremo, os testes são executados após cada edição - a profundidade máxima da pilha é uma). Mas executar os testes não é o objetivo - é uma interrupção -, portanto, há valor em reduzir o impacto da interrupção. Uma maneira de reduzir a interrupção é garantir que os testes sejam rápidos ( Gary Bernhardt sugere 300ms , mas eu não descobri como fazer isso nas minhas circunstâncias).
Se a chamada
Calculator::add
não aumentar significativamente o tempo necessário para executar o teste (ou qualquer uma das outras propriedades importantes para este caso de uso), eu não me incomodaria em usar um teste duplo - ele não fornece benefícios que superam os custos .Observe as duas suposições aqui: um ser humano como parte da avaliação de custos e a pequena pilha de mudanças não verificadas na avaliação de benefícios. Nas circunstâncias em que essas condições não se mantêm, o valor do "isolamento" muda bastante.
Veja também Hot Lava , de Harry Percival.
fonte
Ao minimizar os efeitos colaterais no seu código.
Tomando seu código de exemplo, se,
calculator
por exemplo, falar com uma API da web, você pode criar testes frágeis que dependem da capacidade de interagir com essa API da web ou criar uma farsa. Se, no entanto, é um conjunto determinístico e livre de estado de funções de cálculo, você não (e não deve) zombar dele. Se o fizer, corre o risco de que o seu mock se comporte de maneira diferente do código real, causando bugs em seus testes.Zombarias só devem ser necessárias para o código que lê / grava no sistema de arquivos, bancos de dados, pontos de extremidade de URL, etc; dependentes do ambiente em que você está executando; ou que são de natureza altamente estável e não determinística. Portanto, se você mantiver essas partes do código no mínimo e ocultá-las atrás de abstrações, elas serão fáceis de zombar e o restante do código evitará a necessidade de zombarias.
Para os pontos de código que têm efeitos colaterais, vale a pena escrever testes que zombam e testes que não. Estes últimos precisam de cuidados, pois serão inerentemente frágeis e possivelmente lentos. Portanto, convém executá-los, digamos, da noite para o dia em um servidor de IC, e não sempre que você salvar e criar seu código. Os testes anteriores, porém, devem ser executados o mais rápido possível. Se cada teste é um teste de unidade ou integração, torna-se acadêmico e evita "guerras de chamas" sobre o que é e o que não é um teste de unidade.
fonte
R.equals
). Como essas funções são, na maioria das vezes, puras, geralmente não são ridicularizadas nos testes.Essas questões são bem diferentes em suas dificuldades. Vamos fazer a pergunta 2 primeiro.
Testes de unidade e testes de integração são claramente separados. Um teste de unidade testa uma unidade (método ou classe) e usa outras unidades apenas o necessário para atingir esse objetivo. Zombar pode ser necessário, mas não é o objetivo do teste. Um teste de integração testa a interação entre diferentes unidades reais . Essa diferença é toda a razão pela qual precisamos de testes de unidade e de integração - se um fez o trabalho do outro bem o suficiente, não precisaríamos, mas aconteceu que geralmente é mais eficiente usar duas ferramentas especializadas em vez de uma ferramenta generalizada .
Agora, a pergunta importante: como você deve fazer o teste de unidade? Como dito acima, os testes de unidade devem construir estruturas auxiliares apenas na medida do necessário . Muitas vezes, é mais fácil usar um banco de dados falso do que o seu banco de dados real ou mesmo qualquer banco de dados real. No entanto, zombar por si só não tem valor. Se muitas vezes acontece, é mais fácil usar componentes reais de outra camada como entrada para um teste de unidade de nível médio. Nesse caso, não hesite em usá-los.
Muitos profissionais temem que, se o teste de unidade B reutilizar as classes que já foram testadas pelo teste de unidade A, um defeito na unidade A cause falhas de teste em vários locais. Eu considero este não é um problema: um conjunto de testes tem de ter sucesso de 100% , a fim de dar-lhe a tranquilidade que você precisa, por isso não é um grande problema de ter muitas falhas - afinal, você faz tem um defeito. O único problema crítico seria se um defeito desencadeasse poucas falhas.
Portanto, não faça uma religião de zombaria. É um meio, não um fim; portanto, se você puder evitar o esforço extra, deve fazê-lo.
fonte
The only critical problem would be if a defect triggered too few failures.
esse é um dos pontos fracos da zombaria. Temos que "programar" o comportamento esperado para que possamos falhar, fazendo com que nossos testes terminem como "falsos positivos". Mas zombar é uma técnica muito útil para atingir o determinismo (a condição mais importante do teste). Eu os uso em todos os meus projetos, quando possível. Eles também me mostram quando a integração é muito complexa ou a dependência muito estreita.OK, então responda suas perguntas diretamente:
Como você diz, você deve estar zombando de dependências e testando apenas a unidade em questão.
Um teste de integração é um teste de unidade em que suas dependências não são zombadas.
Não. Você precisa injetar a dependência da calculadora nesse código e pode escolher entre uma versão simulada ou uma versão real. Se você usar um teste simulado, é um teste de unidade, se você usar um teste real, é um teste de integração.
No entanto, uma ressalva. você realmente se importa com o que as pessoas pensam que seus testes devem ser chamados?
Mas sua verdadeira pergunta parece ser a seguinte:
Acho que o problema aqui é que muitas pessoas usam zombarias para recriar completamente as dependências. Por exemplo, eu posso zombar da calculadora no seu exemplo como
Eu não faria algo como:
Eu diria que isso seria "testar meu mock" ou "testar a implementação". Eu diria " Não escreva Mocks! * Assim".
Outras pessoas discordariam de mim, iniciaríamos enormes guerras de chamas em nossos blogs sobre o melhor caminho para zombar, o que realmente não faria sentido a menos que você entendesse todo o contexto das várias abordagens e realmente não ofereça muito valor para alguém que só quer escrever bons testes.
fonte
MockCalc
aStubCalc
, e chamá-lo um esboço não um mock. martinfowler.com/articles/…Minha regra geral é que os testes de unidade adequados:
Se houver classes de utilitário de estruturas que complicam o teste de unidade, você pode até achar útil criar interfaces e classes "wrapper" muito simples para facilitar a zombaria dessas dependências. Esses invólucros não seriam necessariamente sujeitos a testes de unidade.
Eu achei essa distinção a mais útil:
Há uma área cinza aqui. Por exemplo, se você pode executar um aplicativo em um contêiner do Docker e executar os testes de integração como o estágio final de uma compilação e destruir o contêiner posteriormente, é aceitável incluir esses testes como "testes de unidade"? Se este é o seu debate ardente, você está em um bom lugar.
Não. Alguns casos de teste individuais serão para condições de erro, como passar
null
como parâmetro e verificar se você recebe uma exceção. Muitos testes como esse não exigem zombaria. Além disso, implementações que não têm efeitos colaterais, como processamento de sequência ou funções matemáticas, podem não exigir zombarias, porque você simplesmente verifica a saída. Mas acho que a maioria das classes exige, pelo menos, uma simulação em algum lugar do código de teste. (Quanto menos, melhor.)O problema "cheiro de código" que você mencionou surge quando você tem uma classe muito complicada, que requer uma longa lista de dependências simuladas para escrever seus testes. Essa é uma pista de que você precisa refatorar a implementação e dividir as coisas, para que cada classe tenha uma área menor e uma responsabilidade mais clara e, portanto, seja mais facilmente testável. Isso melhorará a qualidade a longo prazo.
Não acho que seja uma expectativa razoável, porque funciona contra a reutilização. Você pode ter um
private
método, por exemplo, chamado por váriospublic
métodos publicados por sua interface. Um bug introduzido nesse método pode causar várias falhas de teste. Isso não significa que você deve copiar o mesmo código em cadapublic
método.fonte
Não tenho muita certeza de como essa regra é útil. Se uma mudança em uma classe / método / qualquer coisa que possa quebrar o comportamento de outra no código de produção, as coisas são, na realidade, colaboradores e não relacionadas. Se seus testes forem interrompidos e o código de produção não, eles serão suspeitos.
Eu consideraria essa regra com suspeita também. Se você é realmente bom o suficiente para estruturar seu código e escrever seus testes de modo que um erro cause exatamente uma falha no teste de unidade, então você está dizendo que já identificou todos os possíveis erros, mesmo que a base de código evolua para casos de uso que você não antecipou.
Não acho que seja uma distinção importante. O que é uma 'unidade' de código de qualquer maneira?
Tente encontrar pontos de entrada nos quais você possa escrever testes que apenas 'façam sentido' em termos do domínio do problema / regras de negócios com as quais esse nível de código está lidando. Frequentemente, esses testes são de natureza um tanto "funcional" - inserem uma entrada e testam se a saída é a esperada. Se os testes expressam o comportamento desejado do sistema, eles geralmente permanecem bastante estáveis, mesmo quando o código de produção evolui e é refatorado.
Não leia muito sobre a palavra 'unidade' e incline-se a usar suas classes de produção reais em testes, sem se preocupar muito se estiver envolvendo mais de uma delas em um teste. Se um deles é difícil de usar (porque é preciso muita inicialização ou precisa atingir um banco de dados / servidor de e-mail real etc.), deixe seus pensamentos se tornarem zombadores / falsos.
fonte
Person:tellStory()
método que incorpora os detalhes de uma pessoa em uma string, então retorna isso, então a "história" é provavelmente uma unidade. Se eu criar um método auxiliar particular que retire parte do código, não acredito que tenha introduzido uma nova unidade - não preciso testá-lo separadamente.Primeiro, algumas definições:
Um teste de unidade testa as unidades isoladamente de outras unidades, mas o que isso significa não é definido concretamente por nenhuma fonte autorizada, portanto, vamos defini-lo um pouco melhor: se os limites de E / S forem cruzados (se a E / S é rede, disco, tela ou entrada da interface do usuário), há um local semi-objetivo para desenhar uma linha. Se o código depender de E / S, ele estará cruzando o limite de uma unidade e, portanto, precisará zombar da unidade responsável por essa E / S.
Sob essa definição, não vejo uma razão convincente para zombar de coisas como funções puras, o que significa que o teste de unidade se presta a funções puras, ou funções sem efeitos colaterais.
Se você deseja unidades de teste de unidade com efeitos, as unidades responsáveis pelos efeitos devem ser ridicularizadas, mas talvez você deva considerar um teste de integração. Portanto, a resposta curta é: "se você precisar zombar, pergunte a si mesmo se o que você realmente precisa é de um teste de integração". Mas há uma resposta melhor e mais longa aqui, e a toca do coelho é muito mais profunda. Zombar pode ser o meu cheiro de código favorito, porque há muito a aprender com eles.
Code Smells
Para isso, veremos a Wikipedia:
Continua mais tarde ...
Em outras palavras, nem todos os odores de código são ruins. Em vez disso, são indicações comuns de que algo pode não ser expresso em sua forma ideal e o cheiro pode indicar uma oportunidade para melhorar o código em questão.
No caso de zombaria, o cheiro indica que as unidades que parecem estar pedindo zombarias dependem das unidades a serem zombadas. Pode ser uma indicação de que não decompusemos o problema em partes atomicamente solucionáveis e que podem indicar uma falha de design no software.
A essência de todo o desenvolvimento de software é o processo de decompor um grande problema em partes menores e independentes (decomposição) e compor as soluções para formar um aplicativo que resolve o grande problema (composição).
A zombaria é necessária quando as unidades usadas para dividir o grande problema em partes menores dependem uma da outra. Dito de outra maneira, a zombaria é necessária quando nossas supostas unidades atômicas de composição não são realmente atômicas e nossa estratégia de decomposição falhou em decompor o problema maior em problemas menores e independentes a serem resolvidos.
O que faz com que zombar de um código cheira não é que exista algo inerentemente errado com zombaria - às vezes é muito útil. O que faz parecer um código é que ele pode indicar uma fonte problemática de acoplamento em seu aplicativo. Às vezes, remover essa fonte de acoplamento é muito mais produtivo do que escrever uma farsa.
Existem muitos tipos de acoplamentos, e alguns são melhores que outros. Entender que as zombarias são um cheiro de código pode ensiná-lo a identificar e evitar os piores tipos no início do ciclo de vida do design do aplicativo, antes que o cheiro se torne algo pior.
fonte
A zombaria só deve ser usada como último recurso, mesmo em testes de unidade.
Um método não é uma unidade, e mesmo uma classe não é uma unidade. Uma unidade é qualquer separação lógica de código que faz sentido, independentemente do que você chama. Um elemento importante de ter código bem testado é poder refatorar livremente, e parte de ser capaz de refatorar livremente significa que você não precisa alterar seus testes para fazer isso. Quanto mais você zomba, mais precisa alterar seus testes quando refatorar. Se você considerar o método a unidade, precisará alterar seus testes sempre que refatorar. E se você considerar a classe a unidade, precisará alterar seus testes toda vez que quiser dividir uma classe em várias classes. Quando você precisa refatorar seus testes para refatorar seu código, as pessoas optam por não refatorar o código, o que é a pior coisa que pode acontecer com um projeto. É essencial que você possa dividir uma classe em várias classes sem ter que refatorar seus testes, ou você terminará com aulas de espaguete de 500 linhas de tamanho grande. Se você está tratando métodos ou classes como suas unidades com teste de unidade, provavelmente não está fazendo Programação Orientada a Objetos, mas algum tipo de programação funcional mutante com objetos.
Isolar seu código para um teste de unidade não significa que você zomba de tudo que está fora dele. Se assim fosse, você teria que zombar da aula de matemática do seu idioma, e absolutamente ninguém acha que é uma boa ideia. Dependências internas não devem ser tratadas de maneira diferente das dependências externas. Você confia que eles são bem testados e funcionam como deveriam. A única diferença real é que, se suas dependências internas estão quebrando seus módulos, você pode parar o que está fazendo para corrigi-lo, em vez de precisar publicar um problema no GitHub e cavar em uma base de código que você não entende para corrigi-lo ou espero o melhor.
Isolar seu código significa apenas que você trata suas dependências internas como caixas-pretas e não testa as coisas que estão acontecendo dentro delas. Se você possui o Módulo B, que aceita entradas de 1, 2 ou 3, e o Módulo A, que o chama, você não tem seus testes para o Módulo A para cada uma dessas opções, basta escolher uma e usá-la. Isso significa que seus testes para o Módulo A devem testar as diferentes maneiras de tratar as respostas do Módulo B, não as coisas que você passa para ele.
Portanto, se o seu controlador passa um objeto complexo para uma dependência, e essa dependência faz várias coisas possíveis, talvez o salve no banco de dados e talvez retorne uma variedade de erros, mas tudo o que o seu controlador realmente faz é simplesmente verificar se ele retorna um erro ou não e repassar essas informações, tudo o que você testa em seu controlador é um teste para se ele retornar um erro e repassá-lo e um teste para se não retornar um erro. Você não testa se algo foi salvo no banco de dados ou que tipo de erro é o erro, porque isso seria um teste de integração. Você não precisa zombar da dependência para fazer isso. Você isolou o código.
fonte