【问题标题】:rspec + capybara: feature testing only 1 expectation per test?rspec + capybara:功能测试每次测试只有 1 个期望?
【发布时间】:2014-03-01 23:45:16
【问题描述】:

我正在使用 rspec 和 capybara 为我的 Rails 4 应用程序编写一些功能测试。我让它正常工作,但我很难理解以这种方式进行测试的某些方面。

我一直在读到每个测试(它“应该......”阻止)应该只测试一件事。好的,这听起来不错,但是当我将其付诸实践时,我最终会为简单的事情编写大量测试。

假设我有一个标准的注册表单,其中包含电子邮件、密码和信用卡。

所以 test 为我的注册编写一个功能测试,我是否必须编写 3 个单独的测试来测试该功能?

describe "Signup" do
  it "informs user of an invalid email" do
    visit signups_path
    fill_in "Email", with: ""
    click_button "Signup"
    expect(page).to have_text("Email can't be blank")
  end
  it "informs user of an invalid password" do
    visit signups_path
    fill_in "Email", with: "test@test.com"
    fill_in "Password", with: ""
    click_button "Signup"
    expect(page).to have_text("Password can't be blank")
  end
  it "informs user of an invalid credit card" do
    visit signups_path
    fill_in "Email", with: "test@test.com"
    fill_in "Password", with: "valid-password"
    fill_in "Card", with: "bogus"
    click_button "Signup"
    expect(page).to have_text("Card is invalid")
  end
end

在一次测试中测试所有这些似乎更简单。我正在努力寻找有关执行功能测试的正确方法的文章。我不想开始随机编写测试(1)实际上并没有覆盖/测试我的代码或(2)变得臃肿和缓慢,因为我没有能力编写它们。我了解测试的必要性,只是不确定如何最好地进行上述功能测试。

【问题讨论】:

  • 为什么你觉得写一个测试会更简单?
  • @JustinKo,我认为这会更简单,因为对于每个测试,我都处于相同的“场景”中,每个测试都有很多重复的代码。不过,在研究了一段时间后,我得出结论,清理 before 块中的重复代码很有帮助,真正的测试应该只有 1 个 expect/assert 才能真正帮助查明问题。 “更简单”我想我的意思是“更短”。但有时更短并不总是更好。

标签: rspec tdd capybara integration-testing


【解决方案1】:

您应该干燥您的代码以尽量减少重复。您可以将每次测试开始时调用的指令移动到before,并为重复代码提取辅助方法:

describe "Signup" do
  before do
    visit signups_path
  end

  def fill_form(fields = {})
    fields.each do |field_name, field_value|
      fill_in field_name.to_s.capitalize, with: field_value
    end
    click_button "Signup"
  end

  it "informs user of an invalid email" do
    fill_form email: ""

    expect(page).to have_text("Email can't be blank")
  end
  it "informs user of an invalid password" do
    fill_form email: "test@test.com", password: ""

    expect(page).to have_text("Password can't be blank")
  end
  it "informs user of an invalid credit card" do
    fill_form email: "test@test.com", 
              password: "valid-password",
              card: "bogus"

    expect(page).to have_text("Card is invalid")
  end
end

【讨论】:

  • 经过一段时间的研究,这是解决这个问题的最干净和最常规的方法。严格来说,每个测试应该只期望/断言一件事才能真正成为一个单元测试。这使每个单独的测试更小,更容易维护。确实会导致运行测试套件的时间更长一些,但这就是权衡。我只遇到过一种情况,我决定将多个 expect(...) 放在一个测试中,那是因为我正在调用外部 API,而且在时间上进行多次调用非常昂贵。
【解决方案2】:

在编写集成测试时,一定要避免coupling the test to the UI

这是一个例子:

RSpec.feature 'Signup', test_helpers: [:sign_ups] do
  before { visit signups_path }

  it 'informs user of an invalid email' do
    sign_ups.sign_up_with(email: '')
    sign_ups.should.have_error("Email can't be blank")
  end

  it 'informs user of an invalid password' do
    sign_ups.sign_up_with(email: 'test@test.com')
    sign_ups.should.have_error("Password can't be blank")
  end

  it 'informs user of an invalid credit card' do
    sign_ups.sign_up_with(email: 'test@test.com', password: 'valid-password', card: 'bogus')
    sign_ups.should.have_error('Card is invalid')
  end
end

您可以使用page objects 或类似Capybara Test Helpers 的库来实现:

class SignUpsTestHelper < Capybara::TestHelper
  def sign_up_with(email:, password: nil, card: nil)
    fill_in 'Email', with: email
    fill_in 'Password', with: password if password
    fill_in 'Card', with: card if card
    click_button 'Signup'
  end

  def have_error(message)
    have_selector('#error_explanation', text: message)
  end
end

也就是说,有时不需要完全隔离,结合场景应该可以显着提高测试套件的速度:

RSpec.feature 'Signup', test_helpers: [:sign_ups] do
  before { visit signups_path }

  it 'informs user of invalid info' do
    sign_ups.sign_up_with(email: '')
    sign_ups.should.have_error("Email can't be blank")

    sign_ups.sign_up_with(email: 'test@test.com')
    sign_ups.should.have_error("Password can't be blank")

    sign_ups.sign_up_with(email: 'test@test.com', password: 'valid-password', card: 'bogus')
    sign_ups.should.have_error('Card is invalid')
  end
end

请注意,现在测试代码已经很好地封装了,在压缩版本中仍然很容易遵循。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-09
    • 1970-01-01
    • 1970-01-01
    • 2015-04-07
    • 1970-01-01
    • 1970-01-01
    • 2015-08-28
    • 1970-01-01
    相关资源
    最近更新 更多