【问题标题】:How to structure my RSpec test folders, files and database?如何构建我的 RSpec 测试文件夹、文件和数据库?
【发布时间】:2015-08-10 13:46:17
【问题描述】:

我已经在 RoR 开发一年多了,但我才刚刚开始使用测试,使用 RSpec。

对于标准模型/控制器测试,我通常没有任何问题,但问题是我想测试一些复杂的功能流程,并且不知道如何构建我的测试文件夹/文件/数据库。

这是我的应用程序的基本结构:

class Customer
  has_one    :wallet
  has_many :orders    
  has_many :invoices, through: :orders
  has_many :invoice_summaries
end

class Wallet
  belongs_to :customer
end

class Order
  has_one    :invoice
  belongs_to :customer
end

class Invoice
  belongs_to :order
  belongs_to :invoice_summary
end

class InvoiceSummary
  belongs_to :customer
  has_many  :invoices
end

主要问题是我想模拟我的对象的生命周期,意思是:

  • 实例化将用于所有测试的客户和钱包(无需重新初始化)

  • 模拟时间流,创建和更新多个订单/发票对象和一些 invoice_summaries。

对于订单/发票/发票摘要的创建和更新,我希望有类似的方法

def create_order_1
  # code specific to create my first order, return the created order
end

def create_order_2
  # code specific to create my second order, return the created order
end
.
.
.
def create_order_n
  # code specific to create my n-th order, return the created order
end

def bill_order(order_to_bill)
  # generic code to do the billing of the order passed as parameter
end

def cancel_order(order_to_cancel)
  # generic code to cancel the order passed as parameter
end

我已经找到了用于模拟时间流的 gem Timecop。因此,我想要一个易于理解的最终测试,看起来像

# Code for the initialization of customers and wallets object

describe "Wallet should be equal to 0 after first day" do
  Timecop.freeze(Time.new(2015,7,1))
  first_request = create_request_1
  first_request.customer.wallet.value.should? == 0
end

describe "Wallet should be equal to -30 after second day" do
  Timecop.freeze(Time.new(2015,7,2))
  bill_order(first_request)
  second_order = create_order_2
  first_request.customer.wallet.value.should? == -30
end

describe "Wallet should be equal to -20 after third day" do 
  Timecop.freeze(Time.new(2015,7,3))
  bill_order(second_request)
  cancel_order(first_request)
  first_request.customer.wallet.value.should? == -20
end

describe "Three first day invoice_summary should have 3 invoices" do
  Timecop.freeze(Time.new(2015,7,4))
  invoice_summary = InvoiceSummary.create(
      begin_date: Date.new(2015,7,1),
      end_date: Date.new(2015, 7,3)
  ) # real InvoiceSummary method
  invoice_summary.invoices.count.should? == 3
end

有人已经做过这样的测试了吗?在对象工厂的结构化、编写测试等方面有没有好的实践?

例如,有人告诉我,一个好主意是将客户/钱包的创建放在 db/seed.rb 文件中,但我真的不知道之后该怎么处理。

【问题讨论】:

  • @HunterStevens 我相信你的编辑是错误的,因为你删除了一些关于订单的逻辑。 def create_order_n; enddef create_order_1; end def create_order_2;end 不同,因为 @vincent 想要表达做两件完全不同的事情的需要。在这样编辑之前你应该小心......
  • @Erowlin 请回滚我的编辑。我试图清理一个很长的帖子。对不起。
  • NP,下次小心点;)。顺便说一句,感谢您的编辑,这是出于好意!

标签: ruby-on-rails ruby rspec rspec-rails functional-testing


【解决方案1】:

完全回答你的问题可以填满书籍,所以我只能在这里概述一个答案。

关于创建要测试的对象,

  • db/seeds.rb 不是用于测试数据,而是用于不由用户更改的静态数据,这是应用程序运行所必需的,无论是在开发、测试还是生产中。此类数据的常见示例包括国家/地区代码和用户角色。

  • 有两种创建测试数据、夹具和工厂的通用方法。

    • Fixtures 是开箱即用的 Rails 方法。它们在创建测试数据库时创建一次。当您有许多测试时,它们会很快,因为它们只在测试套件开始时创建一次。但是,它们会扭曲测试,因为它们鼓励围绕现有夹具编写测试,因此我强烈建议不要使用它们。
    • 工厂是对象创建实用程序。您在每个测试中创建所需的对象,并在最后删除或回滚它们。大多数使用工厂的 Rails 项目都使用 FactoryGirl。这就是我的建议。

    这两种方法都将对象创建代码放在其自己的文件中,与您的规范不同的目录中,这样可以防止它破坏您的规范文件。如果您在 SO 中搜索“夹具或工厂”,您会发现更多关于两者的讨论。

如果您将所有重要的值(在这种情况下包括日期和金额)都放在可以看到的规格中并与您声明的结果进行比较,您的规格将更容易理解。 (否则您需要记住测试对象中的日期和数量以了解规格。)您可以在测试dateamount 参数中提供对象创建方法。那么你可能需要更少的方法。如果您使用 FactoryGirl,则可能只是指定每个对象的 created_atamount 属性的问题。另请注意,Rails 有类似1.day.from_now 的方法;如果您以这种方式创建具有指定日期的对象,则可能不需要 timecop。

关于如何在文件系统中布局 RSpec 规范,在顶层,只需使布局与 Rails 应用程序的布局相同:

app/
  controllers/
    bars_controller.rb
    foos_controller.rb
  models/
    bar.rb
    foo.rb
    ...
  ...

spec/
  controllers/
    bars_controller_spec.rb
    foos_controller_spec.rb
    ...
  models/
    bar_spec.rb
    foo_spec.rb
    ...
  ...

如果您对单个类的规格太大,则表明该类太大了。找到一些模式将其分解并单独测试各个部分。如果你真的不能拆分类(很少见),把类的 spec 文件变成一个 spec 文件的目录,就像我在 How to break down super long specs in RSpec? 中描述的那样。

【讨论】:

【解决方案2】:

您应该为您的任务使用FactoryGirl。按照文档中的说明进行配置,然后像这样使用它:

# factories.rb
factory :order do
end

# your spec
first_order = create(:order, ...) # configure parameters of order in-place

或者让特定的工厂处理不同类型的请求:

# factories.rb
factory :expensive_order, class: Order do
  amount 999 # have 'amount' field of Order be equal to 999
end

# your spec
first_order = create(:expensive_order)

您可以让 FactoryGirl 自动处理您的关联:

factory :order do
  association :user # automatically create User association
end

您正在描述 FactoryGirl 的开发人员旨在解决的确切问题。

【讨论】:

    猜你喜欢
    • 2016-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-21
    • 1970-01-01
    • 2012-05-18
    相关资源
    最近更新 更多