FactoryGirl 工厂可以重用现有模型作为关联

FactoryGirl factory that may reuse existing models as association

假设我想 运行 测试并创建许多 Student。每个学生属于一所学校。学校名是Faker给的gem,但是名额有限,学生都关联到这些学校

我能否以让我重新使用现有学校的方式使用 FactoryGirl? IE。一个新的 FactoryGirl.create(:student) 分配给

.

class Student
  belongs_to :school, class_name: 'SchoolSociety'
end

class School
  has_many :students
  field :name
end

我认为这与此无关,但我使用的是 Mongoid。我的工厂看起来像

FactoryGirl.define do
  factory :student, class: Student do
    association(:school, factory: :school)
  end
  factory :school, class: School do 
    name { Faker::University.name }
  end
end

一个解决方案是使用 School.where(name: Faker::University.name) 但我会失去 FactoryGirls 工厂的所有灵活性... 有更好的解决方法吗?

上下文是运行宁黄瓜测试与许多学生

编辑:

我的实际黄瓜案例正在测试根据涉及当前时间和注册用户数(在学校下注册)的数学公式增加的累积奖金。我在做一些类似

的事情
Scenario Outline: Jackpot increases with registrations and time
    Given the current date is <date>
    And <count> students have registered for the special event
    When I am on the special event page
    Then I should see "<jackpot> €"

    Examples:
    | date | count | jackpot |
    | 2016/11/24 15:00:00 |  5 | 4 9 5 , 3 0 |
    | 2016/11/30 15:00:00 |  10 | 4 9 7 , 6 0 |
    | 2016/12/10 15:00:00 |  20 | 5 0 2 , 2 0 |
    | 2016/12/10 15:00:00 |  150 | 6 5 2 , 2 0 |

现在,那些 <count> students have registered for the special event 必须是属于现有学校的学生(这意味着,在注册过程中,他们必须使用域名 exists/is 可映射到 School 在我们的数据库中)

在使用 Cucumber 时,您应该具体而准确地说明您的 Givens,而不是让工厂规则决定创建什么。所以如果你想在同一所学校创建几个学生,我会

Given there is a school with several students

如果你想有几所学校有学生

Given there are several schools with students

如果你想让一个学生在不止一所学校就读

Given there is a student enrolled in two schools

显然,您可以通过以下方式创建多样化的交易

  1. 命名学校
  2. 命名学生
  3. 数量不等

当您实施这些步骤时,尤其是当您开始时,很容易将所有代码放在步骤中完成工作。抵制这一点,而是在步骤中进行调用并使调用与步骤描述相匹配。例如

 Given 'there is a school with several students' do 
  @school = create_school
  @students = []
  several.times do 
    @students << create_student school: school
  end
end

这一步使用了create_schoolcreate_student两种方法。您可以在辅助模块中定义这些

module SchoolsStepHelper
  def create_school
    ...

  def create_student(school: ...)
    ...
end
World SchoolsStepHelper

现在您可以准确了解如何创建学生,以及何时遇到新事物,例如一个学生在两所学校就读,你可以 add/modify 你的方法来获得这个额外的功能,例如你可以添加

def enroll_student(student: school:)
  ...

这样我们就可以做到

Given 'there is a student enrolled in two schools' do
  @student = create_student
  @school_1 = create_school
  @school_2 = create_schoo
  enroll_student(student: @student, school: @school_1)
  enroll_student(student: @student, school: @school_1)
end

但是现在我们的步骤定义中仍然有太多代码,所以我们需要重构。这里我们有几个选择

  1. 将这个复合步骤分解为更简单的步骤,以便学生和学校已经存在,例如

    假设有一所学校哈佛 还有耶鲁学校 还有一个学生弗雷德 Fred 就读于耶鲁和哈佛。

您会这样做以使您的场景更具描述性,尤其是在开发注册功能时

  1. 提取更高层次的方法

    鉴于'there is a student enrolled in two schools'做 @学生= create_dual_enrolled_student 结束

无论您选择哪种方法,您都将重复使用我们在本答案开头创建的简单方法。

现在你关于工厂的问题基本上是一个实现细节,关于你如何创造事物。应该可以

  1. 实施一个解决方案,只需很少或不需要了解工厂即可了解正在发生的事情。 (在你的方法中只是非常简单的工厂调用)

  2. 实现一个甚至不使用工厂的解决方案(这是我喜欢的方法,但那是另外一回事了)。

最后,如果您采用这种方法并且所有步骤定义都作为简单调用实现,那么即使您有很多类似的步骤定义也没有关系,例如

Given there is a student
Given Fred is a student
Given there is a student Fred
Given there is a student Sue

您可以为其中的每一个定义一个步骤,而不会产生重复,因为进行调用不是重复,而且额外步骤的成本几乎与实现的简单性和不需要参数或正则表达式相平衡。

Given 'there is a student' do
  create_student
end
Given 'Fred is a student' do
  create_student name: 'Fred'
end
Given there is a student Fred do
  create_student name: Fred
end
Given 'there is a student Sue' do
  create_student name: 'Sue'
end

哎呀,这个答案很长,我希望它有用。