简体   繁体   English

Rails控制器测试:隔离还是集成测试?

[英]Rails controller testing: Testing in isolation or integration?

I'm a bit confused when testing Rails controller, whether to test them in isolation(mocking, less DB hits: faster), or in an integration way(no mocking, a lot of DB hits: slower). 在测试Rails控制器时,我是困惑的,是孤立地(模拟,减少数据库命中率:更快),还是以集成方式(无嘲笑,大量数据库命中率:更慢)进行测试。

Say we have this example: 说我们有这个例子:

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

Testing in isolation doesn't hit the DB which makes it faster, however what if in Product model we added a description field that is required, where we forget to add it to product_params method, which will result in Product record not receiving description, and it won't be valid when saved. 隔离测试不会影响数据库,这会使其更快,但是,如果在Product模型中添加了必需的description字段,而忘记将其添加到product_params方法中,将会导致Product记录未收到描述,怎么办?保存后将无效。

Then the first test will pass with no problems, making a false positive that our controller code is okay, where actually it is not because we have to change product_params method for it to work 然后,第一个测试将毫无问题地通过,从而错误地肯定我们的控制器代码是可以的,实际上不是因为我们必须更改product_params方法才能使其正常工作

The second test will fail because we didn't mock creating the product. 第二项测试将失败,因为我们没有模拟产品的创建。

I think this is an open question/topic any way, so I wonder if there is best practices when doing controller tests. 我认为这无论如何都是一个悬而未决的问题,所以我想知道进行控制器测试时是否有最佳实践。

EDIT(generalizing the idea) 编辑(概括想法)

I think this can be generalized as it is not limited to controllers but the concept of testing in isolation, the basic idea is the following: 我认为这是可以概括的,因为它不仅限于控制器,还包括隔离测试的概念,其基本思想如下:

Assume we have a function func(x,y), this function has its own units tests. 假设我们有一个函数func(x,y),这个函数有自己的单元测试。 Then when we have another servive(S) calling func, we will just stub func call as it is tested elsewhere, in order to save time from executing func (especially when it consumes time, like DB hits!) 然后,当我们有另一个servive(s)调用func时,我们将在其他地方对其进行测试时对它进行存根,以节省执行func的时间(尤其是在消耗时间时,例如数据库命中!)

expect(func).to receive(some_x, some_y).and_return(some_value) 期望(func)。接收(some_x,some_y).and_return(some_value)

This is the basic idea of testing in isolation. 这是隔离测试的基本思想。

The problem with this, what if the method signature of func didn't change, however the arguments passed to func from S now will make func throw some exception, due to some implementation changes in func itself, where S is still calling func with the old arguments values, which will result in exception now. 这样的问题是,如果func的方法签名没有更改,但是现在从S传递给func的参数将使func抛出一些异常,这是由于func本身的一些实现更改所致,其中S仍在使用旧的参数值,现在将导致异常。 And since we mocked calls to func, S test won't catch this error and the code will be deployed successfully. 由于我们模拟了对func的调用,因此S test不会捕获此错误,并且代码将成功部署。

I believe this is the general pattern 我相信这是一般的模式

It really depends on what your priorities are, and what sacrifices you're willing to make for fast tests. 这实际上取决于您的优先级,以及您愿意为快速测试付出的牺牲。

Mocked data will definitely mean faster tests as you're removing the database layer. 当删除数据库层时,模拟数据无疑意味着更快的测试。 The cost of this is that if there is a bug that only arises when your controller is interacting with your data persistence layer, you won't catch the bug. 这样做的代价是, 如果仅当控制器与数据持久层交互时才出现错误,那么您就不会发现该错误。 Fast unit testing vs slow full-stack-many-pieces-working-together-at-the-sam- time testing. 快速的单元测试与慢速的全栈许多部件一起在一次测试中一起工作。

Opinions vary on this. 对此有不同意见。 The overall trend in rails testing seems to be moving to integration/feature/system tests and abandoning most controller tests. Rails测试的总体趋势似乎正在转向集成/功能/系统测试,并且放弃了大多数控制器测试。 This is because there is a lot more at play these days with advanced CSS styling and Javascript coming into play. 这是因为随着高级CSS样式和Javascript的出现,如今有许多其他功能在发挥作用。 There is no right answer. 没有正确的答案。

I would go with the integration mode. 我会选择集成模式。

Mocking is useful only for long-running operations. 模拟仅对长时间运行的操作有用。 You don't need to mock everything. 您无需嘲笑一切。

Database objects creation should not take a long time as long as you are using transactional database cleaners. 只要使用事务性数据库清理程序,数据库对象的创建就不需要花费很长时间。

If your ActiveRecord object is doing some other long operations like calling some other third party service, you can mock this long-running operation. 如果您的ActiveRecord对象正在执行其他一些长时间的操作(如调用其他第三方服务),则可以模拟该长时间运行的操作。

If you still think the isolation mode is more suitable for your case, you can set only one test case using the real object. 如果您仍然认为隔离模式更适合您的情况,则只能使用实际对象设置一个测试用例。 And the other test cases can do mocking. 其他测试用例可以进行模拟。 In this case, the false positive case you have mentioned will be covered. 在这种情况下,将覆盖您提到的误报情况。

A quintessential attribute of unit tests and automated testing in general is to be blazing fast. 通常,单元测试和自动测试的典型属性是快速发展。 So, all heavyweight resources should be mocked. 因此,所有重量级资源都应被嘲笑。 This includes databases. 这包括数据库。

In a well designed system, a database is yet another storage medium that should be easily changed without affecting the majority of the code base, only the connector should change. 在设计良好的系统中,数据库是又一种存储介质,应轻松更改数据库而不影响大多数代码库,只需更改连接器即可。 So, in this light, database is a detail that shouldn't concern the automated tests. 因此,从这个角度来看,数据库是一个不应该涉及自动化测试的细节。

Another reason to abandon databases is that it's a temptation to depend on other information available in the database that should've been abstracted from the test. 放弃数据库的另一个原因是,依赖于应该从测试中抽象出来的数据库中可用的其他信息是一种诱惑。

In Rails, the compromise was made to run tests in an isolated database, hoping it will be fast enough not to waste a lot of time. 在Rails中,折衷方案是在隔离的数据库中运行测试,希望它足够快而不会浪费很多时间。 Paying that price is reasonable given the flexibility a database would give the developer. 考虑到数据库将为开发人员提供的灵活性,因此支付此价格是合理的。

After the burst in adopting unit testing, many practitioners went maniac with mocking and gone to far extents with mocking everything without proper judgment regarding where to direct developers efforts and how much gain from such practice. 在采用单元测试后,许多从业者对模拟进行了疯狂,甚至在没有对如何指导开发人员进行努力以及从这种实践中获得多少收益的适当判断的情况下,对模拟一切进行了广泛的模拟。 For many ambitious developers, mocking in itself turned to a technical lust! 对于许多雄心勃勃的开发人员而言,嘲弄本身就变成了技术欲望! ie the means became a goal. 即手段成为目标。

Moral of the story is, don't overdo anything without thinking it through and making sure the expected effort will payoff. 这个故事的寓意是,不要在不考虑任何事情并确保预期的努力会得到回报的情况下做任何事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM