如何构建我的 RSpec 测试文件夹、文件和数据库?
How to structure my RSpec test folders, files and database?
我已经在 RoR 中开发了一年多了,但我才刚刚开始使用测试,使用 RSpec。
对于标准的 model/controller 测试,我通常没有任何问题,但问题是我想测试一些复杂的功能流程,并且真的不知道如何构建我的测试 folders/files/database.
这是我的应用程序的基本结构:
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
主要问题是我想模拟对象的生命周期,意思是:
实例化将用于所有测试的客户和钱包(无需重新初始化)
模拟时间流,创建和更新多个orders/invoice对象和一些invoice_summaries.
对于 orders/invoices/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
我已经找到了模拟时间流的gemTimecop。因此,我想要一个易于理解的最终测试,看起来像
# 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
有人做过这样的测试吗?在构建对象工厂、编写测试等方面是否有好的实践?
例如,有人告诉我将 Customer/Wallet 创建的内容放在 db/seed.rb 文件中是个好主意,但我真的不知道该怎么做之后。
您应该使用 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 开发人员旨在解决的问题。
完全回答你的问题可以填满和填满书,所以我只能在这里概述一个答案。
关于创建要测试的对象,
db/seeds.rb 不是用于测试数据,而是用于静态数据,不被用户更改,应用程序需要 运行,无论是在开发还是测试或生产。此类数据的常见示例包括国家代码和用户角色。
创建测试数据、固定装置和工厂有两种通用方法。
- 夹具是开箱即用的 Rails 方法。它们在创建测试数据库时创建一次。当您有很多测试时它们很快,因为它们只在测试套件开始时创建一次。但是,它们扭曲了测试,因为它们鼓励围绕现有固定装置编写测试,因此我强烈建议不要使用它们。
- 工厂是对象创建实用程序。您在每个测试中创建所需的对象,并在最后删除或回滚它们。大多数使用工厂的 Rails 项目都使用 FactoryGirl。这就是我的建议。
这两种方法都将对象创建代码放在其自己的文件中,该文件位于与您的规范不同的目录中,这样可以防止它破坏您的规范文件。如果您在 SO 中搜索 "fixtures or factories",您会发现更多关于两者的讨论。
如果您将所有重要值(在本例中包括日期和金额)放在可以看到的规格中并与您断言的结果进行比较,您的规格将更容易理解。 (否则您需要记住测试对象中的日期和数量才能理解规格。)您可以在测试 date
和 amount
参数中提供对象创建方法。那么你可能需要更少的方法。如果您使用 FactoryGirl,则可能只是指定每个对象的 created_at
和 amount
属性的问题。另请注意,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
...
...
如果您的单个 class 规格太大,则表明 class 太大。找到一些模式将其分解并单独测试各个部分。如果你真的不能分解class(一种罕见的情况),把class的spec文件变成一个spec文件的目录,就像我在.[=中描述的那样17=]
我已经在 RoR 中开发了一年多了,但我才刚刚开始使用测试,使用 RSpec。
对于标准的 model/controller 测试,我通常没有任何问题,但问题是我想测试一些复杂的功能流程,并且真的不知道如何构建我的测试 folders/files/database.
这是我的应用程序的基本结构:
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
主要问题是我想模拟对象的生命周期,意思是:
实例化将用于所有测试的客户和钱包(无需重新初始化)
模拟时间流,创建和更新多个orders/invoice对象和一些invoice_summaries.
对于 orders/invoices/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
我已经找到了模拟时间流的gemTimecop。因此,我想要一个易于理解的最终测试,看起来像
# 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
有人做过这样的测试吗?在构建对象工厂、编写测试等方面是否有好的实践?
例如,有人告诉我将 Customer/Wallet 创建的内容放在 db/seed.rb 文件中是个好主意,但我真的不知道该怎么做之后。
您应该使用 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 开发人员旨在解决的问题。
完全回答你的问题可以填满和填满书,所以我只能在这里概述一个答案。
关于创建要测试的对象,
db/seeds.rb 不是用于测试数据,而是用于静态数据,不被用户更改,应用程序需要 运行,无论是在开发还是测试或生产。此类数据的常见示例包括国家代码和用户角色。
创建测试数据、固定装置和工厂有两种通用方法。
- 夹具是开箱即用的 Rails 方法。它们在创建测试数据库时创建一次。当您有很多测试时它们很快,因为它们只在测试套件开始时创建一次。但是,它们扭曲了测试,因为它们鼓励围绕现有固定装置编写测试,因此我强烈建议不要使用它们。
- 工厂是对象创建实用程序。您在每个测试中创建所需的对象,并在最后删除或回滚它们。大多数使用工厂的 Rails 项目都使用 FactoryGirl。这就是我的建议。
这两种方法都将对象创建代码放在其自己的文件中,该文件位于与您的规范不同的目录中,这样可以防止它破坏您的规范文件。如果您在 SO 中搜索 "fixtures or factories",您会发现更多关于两者的讨论。
如果您将所有重要值(在本例中包括日期和金额)放在可以看到的规格中并与您断言的结果进行比较,您的规格将更容易理解。 (否则您需要记住测试对象中的日期和数量才能理解规格。)您可以在测试 date
和 amount
参数中提供对象创建方法。那么你可能需要更少的方法。如果您使用 FactoryGirl,则可能只是指定每个对象的 created_at
和 amount
属性的问题。另请注意,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
...
...
如果您的单个 class 规格太大,则表明 class 太大。找到一些模式将其分解并单独测试各个部分。如果你真的不能分解class(一种罕见的情况),把class的spec文件变成一个spec文件的目录,就像我在