1

首先,我知道在单元测试中有多个断言是不好的做法。

但有时您需要测试一些原子事务。作为一个简化的示例,让我们以一些具有 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

在这种情况下,我想一起检查 3 件事:

  1. 源账户余额减少了amount
  2. 目的地账户余额增加了amount
  3. 已插入审核记录

测试该@account.transfer方法的最佳方法是什么?

4

2 回答 2

2

在这种情况下,我想一起检查 3 件事:

我认为您真正想要的是在特定条件下描述这些事物的行为,从而确保行为符合您的规范。这可能意味着事情会一起发生;或者这可能意味着某些事情只在一组条件下发生而不在其他条件下发生,或者异常导致一切都回滚到其原始状态。

在一个测试中包含所有断言并没有什么神奇之处,除了让事情变得更快。除非您面临严重的性能损失(在全栈测试中经常发生),否则每次测试使用一个断言要好得多。

RSpec 可以直接提取测试设置阶段,以便为每个示例重复它:

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

如果“快乐路径”中的所有三个测试都通过了,那么它们都“一起发生”,因为在每种情况下初始系统状态都是相同的。

但是您还需要确保在出现问题时不会发生任何事情,并且系统会恢复到其原始状态。拥有多个断言可以很容易地看到它按预期工作,并且当测试失败时,它们究竟是如何失败的。

于 2014-07-31T22:48:18.223 回答
1

每个测试多个断言并不总是一个不好的做法。如果多个断言验证相同的行为,则没有问题。当试图在同一个测试中验证多个行为时,就会出现问题。当然,每个测试有多个断言存在一些风险。其中之一是您可能会不小心留下先前测试集中的值,从而以一种奇怪的方式使先前的测试无效。此外,当一个断言为假时,剩下的所有断言都不会被执行,这可能会导致难以理解发生了什么。但要合理,您可以有多个断言断言相同的行为,最好是短的,并且没有额外的设置。

在你带来的简单案例中,我会使用多个断言,因为它是如此简单。但当然它可能会变得更复杂,比如负余额、不同类型的账户等等。那么最好对一个(最好)断言使用不同的测试。我会这样组织它:

  • 1 测试当前Account的行为(最简单的情况);
  • 1 到该方法可能具有的每条不同路径(异常、负余额等);
  • 1 在所有这些可能性中测试审核;

  • 1 测试当前to_account的行为(最简单的情况);

  • 1 到该方法可以具有的每条不同路径。(例外、负余额等);
  • 1 在所有这些可能性中测试审核;

由于审计测试非常简单,不需要额外的设置,您还可以与 Account 和 to_account 一起测试它。

于 2014-07-31T04:14:00.883 回答