如何在Factory Girl或Minifacture中使用随机唯一数据制作Ruby测试工厂?

joe*_*son 9 ruby rspec ruby-on-rails minitest factory-bot

我正在使用典型工厂测试典型的Rails模型:

# My model uses a 3-letter uppercase airport code,
# such as "ATL" for Atlanta, "BOS" for Boston, etc.

class Airport < ActiveRecord::Base
  validates :code, uniqueness: true

Factory.define :airport do |f|
  f.code { random_airport_code }  # Get a 3-letter uppercase code
Run Code Online (Sandbox Code Playgroud)

我正在添加更多测试并开始在机场代码中看到碰撞:例如工厂创建一个代码为"XYZ"的机场,然后工厂的后续调用尝试创建具有相同代码的机场.

序列是解决这个问题的一种方法.例如,使用Factory Girl序列,有序列表或预先计算的枚举,一些类似的方式来维护下一个可用代码的状态.

我的问题是:什么是非序列方法来解决这个问题?我想使用随机数据,而不是序列.

我正在尝试的一些想法因为它们是务实的 - 对这些的任何见解都非常感激.

使用乐观锁定的示例构思

while 
  airport = Factory.build :airport
  airport.save && return airport
end
Run Code Online (Sandbox Code Playgroud)

优点:实践快速,因为碰撞很少见; 当地的州.

缺点:笨拙的语法; 非本地工厂; 由于碰撞以外的原因,保存可能会失败.

使用事务的示例构思

Airport.transaction 
  while
    x = random_airport_code
    if Airport.exists?(code: x)
      next
    else
      Factory :airport, code: x
      break
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

优点:这是我最想要的; 地方国家; 确保没有碰撞.

缺点:漫长的尴尬语法.

赏金

Factory Girl或Minifacture是否有任何类型的语法更适合随机数据,而不是序列?

或者,如果存在保存冲突,可能会有某种模式自动重新掷骰子?

一些开销对我很好.实际上,每天都会发生一次碰撞,在数千次测试的持续集成设置中.如果测试套件必须重新掷骰子几次,或探测数据库中的现有值等,那很好.

评论询问为什么随机数据而不是序列.我更喜欢随机数据,因为我的经验是随机数据可以带来更好的测试,更好的长期可维护性以及更好的语义和测试目标.此外,我使用Faker和Forgery代替灯具,以防有助于了解.

要获得赏金,答案必须是随机的 - 而不是序列.(例如,我正在寻找的解决方案可能使用#sample和/或无序集合,并且可能不会使用#shuffle和/或有序集合)

Ger*_*osi 6

你可以使用回调.就像是:

factory :airport do
  after(:build) do |airport|
    airport.code = loop do
      code = ('AAA'..'ZZZ').to_a.sample
      break code unless Airport.exists?(code: code)
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

您可能想要更改after(:build)before(:create),这取决于您希望如何使用工厂.


Dav*_*uth 1

是的,FactoryGirl 有一个功能可以让您做到这一点。请参阅序列文档的末尾:https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#sequences您可以将序列设置为任何知道如何在 # 时返回自身增量版本的对象。接下来是调用它。因此,您可以编写一个知道如何返回唯一随机数据并实现#next 的类,例如

class AirportCode
  ALL = %w(AAA BBB CCC).shuffle

  attr_reader :index

  def initialize(index = rand(ALL.length))
    @index = index
  end

  def value
    ALL[@index]
  end

  def to_s
    value
  end

  # might need to explicitly delegate more methods to the value

  def method_missing(method, *args)
    value.send method, *args
  end

  def next
    AirportCode.new((index + 1) % ALL.length)
  end

end
Run Code Online (Sandbox Code Playgroud)

(这个只有三个唯一值,但这只是为了说明这一点),创建一个 FactoryGirl 序列并将其值设置为该类的一个实例。我没有尝试 FactoryGirl 部分,所以请报告它是否有效:)