简体   繁体   English

具有多个断言的RSpec单元测试

[英]RSpec unit test with multiple assertions

First of all I know it is bad practice to have multiple assertions in unit test. 首先,我知道在单元测试中有多个断言是不好的做法。

But sometimes you need to test some atomic transaction. 但有时你需要测试一些原子事务。 As a simplified example, let's take some banking application that has the Account class: 作为一个简化示例,让我们来看一些具有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

In this situation I want to check 3 things together: 在这种情况下,我想一起检查3件事:

  1. Source account balance decreased by amount 来源帐户余额按amount减少
  2. Destination account balance increased by amount 目标帐户余额按amount增加
  3. Audit record is inserted 插入审计记录

What is the best way to test the @account.transfer method? 测试@account.transfer方法的最佳方法是什么?

In this situation I want to check 3 things together: 在这种情况下,我想一起检查3件事:

I'd argue that what you really want is to describe the behavior of these things under certain conditions, and thus ensure that the behavior meets your specifications. 我认为你真正想要的是在某些条件下描述这些事物的行为,从而确保行为符合你的规范。 That might mean things happen together; 这可能意味着事情一起发生; or it might mean that some things only happen in one set of conditions and not others, or that an exception causes everything to be rolled back to its original state. 或者它可能意味着某些事情只发生在一组条件中而不是其他条件中,或者一个异常导致一切都回滚到其原始状态。

There is no magic to having all your assertions in one test, except to make things faster. 除了让事情变得更快之外,在一次测试中拥有所有断言是没有魔力的。 Unless you are facing a severe performance penalty (as often happens in full-stack tests), it is much better to use one assertion per test. 除非您面临严重的性能损失(通常在全栈测试中发生),否则每次测试使用一个断言要好得多。

RSpec makes it straightforward to extract the test setup phase so that it is repeated for each example: 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

If all three tests in the "happy path" passed, then they all "happened together", since the initial system state was the same in each case. 如果“快乐路径”中的所有三个测试都通过,则它们全部“一起发生”,因为初始系统状态在每种情况下都是相同的。

But you also need to ensure that things don't happen when something goes wrong, and that the system goes back to its original state. 但是,您还需要确保在出现问题时不会发生任何事情,并且系统会恢复到原始状态。 Having multiple assertions makes it easy to see that this works as expected, and when tests fail, exactly how they failed. 有多个断言可以很容易地看到它按预期工作,并且当测试失败时,它们究竟是如何失败的。

Multiple assertions per test is not always a bad practice. 每次测试多次断言并不总是坏习惯。 If the multiple asserts verify the same behaviour, there's no problem with it. 如果多个断言验证了相同的行为,那么它就没有问题。 The problem exists when trying to verify more than one behaviour in the same test. 尝试在同一测试中验证多个行为时存在问题。 Of course there are some risks with multiple asserts per test. 当然,每次测试有多个断言存在一些风险。 One of them is you may accidentally leave values from a previous test set that invalidates a previous test in a weird way. 其中之一是您可能会意外地从以前的测试集中保留值,这会以奇怪的方式使先前的测试无效。 Also, when one assert is false, all the other left will not be executed, which can cause difficulties to understand what's goin on. 此外,当一个断言为假时,所有其他断言都不会被执行,这可能会导致难以理解发生了什么。 But be reasonable, you can have multiple asserts asserting the same behaviour, preferrably short ones and with no extra setup. 但是要合理,你可以有多个断言断言相同的行为,最好是短行程,没有额外的设置。

In the simple case you brought, I would use multiple asserts, because it is so simple. 在你带来的简单案例中,我会使用多个断言,因为它非常简单。 But of course it can get a lot more complicated, like negative balance, different types of accounts and stuff. 但当然它可以变得更复杂,比如负余额,不同类型的账户和东西。 Then it would be better to use different tests with one (preferrably) assert. 那么最好使用一个(最好的)断言进行不同的测试。 I would organize it like this: 我会像这样组织它:

  • 1 to test the behaviour of the current Account (simplest case); 1测试当前账户的行为(最简单的情况);
  • 1 to every different path the method can have (exceptions, negative balance etc.); 1到方法可以有的每个不同的路径(例外,负平衡等);
  • 1 to test Audit in every of these possibilities; 1在每种可能性中测试审计;

  • 1 to test the behaviour of the current to_account (simplest case); 1测试当前to_account的行为(最简单的情况);

  • 1 to every different path the method can have. 1到该方法可以具有的每个不同路径。 (exceptions, negative balance etc.) ; (例外,负余额等);
  • 1 to test Audit in every of these possibilities; 1在每种可能性中测试审计;

Since the Audit test is pretty simple and requires no extra setup, you can also test it along with Account and to_account. 由于Audit测试非常简单,无需额外设置,因此您还可以使用Account和to_account进行测试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM