Unidade de teste de componentes internos


14

Até que ponto você testou os componentes internos / privados de uma classe / módulo / pacote / etc? Você os testa ou apenas testa a interface para o mundo exterior? Um exemplo desses métodos internos são os privados.

Como exemplo, imagine um analisador de descida recursiva , que possui vários procedimentos internos (funções / métodos) chamados de um procedimento central. A única interface para o mundo externo é o procedimento central, que pega uma string e retorna as informações analisadas. Os outros procedimentos analisam partes diferentes da cadeia e são chamados do procedimento central ou de outros procedimentos.

Naturalmente, você deve testar a interface externa chamando-a com sequências de amostra e comparando-a com a saída analisada à mão. Mas e os outros procedimentos? Você os testaria individualmente para verificar se eles analisam seus substrings corretamente?

Eu posso pensar em alguns argumentos:

Prós :

  1. Mais testes são sempre melhores e isso pode ajudar a aumentar a cobertura do código
  2. Alguns componentes internos podem ser difíceis de fornecer entradas específicas (casos extremos, por exemplo) fornecendo entradas para a interface externa
  3. Teste mais claro. Se um componente interno tiver um bug (corrigido), um caso de teste para esse componente deixará claro que o bug estava naquele componente específico

Contras :

  1. A refatoração se torna muito dolorosa e demorada. Para alterar qualquer coisa, é necessário reescrever os testes de unidade, mesmo que os usuários da interface externa não sejam afetados.
  2. Algumas linguagens e estruturas de teste não permitem

Quais são as suas opiniões?


possível duplicação ou sobreposição significativa com: programmers.stackexchange.com/questions/10832/…
azheglov

Respostas:


8

Caso: um "módulo" (em um sentido amplo, isto é, algo que possui uma interface pública e possivelmente também algumas partes internas privadas) possui uma lógica complicada / envolvida dentro dele. Testar apenas a interface do módulo será uma espécie de teste de integração em relação à estrutura interna do módulo e, portanto, caso seja encontrado um erro, esse teste não localizará a parte / componente interno exato que é responsável pela falha.

Solução: transforme as partes internas complicadas em módulos, faça um teste de unidade (e repita essas etapas se forem muito complicadas) e importe para o módulo original. Agora você tem apenas um conjunto de módulos simples o suficiente para testar uniit (ambos verificam se o comportamento está correto e corrigem erros) facilmente, e isso é tudo.

Nota:

  • não haverá necessidade de alterar nada nos testes dos "submódulos" anteriores do módulo ao alterar o contrato do módulo, a menos que o "submódulo" não ofereça mais serviços suficientes para cumprir o contrato novo / alterado.

  • nada será desnecessariamente tornado público, ou seja, o contrato do módulo será mantido e o encapsulamento mantido.

[Atualizar]

Para testar alguma lógica interna inteligente nos casos em que é difícil colocar as partes internas do objeto (refiro-me aos membros e não aos módulos / pacotes importados em particular) no estado apropriado, basta alimentar as entradas por meio da interface pública do objeto:

  • basta ter algum código de teste com acesso amigo (em termos de C ++) ou pacote (Java) às entranhas, definindo o estado de dentro e testando o comportamento como você deseja.

    • isso não interromperá o encapsulamento novamente, proporcionando fácil acesso direto aos internos para fins de teste - basta executar os testes como uma "caixa preta" e compilá-los nas versões de compilação.

e o layout da lista parece estar um pouco quebrado; (
mlvljr

1
Boa resposta. No .NET, você pode usar o [assembly: InternalsVisibleTo("MyUnitTestAssembly")]atributo no seu AssemblyInfo.cspara testar internos. Parece trapaça embora.
Ninguém

@rmx Uma vez que algo satisfaz todos os critérios necessários, não há trapaça, mesmo que tenha algo em comum com as fraudes reais. Mas o tópico do acesso inter / intra-módulo é realmente um pouco artificial nas linguagens mainstream modernas.
mlvljr

2

A abordagem do código baseado no FSM é um pouco diferente da usada tradicionalmente. É muito parecido com o que é descrito aqui para testes de hardware (que geralmente também é um FSM).

Em resumo, você cria uma sequência de entrada de teste (ou um conjunto de seqüências de entrada de teste) que deve não apenas produzir uma determinada saída, mas também ao produzir uma saída "ruim" específica, permitindo identificar o componente com falha pela natureza da falha. A abordagem é bastante escalável, quanto mais tempo você gasta no design do teste, melhor será o teste.

Esse tipo de teste está mais próximo do que é chamado de "testes funcionais", mas elimina a necessidade de alterar os testes toda vez que você toca levemente em uma implementação.


2

Bem, isto depende :-). Se você está seguindo uma abordagem BDD (Behavior Driven Development) ou ATDD (Acceptance Test Driven Development), testar a interface pública é bom (desde que você a teste exaustivamente com entradas variadas. A implementação subjacente, por exemplo, métodos privados, não é realmente importante.

No entanto, digamos que você queira que parte desse algoritmo seja executado dentro de um determinado período de tempo ou ao longo de uma certa curva bigO (por exemplo, nlogn) e sim testando as partes individuais. Alguns chamariam isso de uma abordagem tradicional de TDD / Teste de Unidade.

Como em tudo, YMMV


1

Dividi-lo em várias partes com um significado funcional, por exemplo ParseQuotedString(), ParseExpression(), ParseStatement(), ParseFile()e torná-los todos os públicos. Qual a probabilidade de sua sintaxe mudar tanto que elas se tornem irrelevantes?


1
essa abordagem leva facilmente a um encapsulamento mais fraco e a interfaces maiores e mais difíceis de usar / entender.
Sara
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.