Tudo bem ter várias afirmações em um único teste de unidade?

397

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 .

Restuta
fonte
2
Como você zomba sem ter várias afirmações? Cada expectativa na simulação é uma afirmação em si mesma, incluindo qualquer ordem de chamadas que você impõe.
22413 Christopher Creutzig #
15
Eu já vi a filosofia de uma afirmação por método abusada no passado. Um colega de trabalho antigo usou um mecanismo de herança descolado para tornar isso possível. Isso levou a muitas subclasses (uma por filial) e a muitos testes que fizeram o mesmo processo de configuração / desmontagem apenas para verificar os diferentes resultados. Era lento, difícil de ler e um grave problema de manutenção. Eu nunca o convenci a voltar para uma abordagem mais clássica. O livro de Gerard Meszaros fala sobre esse tópico em detalhes.
Parques Travis
3
Penso que, como regra geral, você deve tentar minimizar o número de afirmações por teste. No entanto, desde que o teste restrinja suficientemente o problema para um local específico no código, será um teste útil.
ConditionRacer
2
Vi casos em que várias afirmações foram usadas em vez de 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.)
GalacticCowboy
@GalacticCowboy Você pode obter funcionalidades semelhantes RowTeste TestCaseusar fontes de dados de teste . Estou usando um arquivo CSV simples com grande sucesso.
12134 julealgon

Respostas:

236

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?

Jaco Pretorius
fonte
9
Como esta resposta - ou seja, tudo bem, mas geralmente não é bom (-:
Murph
5
Ehh Por que você faria isso? a execução do método é exatamente a mesma?
Javauffin 30/10/12
197
Uma única afirmação por teste de unidade é uma ótima maneira de testar a capacidade do leitor de rolar para cima e para baixo.
Tom
3
Algum raciocínio por trás disso? Como é, esta resposta atual apenas indica o que deveria ser, mas não o porquê.
precisa
37
Discordo fortemente. A resposta não lista nenhuma vantagem de ter uma única declaração, e a desvantagem óbvia é que você precisa copiar e colar os testes apenas porque uma resposta na internet o diz. Se você precisar testar vários campos de um resultado ou vários resultados de uma única operação, absolutamente deve declarar todos eles em declarações independentes, pois isso fornece informações muito mais úteis do que testá-las em uma grande declaração de blob. Em um bom teste, você testa uma única operação, não um único resultado de uma operação.
Peter Peter
296

Os testes devem falhar por apenas um motivo, mas isso nem sempre significa que deve haver apenas uma Assertdeclaraçã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

[Test]
public void ValueIsInRange()
{
  int value = GetValueToTest();

  Assert.That(value, Is.GreaterThan(10), "value is too small");
  Assert.That(value, Is.LessThan(100), "value is too large");
} 

ou

[Test]
public void ListContainsOneValue()
{
  var list = GetListOf(1);

  Assert.That(list, Is.Not.Null, "List is null");
  Assert.That(list.Count, Is.EqualTo(1), "Should have one item in list");
  Assert.That(list[0], Is.Not.Null, "Item is null");
} 

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

Assert.IsTrue((10 < value) && (value < 100), "Value out of range"); 

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.

Anthony
fonte
32
A chave é que você tem apenas uma ação e, em seguida, inspeciona os resultados dessa ação usando afirmações.
Amitābha
1
Eu acho que também é interessante ter mais como uma afirmação, se organizar é um tempo caro.
Rekshino 12/05/19
1
@ Rekshino, se o arranjo custar muito tempo, podemos compartilhar o código do arranjo, por exemplo, colocando o código do arranjo na rotina de inicialização do teste.
Shaun Luttin
2
Então, se eu comparar com a resposta de Jaco, a "única afirmação" se torna "apenas um grupo de afirmações" que faz mais sentido para mim.
Walfrat 19/04
2
Esta é uma boa resposta, mas discordo que uma única afirmação não é melhor. O exemplo de afirmação incorreta não é melhor, mas isso não significa que uma única afirmação não seria melhor se feita corretamente. Muitas bibliotecas permitem afirmações / correspondências personalizadas para que algo possa ser criado se ainda não estiver presente. Por exemplo, as Assert.IsBetween(10, 100, value)impressões Expected 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.
Thor84no
85

Eu nunca pensei que mais de uma afirmação fosse ruim.

Eu faço isso o tempo todo:

public void ToPredicateTest()
{
    ResultField rf = new ResultField(ResultFieldType.Measurement, "name", 100);
    Predicate<ResultField> p = (new ConditionBuilder()).LessThanConst(400)
                                                       .Or()
                                                       .OpenParenthesis()
                                                       .GreaterThanConst(500)
                                                       .And()
                                                       .LessThanConst(1000)
                                                       .And().Not()
                                                       .EqualsConst(666)
                                                       .CloseParenthesis()
                                                       .ToPredicate();
    Assert.IsTrue(p(ResultField.FillResult(rf, 399)));
    Assert.IsTrue(p(ResultField.FillResult(rf, 567)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 400)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 666)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 1001)));

    Predicate<ResultField> p2 = (new ConditionBuilder()).EqualsConst(true).ToPredicate();

    Assert.IsTrue(p2(new ResultField(ResultFieldType.Confirmation, "Is True", true)));
    Assert.IsFalse(p2(new ResultField(ResultFieldType.Confirmation, "Is False", false)));
}

