使用 factory_girl 设置用于模型测试的对象
setting up objects for model testing with factory_girl
我想测试下面的示波器,但不知道如何正确设置测试对象。我什至不知道我是否应该在工厂中创建那些与某些关联或使用 let/local vars.
task.rb
belongs_to :assigner, class_name: "User"
belongs_to :executor, class_name: "User"
scope :completed, -> { where.not(completed_at: nil) }
scope :uncompleted, -> { where(completed_at: nil) }
scope :alltasks, -> (u) { where('executor_id = ? OR assigner_id = ?', u.id, u.id) }
scope :between, -> (assigner_id, executor_id) do
where("(tasks.assigner_id = ? AND tasks.executor_id = ?) OR (tasks.assigner_id = ? AND tasks.executor_id = ?)", assigner_id, executor_id, executor_id, assigner_id)
end
工厂
factory :task do
content { Faker::Lorem.sentence }
deadline { Faker::Time.between(DateTime.now + 2, DateTime.now + 3) }
association :executor, factory: :user
association :assigner, factory: :user #I guess this is not right
end
factory :user do
sequence(:email) { |n| "example#{n}@gmail.com" }
password 'example0000'
password_confirmation 'example0000'
new_chat_notification { Faker::Number.between(0, 10) }
new_other_notification { Faker::Number.between(0, 10) }
end
task_spec.rb
describe Task do
.....
describe "scopes" do
#I DON'T KNOW HOW TO SETUP THE TEST OBJECTS PROPERLY
let(:uncompleted_task) { create(:task) }
let(:completed_task) { create(:task, completed_at: DateTime.now - 2) }
let(:user) { create(:user)
let(:task_1) { create(:task, executor_id: user.id, assigner_id: (user.id + 1)) }
let(:task_2) { create(:task, assigner_id: user.id, executor_id: (user.id + 1) }
let(:task_3) { create(:task, assigner_id: user.id, executor_id: (user.id + 2) }
it "completed" do
expect(completed_task).to eq(Task.completed.first)
end
it "uncompleted" do
expect(uncompleted_task).to eq(Task.uncompleted.last)
end
it "alltasks" do
end
it "between" do
end
end
end
首先,我不喜欢在工厂里建立协会,原因如下:
- 如果一个工厂里有关联,那么每次一个对象created/built,关联的对象都会同时被创建。这意味着即使您只是 'build' 一个对象,也会触发许多数据库访问。您使用的关联越多,运行 测试所需的时间就越多。
- 你应该在真正需要的时候在测试中设置关联。如果您在工厂中设置关联,则意味着测试中隐藏了 given-when-then 过程的 'given' 部分。当测试失败时,更难找到问题所在。
要测试示波器,您应该遵循一些最佳实践:
- 让你的作用域尽可能简单,复杂的作用域更难测试。例如,如果需要,您应该将
between
范围分解为较小的范围。
- 使用最小对象来测试范围,因为我们需要在数据库中创建记录来测试范围。数据库访问会减慢测试速度,我们应该尽可能减少对象创建。对于大多数情况,两个对象(正面和负面)应该足以测试一个简单的范围。
对了,大家可能会误解let
的用法,用let定义的对象只有在测试用例中调用该对象时才会创建。在您的原始规范中,没有创建任何任务或用户,因为调用了 none 个任务或用户。做你想做的事,你应该使用 before
块。
对于您的示例,我将在下面创建工厂和规范:
工厂
factory :task do
content { Faker::Lorem.sentence }
deadline { Faker::Time.between(DateTime.now + 2, DateTime.now + 3) }
end
factory :user do
sequence(:email) { |n| "example#{n}@gmail.com" }
sequence(:password) { |n| "password#{n}" }
password_confirmation { password }
new_chat_notification { Faker::Number.between(0, 10) }
new_other_notification { Faker::Number.between(0, 10) }
end
规格
describe Task do
# ...
describe "scopes" do
let(:executor) { create(:user) }
let(:assigner) { create(:user) }
describe "completeness related" do
before(:example) do
@uncompleted_task = create(:task, executor: executor, assigner: assigner)
@completed_task = create(:task, executor: executor, assigner: assigner, completed_at: DateTime.now - 2))
end
it "completed" do
expect(Task.completed).to include(@completed_task)
expect(Task.completed).not_to include(@uncompleted_task)
end
it "uncompleted" do
expect(Task.uncompleted).to include(@uncompleted_task)
expect(Task.uncompleted).not_to include(@completed_task)
end
end
it "alltasks" do
me = create(:user)
owned_task = create(:task, executor: me, assigner: assigner)
assigned_task = create(:task, executor: executor, assigner: me)
not_my_task = create(:task, executor: executor, assigner: assigner)
expect(Task.alltasks(me)).to include(owned_task, assigned_task)
expect(Task.alltasks(me)).not_to include(not_my_task)
end
it "between" do
# ... (there should be as last 5 tasks to create)...
end
end
# ...
end
"Rails 4 Test Prescriptions - Build a Healthy Codebase by Noel Rappin"这本书里有更多关于如何写测试的细节和点击率,有兴趣的可以看看。
我想测试下面的示波器,但不知道如何正确设置测试对象。我什至不知道我是否应该在工厂中创建那些与某些关联或使用 let/local vars.
task.rb
belongs_to :assigner, class_name: "User"
belongs_to :executor, class_name: "User"
scope :completed, -> { where.not(completed_at: nil) }
scope :uncompleted, -> { where(completed_at: nil) }
scope :alltasks, -> (u) { where('executor_id = ? OR assigner_id = ?', u.id, u.id) }
scope :between, -> (assigner_id, executor_id) do
where("(tasks.assigner_id = ? AND tasks.executor_id = ?) OR (tasks.assigner_id = ? AND tasks.executor_id = ?)", assigner_id, executor_id, executor_id, assigner_id)
end
工厂
factory :task do
content { Faker::Lorem.sentence }
deadline { Faker::Time.between(DateTime.now + 2, DateTime.now + 3) }
association :executor, factory: :user
association :assigner, factory: :user #I guess this is not right
end
factory :user do
sequence(:email) { |n| "example#{n}@gmail.com" }
password 'example0000'
password_confirmation 'example0000'
new_chat_notification { Faker::Number.between(0, 10) }
new_other_notification { Faker::Number.between(0, 10) }
end
task_spec.rb
describe Task do
.....
describe "scopes" do
#I DON'T KNOW HOW TO SETUP THE TEST OBJECTS PROPERLY
let(:uncompleted_task) { create(:task) }
let(:completed_task) { create(:task, completed_at: DateTime.now - 2) }
let(:user) { create(:user)
let(:task_1) { create(:task, executor_id: user.id, assigner_id: (user.id + 1)) }
let(:task_2) { create(:task, assigner_id: user.id, executor_id: (user.id + 1) }
let(:task_3) { create(:task, assigner_id: user.id, executor_id: (user.id + 2) }
it "completed" do
expect(completed_task).to eq(Task.completed.first)
end
it "uncompleted" do
expect(uncompleted_task).to eq(Task.uncompleted.last)
end
it "alltasks" do
end
it "between" do
end
end
end
首先,我不喜欢在工厂里建立协会,原因如下:
- 如果一个工厂里有关联,那么每次一个对象created/built,关联的对象都会同时被创建。这意味着即使您只是 'build' 一个对象,也会触发许多数据库访问。您使用的关联越多,运行 测试所需的时间就越多。
- 你应该在真正需要的时候在测试中设置关联。如果您在工厂中设置关联,则意味着测试中隐藏了 given-when-then 过程的 'given' 部分。当测试失败时,更难找到问题所在。
要测试示波器,您应该遵循一些最佳实践:
- 让你的作用域尽可能简单,复杂的作用域更难测试。例如,如果需要,您应该将
between
范围分解为较小的范围。 - 使用最小对象来测试范围,因为我们需要在数据库中创建记录来测试范围。数据库访问会减慢测试速度,我们应该尽可能减少对象创建。对于大多数情况,两个对象(正面和负面)应该足以测试一个简单的范围。
对了,大家可能会误解let
的用法,用let定义的对象只有在测试用例中调用该对象时才会创建。在您的原始规范中,没有创建任何任务或用户,因为调用了 none 个任务或用户。做你想做的事,你应该使用 before
块。
对于您的示例,我将在下面创建工厂和规范:
工厂
factory :task do
content { Faker::Lorem.sentence }
deadline { Faker::Time.between(DateTime.now + 2, DateTime.now + 3) }
end
factory :user do
sequence(:email) { |n| "example#{n}@gmail.com" }
sequence(:password) { |n| "password#{n}" }
password_confirmation { password }
new_chat_notification { Faker::Number.between(0, 10) }
new_other_notification { Faker::Number.between(0, 10) }
end
规格
describe Task do
# ...
describe "scopes" do
let(:executor) { create(:user) }
let(:assigner) { create(:user) }
describe "completeness related" do
before(:example) do
@uncompleted_task = create(:task, executor: executor, assigner: assigner)
@completed_task = create(:task, executor: executor, assigner: assigner, completed_at: DateTime.now - 2))
end
it "completed" do
expect(Task.completed).to include(@completed_task)
expect(Task.completed).not_to include(@uncompleted_task)
end
it "uncompleted" do
expect(Task.uncompleted).to include(@uncompleted_task)
expect(Task.uncompleted).not_to include(@completed_task)
end
end
it "alltasks" do
me = create(:user)
owned_task = create(:task, executor: me, assigner: assigner)
assigned_task = create(:task, executor: executor, assigner: me)
not_my_task = create(:task, executor: executor, assigner: assigner)
expect(Task.alltasks(me)).to include(owned_task, assigned_task)
expect(Task.alltasks(me)).not_to include(not_my_task)
end
it "between" do
# ... (there should be as last 5 tasks to create)...
end
end
# ...
end
"Rails 4 Test Prescriptions - Build a Healthy Codebase by Noel Rappin"这本书里有更多关于如何写测试的细节和点击率,有兴趣的可以看看。