Divisão de testes de unidades por requisito ou método

16

Primeiro, desculpas pelo título, não consegui pensar na maneira mais fácil de explicar!

Eu tenho um método para o qual quero escrever testes de unidade. Vou mantê-lo bastante genérico, pois não quero discutir a implementação do método, apenas o teste. O método é:

public void HandleItem(item a)
{         
     CreateNewItem();
     UpdateStatusOnPreviousItem();
     SetNextRunDate();
}

Portanto, essa classe possui um método público que chama alguns métodos privados para executar a lógica.

Então, ao escrever o teste de unidade, quero verificar se as três coisas foram feitas. Como todos são chamados na mesma execução, pensei que poderia fazê-lo como um teste:

public void GivenItem_WhenRun_Thenxxxxx
{
     HandleItem(item);
     // Assert item has been created
     // Assert status has been set on the previous item
     // Assert run date has been set
}

Mas achei que também poderia escrever como três testes separados:

public void GivenItem_WhenRun_ThenItemIsCreated()
{
    HandleItem(item);
}

public void GivenItem_WhenRun_ThenStatusIsUpdatedOnPreviousItem()
{
   HandleItem(item);
}

public void GivenItem_WhenRun_ThenRunDateIsSet()
{
     HandleItem(item);
}

Então, para mim, isso parece mais agradável, pois essencialmente lista os requisitos, mas todos os três estão relacionados e exigem exatamente o mesmo trabalho realizado no método testado, por isso estou executando o mesmo código três vezes.

Existe uma abordagem recomendada para levar com isso?

obrigado

ADringer
fonte

Respostas:

29

Há uma diferença sutil entre as duas abordagens. No primeiro caso, quando o primeiro Assertfalha, os outros dois não são mais executados. No segundo caso, todos os três testes são sempre executados, mesmo se um falhar. Dependendo da natureza da funcionalidade testada, isso pode se encaixar ou não no seu caso:

  • se faz sentido executar as três afirmações independentemente da outra, porque quando uma falha, as outras duas ainda podem falhar, a segunda abordagem tem a vantagem de obter os resultados completos dos três testes em uma execução. Isso pode ser benéfico se você tiver tempos de construção notáveis, pois oferece a chance de corrigir até 3 erros de uma vez antes de executar a próxima construção.

  • se, no entanto, uma falha no primeiro teste sempre implicar que os outros dois também falharão, provavelmente será melhor usar a primeira abordagem (já que não faz muito sentido executar um teste, se você já souber de antemão falhou).

Doc Brown
fonte
2
+1, bom ponto. Não me ocorreu que os tempos de construção também possam ser um gargalo.
precisa saber é o seguinte
1
@KilianFoth: Você não está trabalhando em C ++ muitas vezes :(
Matthieu M.
1
@MatthieuM .: para ser justo, a questão é marcado "C #"
Doc Brown
10

Resposta curta: é muito mais importante que seus testes abranjam todas as funcionalidades do que como eles fazem.

Resposta mais longa: se você ainda deseja escolher entre essas soluções amplamente equivalentes, pode usar critérios auxiliares para o que é melhor. Por exemplo,

  • legibilidade: se o método faz muitas coisas que não estão intimamente relacionadas, um teste combinado pode ser difícil de entender. No entanto, o próprio método também pode ser difícil de entender; talvez você deva refatorar o método e não o teste!)
  • eficiência: se a execução do método demorar muito, isso pode ser um motivo fraco para combinar todas as três verificações para economizar tempo
  • eficiência2: se a execução do código de configuração da sua estrutura demorar muito, isso também pode ser um motivo fraco para evitar vários métodos de teste. (No entanto, se isso realmente for um problema, você provavelmente deve corrigir ou alterar sua configuração de teste - os testes de regressão perdem muito de seu valor se você não puder executá-los com rapidez.)
Kilian Foth
fonte
2

Use uma chamada de método com várias afirmações. Aqui está o porquê:

Ao testar o HandleItem (a), você está testando se o método colocou o item no estado correto. Em vez de "uma afirmação por teste", pense em "um conceito lógico por teste".

Pergunta: Se um CreateNewItem falhar, mas os outros dois métodos forem bem-sucedidos, isso significa que o HandleItem foi concluído com êxito? Acho que não.

Com várias declarações (com mensagens apropriadas), você saberá exatamente o que falhou. Você normalmente testa um método várias vezes para várias entradas ou estados de entrada, para evitar várias afirmações.

Na IMO, essas perguntas geralmente são um sinal de outra coisa. É um sinal de que HandleItem não é realmente algo que você possa "testar a unidade", pois parece delegar apenas a outros métodos. Quando você está simplesmente validando que o HandleItem chama outros métodos corretamente, ele se torna mais um candidato a teste de integração (nesse caso, você ainda teria três declarações).

Você pode considerar tornar públicos os outros 3 métodos e testá-los independentemente. Ou até mesmo extraí-los para outra classe.

Kevin
fonte
0

Use a segunda abordagem. Com sua primeira abordagem, se o teste falhar, você não saberá o porquê imediatamente, porque pode ser uma das três funções que falharam. Com a segunda abordagem, você saberá imediatamente onde o problema ocorreu. Você pode colocar código duplicado dentro da sua função de configuração de teste.

Eternal21
fonte
-1

IMHO, você deve testar as três partes desse método separadamente, para saber mais especificamente onde as coisas estão dando errado quando elas acontecem, evitando passar duas vezes pela mesma parte do seu código.

Superusuário
fonte
-2

Eu não acho que exista um argumento forte para escrever métodos de teste separados para o seu caso de uso. Se você deseja obter os resultados de todas as três condições variáveis, pode testar todas as três e imprimir suas falhas concatenando-as em stringe afirmando se a sequência de caracteres ainda está vazia depois de concluir o teste. Mantendo todos no mesmo método, suas mensagens de condições e falhas documentam a pós-condição esperada do método em um único local, em vez de dividi-lo em três métodos que podem ser separados posteriormente.

Isso significa que seu teste único terá mais código, mas se você tiver tantas pós-condições que são um problema, provavelmente desejará refatorar seus métodos de teste para testar métodos individuais de HandleItemqualquer maneira.

IllusiveBrian
fonte