在 Spec 中测试 Rails 模型,如何创建高效的示例?
Testing Rails model in Spec, how to create efficient examples?
我正在尝试使用 Rspec 和 FactoryBot 在 Rails 中测试我的模型。我正在使用 let!
:
let!(:account1) { create(:account, :year) }
let!(:account2) { create(:account, :three_months) }
let!(:account3) { create(:account, :month) }
let!(:account4) { create(:account, :day) }
我正在测试我的示波器是否返回了正确的记录数组。我的假设是还应该有一些 "bad data",来测试范围是否没有返回它不应该返回的东西。例如:
describe ".seen_last_two_months" do
subject { Account.seen_last_two_months.to_a }
it { is_expected.to eq([account3, account4]) }
end
看来,随着测试量的增加,速度会明显下降。似乎每个调用 let!
的测试都会命中数据库,创建一条新记录。
- 我设置测试的方式是否正确,那么我是否也应该测试查询不希望返回的记录?
- 有没有办法只调用一次创建记录?
- Is the way I'm setting up my tests correct, so should I also test for records that are not expected to be returned by the query?
是的,只是你应该。
- Is there a way to invoke the creation of the records only once?
是的,您需要创建这些记录来测试特定范围。因此,它们应该只在特定测试时被调用。您可以将它们移动到与范围测试相关的 describe
块,然后它们将不会被其他测试调用。
# Keep this one without `!`, so it will be called (in other tests), if and when needed
let(:account1) { create(:account, :year) }
describe ".seen_last_two_months" do
let!(:account1) { create(:account, :year) }
let!(:account2) { create(:account, :three_months) }
let!(:account3) { create(:account, :month) }
let!(:account4) { create(:account, :day) }
subject { Account.seen_last_two_months.to_a }
it { is_expected.to eq([account3, account4]) }
end
更新
如果您只想为所有测试创建一次记录,请改用 before(:all)
。看起来在 before(:all)
块中调用 let
定义的变量是个坏主意,请改用实例变量:
before(:all) do
@account1 = create(:account, :year)
@account2 = create(:account, :three_months)
@account3 = create(:account, :month)
@account4 = create(:account, :day)
end
describe ".seen_last_two_months" do
subject { Account.seen_last_two_months.to_a }
it { is_expected.to eq([@account3, @account4]) }
end
将测试中出现的所有 account*
替换为 @account*
。
请记住,对象中任何测试所做的更改也会反映在下一个测试中。
您可以一次创建所有帐户并在其上测试您的所有范围。只需将所有内容移动到 it
块下,如下所示:
context "scopes" do
it 'selects accounts correctly' do
let!(:account1) { create(:account, :year) }
let!(:account2) { create(:account, :three_months) }
let!(:account3) { create(:account, :month) }
let!(:account4) { create(:account, :day) }
expect(Account.seen_last_two_months.to_a).to eq([account3, account4])
expect(Account.another_scope.to_a).to eq([account1, account2])
expect(Account.one_more_scope.to_a).to eq([account2])
end
end
但是这种规格更难维护,您需要在实例中指定许多不同的属性来检查所有范围。我仅将这种方式用于类似的范围,例如按状态划分的范围 ('active'、'inactive'、'archived').
顺便说一句:提供更具描述性的名称总是一个好主意,例如
let!(:year_ago) { create(:account, :year) }
我正在尝试使用 Rspec 和 FactoryBot 在 Rails 中测试我的模型。我正在使用 let!
:
let!(:account1) { create(:account, :year) }
let!(:account2) { create(:account, :three_months) }
let!(:account3) { create(:account, :month) }
let!(:account4) { create(:account, :day) }
我正在测试我的示波器是否返回了正确的记录数组。我的假设是还应该有一些 "bad data",来测试范围是否没有返回它不应该返回的东西。例如:
describe ".seen_last_two_months" do
subject { Account.seen_last_two_months.to_a }
it { is_expected.to eq([account3, account4]) }
end
看来,随着测试量的增加,速度会明显下降。似乎每个调用 let!
的测试都会命中数据库,创建一条新记录。
- 我设置测试的方式是否正确,那么我是否也应该测试查询不希望返回的记录?
- 有没有办法只调用一次创建记录?
- Is the way I'm setting up my tests correct, so should I also test for records that are not expected to be returned by the query?
是的,只是你应该。
- Is there a way to invoke the creation of the records only once?
是的,您需要创建这些记录来测试特定范围。因此,它们应该只在特定测试时被调用。您可以将它们移动到与范围测试相关的 describe
块,然后它们将不会被其他测试调用。
# Keep this one without `!`, so it will be called (in other tests), if and when needed
let(:account1) { create(:account, :year) }
describe ".seen_last_two_months" do
let!(:account1) { create(:account, :year) }
let!(:account2) { create(:account, :three_months) }
let!(:account3) { create(:account, :month) }
let!(:account4) { create(:account, :day) }
subject { Account.seen_last_two_months.to_a }
it { is_expected.to eq([account3, account4]) }
end
更新
如果您只想为所有测试创建一次记录,请改用 before(:all)
。看起来在 before(:all)
块中调用 let
定义的变量是个坏主意,请改用实例变量:
before(:all) do
@account1 = create(:account, :year)
@account2 = create(:account, :three_months)
@account3 = create(:account, :month)
@account4 = create(:account, :day)
end
describe ".seen_last_two_months" do
subject { Account.seen_last_two_months.to_a }
it { is_expected.to eq([@account3, @account4]) }
end
将测试中出现的所有 account*
替换为 @account*
。
请记住,对象中任何测试所做的更改也会反映在下一个测试中。
您可以一次创建所有帐户并在其上测试您的所有范围。只需将所有内容移动到 it
块下,如下所示:
context "scopes" do
it 'selects accounts correctly' do
let!(:account1) { create(:account, :year) }
let!(:account2) { create(:account, :three_months) }
let!(:account3) { create(:account, :month) }
let!(:account4) { create(:account, :day) }
expect(Account.seen_last_two_months.to_a).to eq([account3, account4])
expect(Account.another_scope.to_a).to eq([account1, account2])
expect(Account.one_more_scope.to_a).to eq([account2])
end
end
但是这种规格更难维护,您需要在实例中指定许多不同的属性来检查所有范围。我仅将这种方式用于类似的范围,例如按状态划分的范围 ('active'、'inactive'、'archived').
顺便说一句:提供更具描述性的名称总是一个好主意,例如
let!(:year_ago) { create(:account, :year) }