Ao fazer testes de unidade da maneira "adequada", ou seja, apagar todas as chamadas públicas e retornar valores predefinidos ou zombarias, sinto que não estou testando nada. Estou literalmente olhando meu código e criando exemplos com base no fluxo da lógica por meio de meus métodos públicos. E toda vez que a implementação muda, preciso alterar esses testes novamente, sem realmente sentir que estou conseguindo algo útil (seja a médio ou longo prazo). Também faço testes de integração (incluindo caminhos não felizes) e realmente não me importo com o aumento do tempo de teste. Com isso, sinto que estou realmente testando regressões, porque elas capturaram várias, enquanto tudo o que os testes de unidade fazem é me mostrar que a implementação do meu método público mudou, o que eu já sei.
O teste de unidade é um tópico vasto e sinto que não entendo algo aqui. Qual é a vantagem decisiva do teste de unidade versus teste de integração (excluindo a sobrecarga de tempo)?
fonte
Respostas:
Parece que o método que você está testando precisa de várias outras instâncias de classe (que você precisa zombar) e chama vários métodos por conta própria.
Esse tipo de código é realmente difícil de testar por unidade, pelos motivos que você descreve.
O que eu achei útil é dividir essas classes em:
Então as classes de 1. são fáceis de testar por unidade, porque elas apenas aceitam valores e retornam um resultado. Em casos mais complexos, essas classes podem precisar executar chamadas por conta própria, mas chamarão apenas as classes a partir de 2. (e não chamarão diretamente, por exemplo, uma função de banco de dados), e as classes a partir de 2. são fáceis de zombar (porque apenas expor as partes do sistema agrupado necessárias).
As classes de 2. e 3. geralmente não podem ser significativamente testadas por unidade (porque elas não fazem nada útil por si mesmas, são apenas códigos de "cola"). OTOH, essas classes tendem a ser relativamente simples (e poucas), portanto devem ser adequadamente cobertas por testes de integração.
Um exemplo
Uma classe
Digamos que você tenha uma classe que recupera um preço de um banco de dados, aplica alguns descontos e atualiza o banco de dados.
Se você tiver tudo isso em uma classe, precisará chamar funções de banco de dados, difíceis de zombar. No pseudocódigo:
Todas as três etapas precisarão de acesso ao banco de dados, portanto, muitas zombarias (complexas), que provavelmente serão interrompidas se o código ou a estrutura do banco de dados mudar.
Dividir
Você divide em três classes: PriceCalculation, PriceRepository, App.
O PriceCalculation faz apenas o cálculo real e recebe os valores necessários. App une tudo:
Dessa maneira:
Por fim, pode acontecer que o PriceCalculation faça suas próprias chamadas ao banco de dados. Por exemplo, porque apenas o PriceCalculation sabe quais dados são necessários e, portanto, não pode ser buscado antecipadamente pelo aplicativo. Em seguida, você pode transmitir uma instância do PriceRepository (ou alguma outra classe de repositório), personalizada de acordo com as necessidades do PriceCalculation. Essa classe precisará ser ridicularizada, mas isso será simples, porque a interface do PriceRepository é simples, por exemplo
PriceRepository.getPrice(articleNo, contractType)
. Mais importante ainda, a interface do PriceRepository isola o PriceCalculation do banco de dados, portanto, é improvável que as alterações no esquema do banco de dados ou na organização de dados alterem sua interface e, portanto, quebrem as zombarias.fonte
Essa é uma falsa dicotomia.
O teste de unidade e o teste de integração têm dois objetivos semelhantes, mas diferentes. O objetivo do teste de unidade é garantir que seus métodos funcionem. Em termos práticos, os testes de unidade garantem que o código cumpra o contrato descrito pelos testes de unidade. Isso é evidente na maneira como os testes de unidade são projetados: eles especificam especificamente o que o código deve fazer e afirmam que o código faz isso.
Os testes de integração são diferentes. Os testes de integração exercitam a interação entre os componentes de software. Você pode ter componentes de software que são aprovados em todos os testes e ainda falham nos testes de integração porque eles não interagem corretamente.
No entanto, se houver uma vantagem decisiva nos testes de unidade, é esta: os testes de unidade são muito mais fáceis de configurar e exigem muito menos tempo e esforço do que os testes de integração. Quando usados adequadamente, os testes de unidade incentivam o desenvolvimento de código "testável", o que significa que o resultado final será mais confiável, mais fácil de entender e mais fácil de manter. O código testável possui certas características, como uma API coerente, comportamento repetível e retorna resultados fáceis de afirmar.
Os testes de integração são mais difíceis e mais caros, porque muitas vezes você precisa de zombaria elaborada, configuração complexa e afirmações difíceis. No nível mais alto de integração de sistemas, imagine tentar simular a interação humana em uma interface do usuário. Sistemas de software inteiros são dedicados a esse tipo de automação. E é a automação que estamos buscando; o teste em humanos não é repetível e não é dimensionado como o teste automatizado.
Finalmente, o teste de integração não garante a cobertura do código. Quantas combinações de loops de código, condições e ramificações você está testando com seus testes de integração? Você realmente sabe? Existem ferramentas que você pode usar com testes de unidade e métodos em teste que lhe dirão quanta cobertura de código você possui e qual é a complexidade ciclomática do seu código. Mas eles realmente funcionam bem no nível do método, onde vivem os testes de unidade.
Se seus testes estão mudando toda vez que você refatorar, isso é um problema diferente. Os testes de unidade devem documentar o que o software faz, provar que ele faz isso e depois provar que ele faz isso novamente quando você refatorar a implementação subjacente. Se sua API for alterada ou você precisar que seus métodos sejam alterados de acordo com uma alteração no design do sistema, é isso que deve acontecer. Se isso estiver acontecendo muito, considere escrever seus testes primeiro, antes de escrever o código. Isso forçará você a pensar na arquitetura geral e permitirá que você escreva código com a API já estabelecida.
Se você está gastando muito tempo escrevendo testes de unidade para códigos triviais como
então você deve reexaminar sua abordagem. O teste de unidade deve testar o comportamento e não há comportamento na linha de código acima. No entanto, você criou uma dependência em seu código em algum lugar, pois essa propriedade quase certamente será mencionada em outra parte do código. Em vez de fazer isso, considere escrever métodos que aceitem a propriedade necessária como parâmetro:
Agora, seu método não tem nenhuma dependência de algo fora de si e agora é mais testável, pois é completamente independente. Concedido, você nem sempre poderá fazer isso, mas ele move seu código na direção de ser mais testável e, desta vez, você está escrevendo um teste de unidade para o comportamento real.
fonte
Os testes de unidade com zombarias são para garantir que a implementação da classe esteja correta. Você zomba das interfaces públicas das dependências do código que está testando. Dessa forma, você tem controle sobre tudo o que é externo à classe e tem certeza de que um teste com falha ocorre devido a algo interno à classe e não a um dos outros objetos.
Você também está testando o comportamento da classe em teste e não a implementação. Se você refatorar o código (criando novos métodos internos, etc.), os testes de unidade não devem falhar. Mas se você estiver alterando o que o método público faz, absolutamente os testes devem falhar porque você mudou o comportamento.
Também parece que você está escrevendo os testes depois de escrever o código; tente escrever os primeiros testes. Tente descrever o comportamento que a classe deve ter e, em seguida, escreva a quantidade mínima de código para fazer os testes passarem.
O teste de unidade e o teste de integração são úteis para garantir a qualidade do seu código. Os testes de unidade examinam cada componente isoladamente. E os testes de integração garantem que todos os componentes interajam adequadamente. Eu quero ter os dois tipos no meu conjunto de testes.
Os testes de unidade me ajudaram no meu desenvolvimento, pois posso me concentrar em uma parte do aplicativo de cada vez. Zombando dos componentes que ainda não fiz. Eles também são ótimos para regressão, pois documentam quaisquer erros na lógica que eu encontrei (mesmo nos testes de unidade).
ATUALIZAR
A criação de um teste que apenas garanta que os métodos sejam chamados tem valor, pois você garante que os métodos realmente sejam chamados. Especialmente se você estiver escrevendo seus testes primeiro, você tem uma lista de verificação dos métodos que precisam acontecer. Como esse código é bastante processual, você não precisa verificar muito além dos métodos chamados. Você está protegendo o código para alterações no futuro. Quando você precisar chamar um método antes do outro. Ou que um método sempre seja chamado, mesmo que o método inicial gere uma exceção.
O teste para esse método nunca pode ser alterado ou pode ser alterado apenas quando você está alterando os métodos. Por que isso é uma coisa ruim? Ajuda a reforçar o uso dos testes. Se você precisar corrigir um teste após alterar o código, terá o hábito de alterar os testes com o código.
fonte
Eu estava enfrentando uma pergunta semelhante - até descobrir o poder dos testes de componentes. Em resumo, eles são iguais aos testes de unidade, exceto que você não zomba por padrão, mas usa objetos reais (idealmente via injeção de dependência).
Dessa forma, você pode criar rapidamente testes robustos com uma boa cobertura de código. Não há necessidade de atualizar suas zombarias o tempo todo. Pode ser um pouco menos preciso do que testes de unidade com zombarias 100%, mas o tempo e o dinheiro que você economiza compensam isso. A única coisa para a qual você realmente precisa usar zombarias ou acessórios é back-end de armazenamento ou serviços externos.
Na verdade, a zombaria excessiva é um antipadrão: os antipadrões e zombarias TDD são maus .
fonte
Embora a operação já tenha marcado uma resposta, estou apenas adicionando meus 2 centavos aqui.
E também em resposta a
Existe um OP útil, mas não exatamente o que o OP pediu:
Os testes de unidade funcionam, mas ainda existem erros?
da minha pouca experiência em conjuntos de testes, entendo que os testes de unidade devem sempre testar a funcionalidade mais básica do nível de método de uma classe. Na minha opinião, todo método público, privado ou interno merece ter testes de unidade dedicados. Mesmo em minha experiência recente, eu tinha um método público que chamava outro pequeno método privado. Portanto, havia duas abordagens:
Se você pensa logicamente, o objetivo de ter um método privado é: o principal método público está ficando muito grande ou bagunçado. Para resolver isso, você refatora sabiamente e cria pequenos trechos de código que merecem ser métodos privados separados que, por sua vez, tornam seu método público principal menos volumoso. Você refatora lembrando que esse método particular pode ser reutilizado posteriormente. Pode haver casos em que não há outro método público, dependendo desse método privado, mas quem sabe sobre o futuro.
Considerando o caso em que o método privado é reutilizado por muitos outros métodos públicos.
Portanto, se eu tivesse escolhido a abordagem 1: eu teria duplicado os testes de unidade e eles teriam sido complicados, pois você tinha um número de testes de unidade para cada ramo do método público e também do método privado.
Se eu tivesse escolhido a abordagem 2: o código escrito para testes de unidade seria relativamente menor e seria muito mais fácil de testar.
Considerando o caso em que o método privado não é reutilizado Não há sentido em escrever um teste de unidade separado para esse método.
Quanto aos testes de integração , eles tendem a ser exaustivos e mais de alto nível. Eles dirão a você que, dada uma entrada, todas as suas turmas devem chegar a esta conclusão final. Para entender mais sobre a utilidade do teste de integração, consulte o link mencionado.
fonte