Aqui, uso várias afirmações para garantir que condições complexas possam ser transformadas no predicado esperado.

Estou testando apenas uma unidade (o ToPredicatemétodo), mas estou cobrindo tudo o que consigo pensar no teste.

Matt Ellen
fonte
46
Várias afirmações são ruins devido à detecção de erros. Se você falhou no seu primeiro Assert.IsTrue, outros assertivos não serão executados e você não receberá nenhuma informação deles. Por outro lado, se você tivesse 5 testes em vez de 1 com 5 afirmações, poderia obter algo útil
Sly
6
Você ainda considera ruim se todas as declarações testarem o mesmo tipo de funcionalidade? Como acima, o exemplo testa os condicionais e, se algo disso falhar, você deve corrigi-lo. Importa a você que você pode perder as duas últimas afirmações se uma anterior falhar?
cringe
106
Eu resolvo meus problemas, um de cada vez. Portanto, o fato de o teste falhar mais de uma vez não me incomoda. Se os separasse, surgiriam os mesmos erros, mas de uma só vez. Acho mais fácil consertar as coisas passo a passo. Admito que, neste caso, as duas últimas afirmações provavelmente poderiam ser refatoradas em seu próprio teste.
Matt Ellen
19
Seu caso é muito representativo, é por isso que o NUnit tem atributo adicional TestCase - nunit.org/?p=testCase&r=2.5
Restuta
10
A estrutura de teste do Google C ++ possui ASSERT () e EXPECT (). O ASSERT () para na falha enquanto EXPECT () continua. Isso é muito útil quando você deseja validar mais de uma coisa no teste.
ratkok
21

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.

@Test
public void testAlarmSent() {
    assertAllUnitsAvailable();
    assertNewAlarmMessages(0);

    pulseMainProcessor();

    assertAllUnitsAlerting();
    assertAllNotificationsSent();
    assertAllNotificationsUnclosed();
    assertNewAlarmMessages(1);
}

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 com assertAllNotificationSent()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.)

BlairHippo
fonte
Quando você faz que você é melhor você usar estruturas de teste com testes dependentes, é melhor (por exemplo testng suporte a esse recurso)
Kemoda
8
Também escrevo testes como este para que você possa ter certeza do que o código está fazendo e indicar mudanças, não acho que seja um teste de unidade, mas um teste de integração.
Mdma 11/09/13
Quais são as suas opiniões sobre refatorá-lo em um assertAlarmStatus (int numberOfAlarmMessages) ;?
Borjab
1
Suas declarações resultariam em nomes de teste muito agradáveis.
Bjorn
É melhor deixar o teste executar, mesmo com entrada inválida. Dá a você mais informações dessa maneira (e especialmente se ainda passar quando você não esperava).
precisa saber é o seguinte
8

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:

class Service {
    Result process();
}

class Result {
    Inner inner;
}

class Inner {
    int number;
}

No meu teste, eu simplesmente quero testar o que service.process()retorna o número correto nas Innerinstâncias da classe.

Em vez de testar ...

@Test
public void test() {
    Result res = service.process();
    if ( res != null && res.getInner() != null ) Assert.assertEquals( ..., res.getInner() );
}

estou fazendo

@Test
public void test() {
    Result res = service.process();
    Assert.notNull(res);
    Assert.notNull(res.getInner());
    Assert.assertEquals( ..., res.getInner() );
}
Betlista
fonte
2
E isso é bom, você não deve ter nenhuma lógica condicional em seus testes. Torna o teste mais complexo e menos legível. E acho que Roy destacou bem em seu post que várias afirmações sobre um objeto são boas na maioria das vezes. Então, aqueles que você tem são apenas afirmações de guarda e não há problema em tê-las.
Restuta 30/10/12
2
Seus Assert.notNulls são redundantes, seu teste falhará com um NPE se eles forem nulos.
Sara
1
Além disso, o primeiro exemplo (com a if) vai passar se resénull
sara
1
@kai NOTNULL de são redundantes, eu concordo, mas eu sinto que é mais limpo para ter o assert (e se eu não sou preguiçoso também com a mensagem adequada) em vez de exceção ...
Betlista
1
Bem, uma afirmação com falha também gera uma exceção e, em ambos os casos, você obtém um link direto para a linha exata que a lançou com um rastreamento de pilha que o acompanha. Eu preferiria um oneliner à la Assert.assertEquals(..., service.process().getInner());, possível com variáveis extraídas se a linha fica "muito longo"
sara
6

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:

