No comentário a este ótimo post , Roy Osherove mencionou o projeto OAPT , projetado para executar cada afirmação em um único teste.
O seguinte está escrito na página inicial do projeto:
Os testes de unidade adequados devem falhar por exatamente um motivo, por isso você deve usar uma declaração por teste de unidade.
E, também, Roy escreveu nos comentários:
Minha orientação é geralmente que você teste um CONCEITO lógico por teste. você pode ter várias declarações no mesmo objeto . eles geralmente serão o mesmo conceito sendo testado.
Penso que existem alguns casos em que são necessárias múltiplas asserções (por exemplo , asserção de guarda ), mas, em geral, tento evitar isso. qual e sua OPINIAO? Forneça um exemplo do mundo real, onde várias afirmações são realmente necessárias .
fonte
RowTest
(MbUnit) /TestCase
(NUnit) para testar uma variedade de comportamentos de casos extremos. Use as ferramentas adequadas para o trabalho! (Infelizmente, MSTest não parece ter uma capacidade de linha-teste ainda.)RowTest
eTestCase
usar fontes de dados de teste . Estou usando um arquivo CSV simples com grande sucesso.Respostas:
Não acho que seja necessariamente uma coisa ruim , mas acho que devemos nos esforçar para ter apenas afirmações únicas em nossos testes. Isso significa que você escreve muito mais testes e nossos testes acabariam testando apenas uma coisa de cada vez.
Dito isto, eu diria que talvez metade dos meus testes tenha apenas uma afirmação. Eu acho que só se torna um cheiro de código (teste?) Quando você tem cerca de cinco ou mais afirmações em seu teste.
Como você resolve várias afirmações?
fonte
Os testes devem falhar por apenas um motivo, mas isso nem sempre significa que deve haver apenas uma
Assert
declaração. IMHO é mais importante manter o padrão " Organizar, Agir, Afirmar ".A chave é que você tem apenas uma ação e, em seguida, inspeciona os resultados dessa ação usando afirmações. Mas é "Organizar, agir, afirmar, final de teste ". Se você for tentado a continuar o teste executando outra ação e mais declarações depois, faça um teste separado.
Fico feliz em ver várias declarações de afirmação que fazem parte do teste da mesma ação. por exemplo
ou
Você pode combiná-las em uma única afirmação, mas isso é diferente de insistir que você deve ou deve . Não há melhoria em combiná-los.
por exemplo, o primeiro pode ser
Mas isso não é melhor - a mensagem de erro é menos específica e não tem outras vantagens. Tenho certeza de que você pode pensar em outros exemplos em que a combinação de duas ou três (ou mais) afirmações em uma grande condição booleana torna mais difícil de ler, mais difícil de alterar e mais difícil de descobrir por que falhou. Por que fazer isso apenas por uma regra?
NB : O código que estou escrevendo aqui é C # com NUnit, mas os princípios serão mantidos em outros idiomas e estruturas. A sintaxe também pode ser muito semelhante.
fonte
Assert.IsBetween(10, 100, value)
impressõesExpected 8 to be between 10 and 100
são melhores do que duas afirmações separadas na minha opinião. Você certamente pode argumentar que não é necessário, mas geralmente vale a pena considerar se é fácil reduzir a uma única afirmação antes de fazer um conjunto inteiro delas.Eu nunca pensei que mais de uma afirmação fosse ruim.
Eu faço isso o tempo todo:
Aqui, uso várias afirmações para garantir que condições complexas possam ser transformadas no predicado esperado.
Estou testando apenas uma unidade (o
ToPredicate
método), mas estou cobrindo tudo o que consigo pensar no teste.fonte
Quando estou usando o teste de unidade para validar o comportamento de alto nível, absolutamente coloco várias asserções em um único teste. Aqui está um teste que estou usando para algum código de notificação de emergência. O código executado antes do teste coloca o sistema em um estado em que, se o processador principal for executado, um alarme será enviado.
Ele representa as condições que precisam existir em todas as etapas do processo para que eu tenha certeza de que o código está se comportando da maneira que eu espero. Se uma única afirmação falhar, não me importo que as demais nem sejam executadas; como o estado do sistema não é mais válido, essas afirmações subsequentes não me revelariam nada valioso. * Se
assertAllUnitsAlerting()
falhasse, não saberia o que fazer comassertAllNotificationSent()
o sucesso OU falha até determinar o que estava causando o erro anterior. e corrigiu.(* - Ok, eles podem ser úteis na depuração do problema. Mas as informações mais importantes, de que o teste falhou, já foram recebidas.)
fonte
Outra razão pela qual penso que várias afirmações em um método não é uma coisa ruim é descrita no código a seguir:
No meu teste, eu simplesmente quero testar o que
service.process()
retorna o número correto nasInner
instâncias da classe.Em vez de testar ...
estou fazendo
fonte
Assert.notNull
s são redundantes, seu teste falhará com um NPE se eles forem nulos.if
) vai passar seres
énull
Assert.assertEquals(..., service.process().getInner());
, possível com variáveis extraídas se a linha fica "muito longo"Eu acho que existem muitos casos em que escrever várias afirmações é válido dentro da regra de que um teste deve falhar apenas por um motivo.
Por exemplo, imagine uma função que analise uma sequência de datas:
Se o teste falhar, é devido a um motivo: a análise está incorreta. Se você argumentar que este teste pode falhar por três razões diferentes, você seria muito refinado na definição de "uma razão".
fonte
Não conheço nenhuma situação em que seria uma boa ideia ter várias asserções dentro do próprio método [Teste]. A principal razão pela qual as pessoas gostam de ter várias asserções é que estão tentando ter uma classe [TestFixture] para cada classe que está sendo testada. Em vez disso, você pode dividir seus testes em mais classes [TestFixture]. Isso permite que você veja várias maneiras pelas quais o código pode não ter reagido da maneira esperada, em vez de apenas aquele em que a primeira afirmação falhou. A maneira como você consegue isso é que você tem pelo menos um diretório por classe sendo testado com muitas classes [TestFixture] dentro. Cada classe [TestFixture] seria nomeada após o estado específico de um objeto que você estará testando. O método [SetUp] colocará o objeto no estado descrito pelo nome da classe. Então você tem vários métodos [Test], cada um deles afirmando coisas diferentes que você esperaria serem verdadeiras, dado o estado atual do objeto. Cada método [Teste] recebe o nome da coisa que está afirmando, exceto, talvez, o nome do conceito, em vez de apenas uma leitura em inglês do código. Então, cada implementação do método [Test] precisa apenas de uma única linha de código em que está afirmando algo. Outra vantagem dessa abordagem é que ela torna os testes muito legíveis, pois fica bem claro o que você está testando e o que você espera apenas observando os nomes de classe e método. Isso também será melhor quando você perceber todos os pequenos casos extremos que deseja testar e encontrar bugs. exceto que talvez possa ter o nome do conceito em vez de apenas uma leitura em inglês do código. Então, cada implementação do método [Test] precisa apenas de uma única linha de código em que está afirmando algo. Outra vantagem dessa abordagem é que ela torna os testes muito legíveis, pois fica bem claro o que você está testando e o que você espera apenas observando os nomes de classe e método. Isso também será melhor quando você perceber todos os pequenos casos extremos que deseja testar e encontrar bugs. exceto que talvez possa ter o nome do conceito em vez de apenas uma leitura em inglês do código. Então, cada implementação do método [Test] precisa apenas de uma única linha de código em que está afirmando algo. Outra vantagem dessa abordagem é que ela torna os testes muito legíveis, pois fica bem claro o que você está testando e o que você espera apenas observando os nomes de classe e método. Isso também será melhor quando você perceber todos os pequenos casos extremos que deseja testar e encontrar bugs. e o que você espera apenas olhando para os nomes de classe e método. Isso também será melhor quando você perceber todos os pequenos casos extremos que deseja testar e encontrar bugs. e o que você espera apenas olhando para os nomes de classe e método. Isso também será melhor quando você perceber todos os pequenos casos extremos que deseja testar e encontrar bugs.
Geralmente, isso significa que a linha final de código dentro do método [SetUp] deve armazenar um valor de propriedade ou retornar um valor em uma variável de instância privada do [TestFixture]. Então você pode afirmar várias coisas diferentes sobre essa variável de instância a partir de diferentes métodos [Test]. Você também pode fazer afirmações sobre quais propriedades diferentes do objeto em teste estão definidas agora que estão no estado desejado.
Às vezes, você precisa fazer afirmações ao longo do caminho ao colocar o objeto em teste no estado desejado, para garantir que você não estrague tudo antes de colocar o objeto no estado desejado. Nesse caso, essas asserções extras devem aparecer dentro do método [SetUp]. Se algo der errado dentro do método [SetUp], ficará claro que algo estava errado com o teste antes que o objeto chegasse ao estado desejado que você pretendia testar.
Outro problema que você pode encontrar é que você pode estar testando uma exceção que você esperava que fosse lançada. Isso pode levá-lo a não seguir o modelo acima. No entanto, isso ainda pode ser alcançado capturando a exceção dentro do método [SetUp] e armazenando-a em uma variável de instância. Isso permitirá que você afirme coisas diferentes sobre a exceção, cada uma no seu próprio método [Teste]. Você também pode afirmar outras coisas sobre o objeto em teste para garantir que não haja efeitos colaterais indesejados da exceção lançada.
Exemplo (isso seria dividido em vários arquivos):
fonte
[Test]
método, essa classe é instanciada novamente e o[SetUp]
método é executado novamente. Isso mata o .NET Garbage Collector e faz com que os testes sejam executados extremamente lentamente: mais de 5 minutos localmente, mais de 20 minutos no servidor de compilação. Os testes de 20K devem ser executados em cerca de 2 a 3 minutos. Eu não recomendaria esse estilo de teste, especialmente para um conjunto de testes grande.Ter várias asserções no mesmo teste é apenas um problema quando o teste falha. Talvez você precise depurar o teste ou analisar a exceção para descobrir qual afirmação é que falha. Com uma afirmação em cada teste, geralmente é mais fácil identificar o que está errado.
Não consigo pensar em um cenário em que várias asserções sejam realmente necessárias , pois você sempre pode reescrevê-las como várias condições na mesma asserção. No entanto, pode ser preferível se você, por exemplo, tiver várias etapas para verificar os dados intermediários entre as etapas, em vez de arriscar que as etapas posteriores falhem devido a uma entrada incorreta.
fonte
Se seu teste falhar, você não saberá se as afirmações a seguir também serão quebradas. Freqüentemente, isso significa que você estará perdendo informações valiosas para descobrir a origem do problema. Minha solução é usar uma afirmação, mas com vários valores:
Isso me permite ver todas as afirmações com falha ao mesmo tempo. Uso várias linhas porque a maioria dos IDEs exibirá diferenças de seqüência de caracteres em um diálogo de comparação lado a lado.
fonte
Se você tiver várias afirmações em uma única função de teste, espero que sejam diretamente relevantes para o teste que você está realizando. Por exemplo,
Ter muitos testes (mesmo quando você sente que provavelmente é um exagero) não é uma coisa ruim. Você pode argumentar que ter os testes vitais e mais essenciais é mais importante. Portanto, quando você estiver afirmando, verifique se suas declarações de afirmação estão corretamente colocadas, em vez de se preocupar demais com várias afirmações. Se você precisar de mais de um, use mais de um.
fonte
O objetivo do teste de unidade é fornecer o máximo de informações possível sobre o que está falhando, mas também ajudar a identificar com precisão os problemas mais fundamentais primeiro. Quando você sabe logicamente que uma afirmação falhará, uma vez que outra afirmação falhar ou, em outras palavras, existe um relacionamento de dependência entre o teste, faz sentido lançá-las como múltiplas afirmações em um único teste. Isso tem o benefício de não desarrumar os resultados dos testes com falhas óbvias que poderiam ter sido eliminadas se resgatássemos a primeira afirmação em um único teste. No caso em que esse relacionamento não exista, a preferência seria, naturalmente, separar essas asserções em testes individuais porque, caso contrário, encontrar essas falhas exigiria várias iterações de execuções de teste para solucionar todos os problemas.
Se você também projetar as unidades / classes de forma que testes excessivamente complexos precisem ser escritos, isso gera menos carga durante o teste e provavelmente promove um design melhor.
fonte
Sim, não há problema em ter várias asserções , desde que um teste com falha forneça informações suficientes para poder diagnosticar a falha. Isso vai depender do que você está testando e quais são os modos de falha.
Nunca achei essas formulações úteis (o fato de uma classe ter um motivo para mudar é um exemplo de um ditado inútil). Considere uma afirmação de que duas cadeias são iguais, isso é semanticamente equivalente a afirmar que o comprimento das duas cadeias é o mesmo e que cada caractere no índice correspondente é igual.
Poderíamos generalizar e dizer que qualquer sistema de múltiplas asserções poderia ser reescrito como uma única asserção, e qualquer única asserção poderia ser decomposta em um conjunto de asserções menores.
Portanto, concentre-se apenas na clareza do código e na clareza dos resultados do teste e deixe que ele guie o número de afirmações que você usa, em vez de vice-versa.
fonte
A resposta é muito simples - se você testar uma função que altera mais de um atributo, do mesmo objeto ou mesmo dois objetos diferentes, e a correção da função depende dos resultados de todas essas alterações, você deseja afirmar que todas essas mudanças foram realizadas corretamente!
Tenho a idéia de um conceito lógico, mas a conclusão inversa diria que nenhuma função deve jamais mudar mais de um objeto. Mas isso é impossível de implementar em todos os casos, na minha experiência.
Adote o conceito lógico de uma transação bancária - retirar um valor de uma conta bancária na maioria dos casos DEVE incluir adicionar esse valor a outra conta. Você NUNCA deseja separar essas duas coisas, elas formam uma unidade atômica. Você pode querer fazer duas funções (retirar / adicionar dinheiro) e, assim, escrever dois testes de unidade diferentes - além disso. Mas essas duas ações precisam ocorrer em uma transação e você também deseja garantir que a transação funcione. Nesse caso, simplesmente não é suficiente para garantir que as etapas individuais tenham êxito. Você deve verificar as duas contas bancárias no seu teste.
Pode haver exemplos mais complexos que você não testaria em um teste de unidade, em primeiro lugar, mas em um teste de integração ou aceitação. Mas esses limites são fluentes, IMHO! Não é tão fácil de decidir, é uma questão de circunstâncias e talvez preferência pessoal. Retirar dinheiro de um e adicioná-lo a outra conta ainda é uma função muito simples e definitivamente um candidato a testes de unidade.
fonte
Esta questão está relacionada ao problema clássico de balanceamento entre problemas de código de espaguete e lasanha.
Ter várias afirmações poderia facilmente entrar no problema do espaguete, onde você não tem idéia do que é o teste, mas ter uma única afirmação por teste pode tornar seu teste igualmente ilegível, tendo vários testes em uma lasanha grande, tornando difícil descobrir qual teste faz o impossível. .
Existem algumas exceções, mas, neste caso, manter o pêndulo no meio é a resposta.
fonte
Eu nem concordo com o "fracasso por uma única razão" em geral. O mais importante é que os testes sejam curtos e tenham uma leitura clara.
Porém, isso nem sempre é possível e quando um teste é complicado, um nome descritivo (longo) e o teste de menos coisas fazem mais sentido.
fonte