Estou testando que uma função faz o que se espera em uma lista. Então eu quero testar
f(null) -> null
f(empty) -> empty
f(list with one element) -> list with one element
f(list with 2+ elements) -> list with the same number of elements, doing what expected
Para fazer isso, qual é a melhor abordagem?
- Testando todos os casos no mesmo teste (método), sob o nome "WorksAsExpected"
- Colocando um teste para cada caso, tendo assim
- "WorksAsExpectedWhenNull"
- "WorksAsExpectedWhenEmpty"
- "WorksAsExpectedWhenSingleElement"
- "WorksAsExpectedWhenMoreElements"
- Outra opção que eu não estava pensando :-)
unit-testing
tdd
malarres
fonte
fonte
Respostas:
A regra geral simples que eu uso para executar um conjunto de testes em um caso de teste ou em muitos é: envolve apenas uma configuração?
Portanto, se eu estivesse testando que, para vários elementos, ele processou todos eles e obteve o resultado correto, posso ter duas ou mais declarações, mas preciso configurar a lista apenas uma vez. Então, um caso de teste está bom.
No seu caso, porém, eu teria que configurar uma lista nula, uma lista vazia etc. Isso é várias configurações. Então, eu definitivamente criaria vários testes neste caso.
Como outros já mencionaram, esses "múltiplos testes" podem existir como um único caso de teste parametrizado; ou seja, o mesmo caso de teste é executado em uma variedade de dados de configuração. A chave para saber se essa é uma solução viável está nas outras partes do teste: "ação" e "afirmação". Se você pode executar as mesmas ações e afirmações em cada conjunto de dados, use esta abordagem. Se você se encontra adicionando
if
s, por exemplo, para executar código diferente em diferentes partes desses dados, essa não é a solução. Use casos de teste individuais nesse último caso.fonte
Há uma troca. Quanto mais você empacotar em um teste, maior será a probabilidade de você ter um efeito de cebola tentando fazê-lo passar. Em outras palavras, a primeira falha interrompe esse teste. Você não saberá sobre as outras asserções até corrigir a primeira falha. Dito isto, ter vários testes de unidade que são basicamente similares, exceto pelo código de configuração, é muito trabalhoso apenas para descobrir que alguns funcionam como escritos e outros não.
Ferramentas possíveis, com base na sua estrutura:
Assume.that()
simplesmente pula o teste para os dados, se eles falharem na pré-condição. Isso permite definir "Funciona conforme o esperado" e, em seguida, simplesmente alimentar muitos dados. Ao visualizar os resultados, você tem uma entrada para os testes pai e, em seguida, uma subentrada para cada parte dos dados.Não sou da opinião estrita de que só pode haver uma
assert
declaração em seu teste, mas coloco as restrições de que todas as afirmações devem testar as pós-condições de uma única ação. Se a única diferença entre os testes é de dados, sou da opinião de usar os recursos mais avançados de testes controlados por dados, como testes parametrizados ou teorias.Pese suas opções para decidir qual é o melhor resultado. Eu direi que "WorksAsExpectedWhenNull" é fundamentalmente diferente de qualquer um dos casos em que você está lidando com uma coleção que possui vários números de elementos.
fonte
Esses são casos de teste diferentes, mas o código para o teste é o mesmo. Usar testes parametrizados é, portanto, a melhor solução. Se sua estrutura de teste não suportar parametrização, extraia o código compartilhado em uma função auxiliar e chame-o de casos de teste individuais.
Tente evitar a parametrização por meio de um loop em um caso de teste, pois isso dificulta a determinação de qual conjunto de dados causou o erro.
No seu ciclo de refatoramento TDD vermelho, verde, você deve adicionar um conjunto de dados de exemplo por vez. A combinação de vários casos de teste em um teste parametrizado faria parte da etapa de refatoração.
Uma abordagem bastante diferente é o teste de propriedades . Você criaria vários testes (parametrizados) que afirmam várias propriedades de sua função, sem especificar dados de entrada concretos. Por exemplo, uma propriedade pode ser: para todas as listas
xs
, a listays = f(xs)
tem o mesmo tamanho quexs
. A estrutura de teste geraria listas interessantes e listas aleatórias e afirmaria que suas propriedades são válidas para todas elas. Isso se afasta da especificação manual de exemplos, pois a escolha manual de exemplos pode perder casos interessantes.fonte
Ter um teste para cada caso é apropriado porque testar um único conceito em cada teste é uma boa diretriz que é frequentemente recomendada.
Veja este post: Não há problema em ter várias afirmações em um único teste de unidade? . Também há uma discussão relevante e detalhada:
[...]
fonte
Na minha opinião, isso depende da condição do teste.
fonte