function testParseValidDateYMD() {
    var date = Date.parse("2016-01-02");

    Assert.That(date.Year).Equals(2016);
    Assert.That(date.Month).Equals(1);
    Assert.That(date.Day).Equals(0);
}

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".

Pete
fonte
3

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):

namespace Tests.AcctTests
{
    [TestFixture]
    public class no_events
    {
        private Acct _acct;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
        }

        [Test]
        public void balance_0() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_0
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(0m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_negative
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(-0.01m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }
}
INTPnerd
fonte
Como você lida com a entrada do TestCase neste caso?
Simon Gillbee
Então, na minha organização atual, temos mais de 20.000 testes de unidade muito semelhantes ao que você mostra. Isso é um pesadelo. Grande parte do código de configuração do teste é copiada / colada, resultando na configuração incorreta do teste e nos testes inválidos que passam. Para cada [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.
Fourpastmidnight 9/07/19
@fourpastmidnight A maior parte do que você disse parece uma crítica válida, mas o ponto de copiar e colar o código de instalação e, portanto, estar errado, não é um problema de estrutura, mas de programadores irresponsáveis ​​(que podem ser o resultado de gerentes irresponsáveis ​​ou de um ambiente negativo). mais do que maus programadores). Se as pessoas simplesmente copiam e colam o código e esperam que ele esteja correto e não se incomodem em entendê-lo, em qualquer contexto, por qualquer motivo, elas precisam ser treinadas para não fazer isso ou ser dispensadas, caso não possam treinado. Isso vai contra todo bom princípio de programação.
still_dreaming_1
Mas, em geral, eu concordo, isso é um exagero louco que resultará em muito inchaço / bagagem / duplicação que levará a todos os tipos de problemas. Eu costumava ser louco e recomendar coisas assim. É o que espero poder dizer todos os dias sobre mim no dia anterior, porque isso significa que nunca paro de encontrar maneiras melhores de fazer as coisas.
still_dreaming_1
@ still_dreaming_1 wrt "programadores irresponsáveis": concordo que esse comportamento é um grande problema. No entanto, esse tipo de estrutura de teste realmente convida esse tipo de comportamento, para melhor ou para pior. Más práticas de desenvolvimento à parte, minha principal objeção a esse formulário é que ele realmente mata o desempenho do teste. Não há nada pior do que um conjunto de testes de execução lenta. Um conjunto de testes de execução lenta significa que as pessoas não executam testes localmente e até tentam ignorá-los em versões intermediárias - novamente, um problema de pessoas, mas acontece - o que pode ser evitado, garantindo que você tenha testes de execução rápidos para iniciar com.
Fourpastmidnight
2

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.

Guffa
fonte
1
Se você estiver combinando várias condições em uma única afirmação, na falha, tudo o que você sabe é que uma falhou. Com várias afirmações, você conhece especificamente algumas delas (as que incluem a falha inclusive). Considere verificar se uma matriz retornada contém um valor único: verifique se não é nulo, se há exatamente um elemento e, em seguida, o valor desse elemento. (Dependendo da plataforma) apenas a verificação imediata do valor pode gerar uma desreferência nula (menos útil do que a declaração nula falha) e não verifica o comprimento da matriz.
Richard
@ Richard: Obter um resultado e depois extrair algo desse resultado seria um processo em várias etapas, então cobri isso no segundo parágrafo da resposta.
Guffa 28/09/10
2
Regra geral: se você tiver várias asserções em um teste, cada uma deve ter uma mensagem diferente. Então você não tem esse problema.
26612 Anthony
E o uso de um testador de qualidade como o NCrunch mostrará exatamente em que linha o teste falhou, tanto no código de teste quanto no código sob teste.
Fourpastmidnight 9/07/19
2

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:

String actual = "val1="+val1+"\nval2="+val2;
assertEquals(
    "val1=5\n" +
    "val2=hello"
    , actual
);

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.

Aaron Digulla
fonte
2

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,

@Test
test_Is_Date_segments_correct {

   // It is okay if you have multiple asserts checking dd, mm, yyyy, hh, mm, ss, etc. 
   // But you would not have any assert statement checking if it is string or number,
   // that is a different test and may be with multiple or single assert statement.
}

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.

hagubear
fonte
1

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.

jpierson
fonte
1

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.

Os testes de unidade adequados devem falhar por exatamente um motivo, por isso você deve usar uma declaração por teste de unidade.

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.

CurtainDog
fonte
0

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.

cslotty
fonte
-1

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.

gsf
fonte
-3

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.

Johan Larsson
fonte