FactoryGirl与深度关联链的最佳实践?

时间:2013-05-11 00:33:16

标签: ruby-on-rails ruby rspec bdd factory-bot

我正在为Rails中的复杂采购工作流建模,将申请转换为订单。我正在使用FactoryGirl进行测试,一切顺利,直到我尝试测试OrderLineItem,它依赖于Order和Quote,每个依赖于其他对象,依此类推......

有问题的测试会检查受产品影响的OrderLineItem上的行为,这是链上方的几个关联。

有没有一种很好的方法来设置FactoryGirl,这样我就可以轻松构建OrderLineItems,并且还可以指定链中更高层对象的行为,而无需一次分解每个对象?

这是我的对象图:

class Requisition
  has_many :requisition_line_items
  has_many :orders
end

class RequisitionLineItem
  belongs_to :requisition
  belongs_to :product
  has_many :quotes
end

class Quote
  belongs_to :line_item
  belongs_to :vendor
  has_one :order_line_item
end

class Order
  belongs_to :requisition
  belongs_to :vendor
  has_many :order_line_items
end

class OrderLineItem
  belongs_to :order
  belongs_to :quote
  has_many :assets
end

class Asset
  belongs_to :order_line_item
  belongs_to :product
end

class Product
  has_many :assets
end

class Vendor
  has_many :orders
end

看似复杂的模型允许根据供应商的报价将购买“提案”转换为一个或多个实际订单,当项目到达时,它们将被赋予资产标签。然后,资产本身可以链接回订单和供应商,以便稍后获得支持。

这是我的OrderLineItem规范,我有一个相当简洁的设置:

describe '#requires_tag?' do

  let(:product)              { FactoryGirl.create :product, requires_tag: false }
  let(:purchase_requisition) { FactoryGirl.create :purchase_requisition }
  let(:line_item)            { FactoryGirl.create :line_item, 
                                 purchase_requisition: purchase_requisition, 
                                 product: product }
  let(:quote)                { FactoryGirl.create :quote, 
                                 line_item: line_item, unit_price: 0 }

  subject { FactoryGirl.build :order_line_item, quote: quote }

  context 'when neither product nor price require a tag' do
    its(:requires_tag?) { should be_false }
  end

  context 'when product requires a tag' do
    let(:product) { FactoryGirl.create :product, requires_tag: true }
    its(:requires_tag?) { should be_true }
  end

end

我真的需要无数let语句,还是有更好的方法来构建OrderLineItem并对其所依赖的产品属性施加控制?

2 个答案:

答案 0 :(得分:3)

我不同意w / cpuguy。我同意demeter定律是一件很棒的事情,但由于关系数据库与您存储的分层数据之间的阻抗不匹配,您的对象图似乎只是违反了它。

如果这里有一些可以重构的东西,它可能是你的模型结构,或者你的模型存储机制。您遇到的Demeter问题表明您正在使用关系系统来建模分层数据模型。考虑所有订单信息是否只是一个大哈希。我不认为你会感受到同样的痛苦程度。唯一的替代方法是尝试将其中一些字段复制到您正在使用它们的位置。

我实际上认为你的规格非常好,因为: a)他们是行为,测试听起来像不太可能改变的业务功能的离散元素 b)如果你的规格需要嘲笑内部,那么规范在重构方面就变得毫无用处,因为你的期望必须随之改变。

主要问题在于如何构建测试环境。您可以将它们抽象到更高级别的工厂中,但要小心不要隐藏使您的规范独特的内容。你也做得很好。我可能建议的一个建议是在每个上下文中创建一个let(:requires_tag),一个使用true,一个使用false。然后在设置中保留其他所有内容。这样就很清楚每个上下文如何与主要设置不同,这可能需要更长的时间才能完成。

除此之外,如果有更好的方法,我还没有找到它。

答案 1 :(得分:1)

我会说尝试重构这个,所以你正在测试一件事。如果你这样做,你不需要创建所有这些对象(反过来减慢你的测试速度)。

如果你没有违反LoD https://www.google.com/search?q=law+of+demeter&ie=UTF-8&oe=UTF-8&hl=en&client=safari#itp=open0,那么这就容易多了。

您应该能够根据需要存储您在相关对象上调用的方法,而不是创建实际的关系。