que tipo de funções e / ou classes são impossíveis de realizar testes unitários e por que

21

A principal desculpa do desenvolvedor por não ter um bom teste de unidade é "O código não foi projetado de maneira testável por unidade". Estou tentando entender que tipo de design e código não pode ser testado em unidade.

manizzzz
fonte
2
Sua desculpa? A colegas de trabalho? Gerentes? Com qual idioma / estruturas você está trabalhando?
1
Muito código legado no aplicativo e sem tempo para reprojetar.
Knut
4
@gnat: Eu discordo. A pergunta que você citou é sobre situações em que os testes de unidade não são úteis. A pergunta atual é sobre situações que dificultam os testes de unidade.
Arseni Mourzenko
@ MainMa, aparentemente, lemos perguntas diferentes. "Estou tentando entender que tipo de design e código não pode ser testado em unidade". => "Quando é apropriado não testar a unidade?"
precisa
1
@ manizzzz: você pode querer editar isso na pergunta.
jmoreno

Respostas:

27

Vários fatores podem tornar o código difícil de testar na unidade. Quando este for o caso, a refatoração ajuda a melhorar o código para que seja testável.

Alguns exemplos de código que provavelmente seriam difíceis de testar:

  • Uma função 1000-LOC,
  • Código que depende fortemente do estado global,
  • Código que requer concreto, dificulta a construção de objetos, como o contexto do banco de dados, em vez de depender de interfaces e injeção de dependência,
  • Código que executa lentamente ,
  • Código de espaguete,
  • Código legado que foi modificado por anos sem se preocupar com legibilidade ou manutenção,
  • Difícil de entender código que não tem comentários ou dicas sobre a intenção original do autor (por exemplo, código que usa nomes de variáveis ​​como function pGetDp_U(int i, int i2, string sText).

Observe que a falta de arquitetura clara não torna o código difícil para o teste de unidade, pois os testes de unidade dizem respeito a pequenas partes do código. A arquitetura pouco clara ainda teria um impacto negativo na integração e nos testes do sistema.

Arseni Mourzenko
fonte
8
Também é difícil de código de teste que não injeta dependências em funções não-puros, como números aleatórios, o tempo atual, hard-wired I / O, etc.
9000
é trivial testar código assim - você só precisa das ferramentas de teste corretas, não para alterar seu código de acordo com elas. Experimente o Microsoft Fakes para um exemplo.
Gbjbaanb
@ MainMa, eu gosto desta resposta. Você também gostaria de comentar um pouco sobre quais fatores levam os testes diferentes à integração e aos testes do sistema? Eu sei que a razão pela qual eu fiz perguntas semelhantes à do passado é que eu não tinha um roteiro explicando quais tipos de testes são melhor colocados onde (ou talvez os mais econômicos onde) - pensei os testes unitários foram os únicos.
J Trana
14

Há muitas coisas que tornam o código difícil para o teste de unidade. Coincidentemente, muitos deles também dificultam a manutenção do código:

  • Violações da Lei de Deméter .
  • Criando objetos dentro de um método em vez de injetar dependências .
  • Acoplamento apertado.
  • Coesão fraca.
  • Depende fortemente de efeitos colaterais.
  • Depende fortemente de globais ou singletons.
  • Não expõe muitos resultados intermediários. (Uma vez eu tive que testar unitariamente uma função matemática de dez páginas com uma única saída e nenhum resultado intermediário disponível. Meus antecessores basicamente codificaram o código independentemente da resposta que o código deu).
  • Depende fortemente e diretamente de serviços difíceis de zombar, como bancos de dados.
  • O ambiente de tempo de execução é significativamente diferente do ambiente de desenvolvimento, como um destino incorporado.
  • Unidades disponíveis apenas na forma compilada (como uma DLL de terceiros).
Karl Bielefeldt
fonte
Eu acho que essa é uma excelente resposta. Você toca em muitos problemas no nível do código e no estado global. O @MaainMa tem outros problemas que eu acho válidos, mas menos bem definidos. Jeffery Thomas menciona E / S e UI. Acho que se você adicionar as partes boas dessas três respostas, terá uma ótima resposta coesa. Eu gosto mais desta resposta por causa do foco nos antipatterns de código.
M2tM
1
Argh - nada pior do que o teste de unidade afirma que não tem nenhuma semelhança com os requisitos de negócios e são apenas a saída em um determinado tempo - zombarias que são configuradas para serem chamadas três vezes, por exemplo? Por que 3? Porque era 3 pela primeira vez o teste foi executado / discurso :)
Michael
O acoplamento apertado só é ruim quando é inapropriado. É necessário um acoplamento rígido no código que seja altamente coeso. Por exemplo, uma declaração de variável seguida por seu uso. Bem acoplado, altamente coeso.
dietbuddha
1
Os testes de unidade em que a saída é verificada em relação ao que o código fez, sem nenhum caso / justificativa comercial, são chamados de Testes de Caracterização. Eles são usados ​​em manutenção onde anteriormente não havia testes e, muitas vezes, não há requisitos documentados, e você deve colocar algo que irá quebrar se a saída dessa função mudar. Eles são melhores que nada.
Andy Krouwel
5

