【问题标题】:Rails controller testing: Testing in isolation or integration?Rails 控制器测试:隔离测试还是集成测试?
【发布时间】: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


    【解决方案1】:

    这实际上取决于您的优先事项,以及您愿意为快速测试做出哪些牺牲。

    当您删除数据库层时,模拟数据肯定意味着更快的测试。这样做的代价是 如果 存在仅在控制器与数据持久层交互时出现的错误,您将无法捕获该错误。快速单元测试与慢速全栈多件同时工作测试。

    对此意见不一。 Rails 测试的总体趋势似乎正在转向集成/功能/系统测试并放弃大多数控制器测试。这是因为现在有更多的东西在使用高级 CSS 样式和 Javascript。没有正确答案。

    【讨论】:

      【解决方案2】:

      我会选择集成模式。

      模拟仅对长时间运行的操作有用。你不需要嘲笑一切。

      只要您使用事务性数据库清理器,创建数据库对象的时间就不会太长。

      如果您的 ActiveRecord 对象正在执行一些其他长时间操作,例如调用其他第三方服务,您可以模拟这个长时间运行的操作。

      如果您仍然认为隔离模式更适合您的情况,您可以只设置一个使用真实对象的测试用例。其他测试用例可以做模拟。在这种情况下,您提到的误报情况将被覆盖。

      【讨论】:

        【解决方案3】:

        一般来说,单元测试和自动化测试的一个典型属性是速度极快。因此,所有重量级资源都应该被嘲笑。这包括数据库。

        在设计良好的系统中,数据库是另一种存储介质,可以轻松更改而不影响大部分代码库,只有连接器需要更改。因此,从这个角度来看,数据库是一个不应该涉及自动化测试的细节。

        放弃数据库的另一个原因是,依赖数据库中本应从测试中提取的其他可用信息是一种诱惑。

        在 Rails 中,妥协是在一个隔离的数据库中运行测试,希望它足够快而不会浪费很多时间。考虑到数据库为开发人员提供的灵活性,支付这个价格是合理的。

        在采用单元测试的爆发式增长之后,许多从业者疯狂地嘲笑,并在没有正确判断将开发人员的工作引向何处以及从这种实践中获得多少收益的情况下,对一切都进行了嘲笑。对于许多雄心勃勃的开发人员来说,嘲笑本身就变成了一种技术欲望!即手段变成了目标。

        这个故事的寓意是,不要在没有深思熟虑并确保预期的努力会得到回报的情况下做任何事情。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-02-02
          • 1970-01-01
          • 1970-01-01
          • 2019-05-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-10
          相关资源
          最近更新 更多