【发布时间】:2017-11-14 21:46:17
【问题描述】:
在测试 Rails 控制器时,我有点困惑,是单独测试它们(模拟,更少的 DB 命中:更快),还是以集成方式(没有模拟,很多 DB 命中:更慢)。
假设我们有这个例子:
class ProductsController
def create
product = Product.new(product_params)
if product.save
redirect_to root_path
else
render :new
end
end
private
def product_params
require(:product).permit(:name, :price)
end
end
RSpec.describe ProductsController do
describe '#create in isolation' do
it "pass" do
params = { name: "name", price: 100}
mocked_product = double(save: true)
expect(Product).to receive(:new).with(params).and_return(mocked_product)
post :create, params: { product: params }
expect(response).to be_redirect
end
end
describe '#create in integration' do
it "pass" do
params = { name: "name", price: 100}
post :create, params: { product: params }
expect(response).to be_redirect
end
end
end
单独测试不会影响数据库,这会使其更快,但是如果在产品模型中呢? 我们添加了一个必需的描述字段,我们忘记将它添加到 product_params 方法中,这将 导致产品记录没有收到描述,保存时无效。
然后第一个测试将毫无问题地通过,误报我们的控制器代码没问题,实际上 这不是因为我们必须更改 product_params 方法才能使其工作
第二个测试将失败,因为我们没有模拟创建产品。
我认为这是一个开放的问题/主题,所以我想知道在进行控制器测试时是否有最佳实践。
EDIT(概括思路)
我认为这可以概括,因为它不仅限于控制器,而是孤立测试的概念,基本思想如下:
假设我们有一个函数 func(x,y),这个函数有它自己的单元测试。然后,当我们有另一个 servive(S) 调用 func 时,我们将在其他地方测试时仅存根 func 调用,以节省执行 func 的时间(尤其是当它消耗时间时,例如 DB 命中!)
expect(func).to receive(some_x, some_y).and_return(some_value)
这是隔离测试的基本思想。
这个问题,如果 func 的方法签名没有改变怎么办,但是现在从 S 传递给 func 的参数将使 func 抛出一些异常,由于 func 本身的一些实现更改,其中 S 仍在调用func 使用旧的参数值,现在将导致异常。由于我们模拟了对 func 的调用,因此 S 测试不会捕捉到这个错误并且代码将被成功部署。
我相信这是一般模式
【问题讨论】:
标签: ruby-on-rails unit-testing testing rspec mocking