Pergunta

Antes de tudo, sei que é uma má prática ter múltiplas asserções em testes unitários.

Mas às vezes você precisa testar alguma transação atômica.Como exemplo simplificado, vamos pegar uma aplicação bancária que possui a classe Account:

class Account 
  attr_accessor :balance

  def transfer(to_account, amount)
    self.balance -= amount
    to_account.balance += amount
    Audit.create(message: "Transferred #{amount} from #{self.number} to #{to_account.number}."
  end

end

Nesta situação, quero verificar três coisas juntas:

  1. O saldo da conta de origem diminuiu em amount
  2. Saldo da conta de destino aumentado em amount
  3. O registro de auditoria é inserido

Qual é a melhor maneira de testar o @account.transfer método?

Foi útil?

Solução

Nesta situação, quero verificar três coisas juntas:

Eu diria que o que você realmente quer é descrever o comportamento dessas coisas sob certas condições e, assim, garantir que o comportamento atenda às suas especificações.Isso pode significar que as coisas acontecem juntas;ou pode significar que algumas coisas acontecem apenas em um conjunto de condições e não em outras, ou que uma exceção faz com que tudo volte ao seu estado original.

Não há mágica em ter todas as suas afirmações em um único teste, exceto para tornar as coisas mais rápidas.A menos que você esteja enfrentando uma penalidade severa de desempenho (como acontece frequentemente em testes full-stack), é muito melhor usar uma afirmação por teste.

O RSpec simplifica a extração da fase de configuração do teste para que ela seja repetida para cada exemplo:

class Account 
  attr_accessor :balance

  def transfer(to_account, amount)
    self.debit!(amount)
    to_account.credit!(amount)
    Audit.create!(message: "Transferred #{amount} from #{self.number} to #{to_account.number}."
  rescue SomethingBadError
    # undo all of our hard work
  end

end

describe Account do
  context "when a transfer is made to another account" do
    let(:other_account} { other_account }
    context "and the subject account has sufficient funds" do
      subject { account_with_beaucoup_bucks }
      it "debits the subject account"
      it "credits the other account"
      it "creates an Audit entry"
    end
    context "and the subject account is overdrawn" do
      subject { overdrawn_account }
      it "does not debit the subject account"
      it "does not credit the other account"
      it "creates an Audit entry" # to show the attempted transfer failed
    end
  end
end

Se todos os três testes do "caminho feliz" passaram, então todos eles "aconteceram juntos", já que o estado inicial do sistema era o mesmo em cada caso.

Mas você também precisa garantir que as coisas não acontece quando algo dá errado e que o sistema volta ao seu estado original.Ter múltiplas asserções facilita ver que isso funciona conforme o esperado e, quando os testes falham, exatamente como eles falharam.

Outras dicas

Múltiplas asserções por teste nem sempre são uma prática ruim.Se as múltiplas afirmações verificarem o mesmo comportamento, não há problema com isso.O problema existe ao tentar verificar mais de um comportamento no mesmo teste.É claro que existem alguns riscos com múltiplas afirmações por teste.Uma delas é que você pode acidentalmente deixar valores de um conjunto de testes anterior que invalidam um teste anterior de uma forma estranha.Além disso, quando uma afirmação é falsa, todas as outras restantes não serão executadas, o que pode causar dificuldades para entender o que está acontecendo.Mas seja razoável, você pode ter várias afirmações afirmando o mesmo comportamento, de preferência curtas e sem configuração extra.

No caso simples que você trouxe, eu usaria múltiplas afirmações, porque é muito simples.Mas é claro que pode ficar muito mais complicado, como saldo negativo, diferentes tipos de contas e outras coisas.Então seria melhor usar testes diferentes com uma (de preferência) afirmação.Eu organizaria assim:

  • 1 para testar o comportamento da Conta Corrente (caso mais simples);
  • 1 a cada caminho diferente que o método pode ter (exceções, equilíbrio negativo etc.);
  • 1 testar a Auditoria em cada uma dessas possibilidades;

  • 1 para testar o comportamento da to_account atual (caso mais simples);

  • 1 para cada caminho diferente que o método possa ter.(exceções, equilíbrio negativo etc.);
  • 1 testar a Auditoria em cada uma dessas possibilidades;

Como o teste de auditoria é bastante simples e não requer configuração extra, você também pode testá-lo junto com Account e to_account.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top