Exemplos comuns de código que as pessoas não desejam para o teste de unidade:

  • Código que interage diretamente com a E / S (leitura de arquivos, chamadas diretas de rede, ...).
  • Código que atualiza diretamente a interface do usuário.
  • Código que referencia diretamente singletons ou objetos globais.
  • Código que altera implicitamente o estado do objeto ou subobjeto.

Usando uma estrutura simulada, todos esses exemplos podem ser testados em unidade. É apenas um trabalho para configurar as substituições simuladas para as dependências internas.

Coisas que realmente não podem ser testadas em unidade:

  • Loops infinitos (para um gerenciador de threads, driver ou algum outro tipo de código de execução longa)
  • Certos tipos de operações diretas de montagem (suportadas por alguns idiomas)
  • Código que requer acesso privilegiado (não é impossível, apenas não é uma boa ideia)
Jeffery Thomas
fonte
2

Existem algumas áreas que podem dificultar a gravação de testes de unidade. No entanto, enfatizo que isso não significa que você deva desconsiderar técnicas úteis simplesmente porque elas podem adicionar alguma complexidade ao seu teste. Como em qualquer codificação, você deve fazer sua própria análise para determinar se os benefícios ultrapassam os custos, e não aceitar cegamente o que um cara aleatório publica na rede.

Escrito mal do código projetado

  • acoplamento inadequado (geralmente acoplamento apertado onde não deveria estar)
  • código da pia da cozinha (onde uma função tem muita lógica / responsabilidades)

Confiança do estado em um escopo diferente

O custo para a maioria deles fica fora de controle, a menos que você saiba o que está fazendo. Infelizmente, muitas vezes não sabem como usar essas técnicas de maneira a mitigar coisas como testar a complexidade.

  • Singletons
  • Globals
  • Encerramentos

Estado externo / do sistema

  • Dependências de hardware / dispositivo
  • Dependências de rede
  • Dependências do sistema de arquivos
  • Dependências entre processos
  • Outras dependências de chamada do sistema

Concorrência

  • Rosqueamento (bloqueios, seções críticas, etc.)
  • bifurcação
  • Coroutines
  • Retornos de chamada
  • Manipuladores de sinal (não todos, mas alguns)
dietbuddha
fonte
2

Não existe código que não possa ser testado. No entanto, existem alguns exemplos de código que são REALMENTE, REALMENTE difíceis de testar (a ponto de possivelmente não valer o esforço):

Interações de hardware - Se o código manipular diretamente o hardware (por exemplo, gravar em um registro para mover um dispositivo físico), o teste de unidade poderá ser muito difícil ou caro. Se você usa hardware real para o teste, pode ser caro obter feedback apropriado no equipamento de teste (ainda mais equipamentos!) E, caso contrário, é necessário emular o comportamento exato dos objetos físicos - não é um truque fácil. algumas instâncias.

Interações do relógio - isso geralmente é mais fácil, porque quase sempre é possível zombar das funções do relógio do sistema de maneira bastante trivial. Mas quando você não pode, esses testes se tornam incontroláveis ​​- testes baseados em tempo real tendem a demorar muito tempo para serem executados, e, na minha experiência, eles tendem a ser muito frágeis, pois as cargas do sistema tornam as coisas mais demoradas do que deveriam , causando falhas no teste fantasma.

Michael Kohne
fonte
0

Meus três principais grupos para isso são:

  • código que depende de serviços externos

  • sistemas que não permitem que os testadores modifiquem o estado independentemente do aplicativo.

  • ambientes de teste que não replicam a configuração de produção.

Isso é o que eu mais experimentei como desenvolvedor que virou engenheiro de controle de qualidade.

Michael Durrant
fonte