Como devo testar a unidade de uma classe que depende de dados realistas?

8

Eu tenho uma classe que encapsula os resultados de uma medição científica. Estou desenvolvendo testes de unidade desde o início, mas não tenho muita experiência com testes de unidade e não tenho certeza de quais comportamentos devo testar e como.

Minha turma faz três tipos de coisas:

  1. Lê dados de medição de um arquivo (ou uma string) em suas variáveis ​​de instância
  2. Grava seus dados de medição em um arquivo ou string
  3. Executa cálculos em seus dados (por exemplo, obtendo a média de um conjunto de números)

Minha abordagem agora é incluir um arquivo de dados de exemplo em bom estado no meu testdiretório. Um teste lê os dados do arquivo, passa-os para a minha turma e garante que ele atenda a algumas verificações básicas de sanidade. Outro teste passa o nome do arquivo do arquivo para minha classe, permite que a classe o leia e executa os mesmos testes. O restante dos testes lê os dados do arquivo, passa-os para a minha classe e verifica se os resultados dos métodos de processamento de dados estão corretos, considerando o que sei sobre esse conjunto de dados.

Isso parece bastante confuso, no entanto. Os testes que verificam (3) implicitamente assumem que os comportamentos de (1) estão corretos, pois são as funções em (1) que estão sendo usadas para preencher a classe em primeiro lugar. E os testes de (1) podem se beneficiar das extensas verificações feitas pelos testes de (3). Estou estruturando mal meus testes de unidade ou isso é apenas um resultado natural do fato de eu precisar usar um conjunto de dados específico em meus testes?

Bdesham
fonte
2
Bem-vindo ao teste de integração. Sério, você está no caminho certo. Algumas considerações: se você quiser que seu projeto esteja sendo concluído rapidamente, tente a integração do big-bang. Se você deseja realmente descobrir o que há de errado com seu programa, agora e depois, continue metodicamente estabelecendo casos de teste para cada subsistema.
Andyz Smith

Respostas:

14

O que você está fazendo é um teste de integração, porque, como você provavelmente pode perceber, seus testes dependem de outras partes do seu código. Tudo bem, mas é bom saber quando você está vendo artigos / exemplos / etc. conectados.

Alguns pontos a considerar e coisas a serem lembradas:

  • Organizar , agir , afirmar . Todos os testes têm essas três etapas.
    • Precisa de mais etapas? Você provavelmente está testando demais para um teste
    • Não organizar? Você tem dependências externas, o que quase sempre torna os testes inadequados e não confiáveis.
    • Muitos códigos de organização geralmente significam muitas dependências e tipos de dependências no estado do sistema. Essa é uma receita para código frágil.
    • Nenhum ato? Frequentemente testa o compilador. Deixe isso para os desenvolvedores de compiladores.
    • Lotes de ato (s)? Você provavelmente está testando demais para 1 teste novamente.
    • Não afirmar? Geralmente, o caso em que você deseja testar se "nenhum erro ocorreu". Sem erros! = Funcionando corretamente.
    • Muitas afirmações? O código em teste pode estar fazendo muito / tocando em muitos sistemas. Tente refatorar e testar os bits individuais.
  • Os testes devem existir por conta própria. Evite cenários em que algo que teste 3 depende do código que faz 1 . Em vez disso, procure substituir o código que faz 1 pelo código de teste. No seu cenário: tente definir manualmente os valores para o que você deseja que eles estejam na etapa Organizar do teste , depois execute a Ação (os cálculos) e depois afirme o resultado.
  • Os testes do caminho feliz costumam ser uma perda de tempo. Os melhores cenários quase sempre funcionam. Seus testes devem estar tentando forçar erros e casos extremos.
    • Você tem um teste que passa fluxos ruins a serem processados?
    • Você tem um teste que passa nomes de arquivos nulos / inexistentes para serem processados?
    • O que acontece se os valores numéricos a serem calculados não forem numéricos, ou quando analisados, forem enormes , ou produzirem valores enormes quando calculados?
  • Surpreendentemente, escrever testes raramente confirma que o que você fez está certo. Em vez disso, fornecem informações sobre como o design é utilizável, estável e flexível.

editar> Isso já foi aceito, mas eu queria acrescentar algo que aprendi há muito tempo via Teste de unidade pragmática :

Teste de Unidade com seu BICEP Direito

  • Os resultados estão corretos ?
  • CORRETO B Condições oundary
    • C onform para o formato esperado
    • O rdered corretamente
    • R ange está correto
    • As referências a dependências externas são seguras
    • E Existência (nulo etc.)
    • C ardinalidade
    • Tempo (as coisas acontecem na ordem certa, com o tempo correto)
  • I nverse relações
  • C ross-Verifique os resultados com outros meios
  • Os espelhos E são forçados
  • As características de desempenho estão dentro dos limites aceitáveis
Steven Evers
fonte
5

Eu acho que é difícil testar sua turma porque tem muitas responsabilidades. Você mesmo os menciona

  • Ler dados do arquivo
  • Gravar dados no arquivo
  • Executar cálculo

Idealmente, uma classe deve ter uma única responsabilidade ou uma única área de responsabilidade. Nesse caso, você definitivamente deve ter uma classe contendo apenas a lógica para realizar os cálculos.

Se você tivesse isso, talvez você pudesse ter uma função como esta:

CalculationResult PerformCalculation(Measurement[] measurements)
{ ... }

Isso se torna relativamente fácil de testar, pois você pode fornecer facilmente uma série de medidas bem definidas em um teste.

Estou assumindo aqui que você leu o arquivo uma vez para obter todas as medidas de uma só vez. Se você coletar novas medidas gravadas no arquivo ao longo do tempo, sua classe poderá ficar assim:

class Calculator {
    AddMeasurement(Measurement measurement) { ... }

    CalculationResult PerformCalculation() { ... }
}

Ainda é fácil de testar, porque você pode alimentar a classe com uma série de medidas bem definidas durante o teste e ler o resultado, sem depender do sistema de arquivos.

Uma classe diferente pode ter a responsabilidade de ler os dados de medição do arquivo. Isso pode ser dividido novamente na leitura de dados de um arquivo e na análise dos dados como objetos de Medição, para permitir o teste da lógica de análise separadamente, sem dependência do sistema de arquivos etc.

Se for difícil escrever um teste de unidade, isso geralmente significa que sua "unidade" tem várias responsabilidades, muitas dependências ou de outras maneiras não é o SOLID .

Pete
fonte
3

O teste de unidade deve testar dois conjuntos de casos:

Os casos muito básicos: o cálculo está correto, os totais somados etc. Use dados simples e fáceis de verificar para esses casos.

Os casos extremos: data de entrega de 29 de fevereiro de 2016, quantidade da ordem de 999.999, itens com preço R $ 0,00, GPS para o Pólo Norte (tente se mudar para o oeste!) Etc. etc.

James Anderson
fonte
0

Eu acho que muitas das respostas estão no ponto, no sentido de que muitos dos testes essenciais parecem estar sendo combinados em uma instância de teste de unidade. MAS.

Muitas funções requerem dados complicados e produzem resultados complicados. O processamento de imagens, por exemplo, pode ter uma função compacta que produz uma máscara a partir de uma imagem. Um driver de teste (eu diria, apropriado) lê uma imagem, processa-a, escreve um arquivo de imagem e compara o arquivo resultante a um arquivo de imagem de referência.

Testar a integração no destino de toda essa funcionalidade seria um teste de integração. Testar uma única função do seu alvo, uma entrada complicada para uma saída complicada, é um teste de unidade apropriado.

sasguy
fonte