Rails,SRP和单元测试

时间:2012-07-12 00:05:19

标签: ruby-on-rails unit-testing solid-principles acceptance-testing single-responsibility-principle

序言

这是我第一次尝试使用SRP构建应用程序,并且真正尝试使用测试来驱动网站的代码,而不是从我的数据架构(ActiveRecord)开始,然后构建适合的应用程序。 / p>

但我遇到了问题。我已经订阅并观看了大量的Destroy All Software截屏视频,理论上我喜欢他所宣传的内容,但我在实践中遇到了麻烦。

手头的问题

我知道我应用的主要功能是根据位置搜索个人资料。所以我为此编写了一个简单的Cucumber功能(故意暂时离开路由/控制器/等以简化手头的任务)。

search.feature:

Feature: Search for profiles

  Scenario: By zipcode
    When I search for 90210
    Then I will see profiles near 90210

search_steps.rb:

When /^I search for 90210$/ do
  @profiles = ProfileSearch.new.near_location(90210).all
end

Then /^I will see profiles near 90210$/ do
  pending # express the regexp above with the code you wish you had
end

没有问题,现在是规格:

profile_search_spec.rb:

require_relative '../../app/services/profile_search'

describe ProfileSearch do  
  it "finds nearby profiles"
  it "does not find far away profiles"
end

profile_search.rb:

class ProfileSearch
end

我出于某些原因使用了ProfileSearch类。

  1. 在ActiveRecord之外移动尽可能多的业务逻辑似乎是正确的做法(单一责任原则)。
  2. PORO可以加快测试速度(无需加载Rails)。
  3. 我打算在不久的将来使用ElasticSearch或Solr,并希望界面保持不变。
  4. 我不确定接下来要做什么。 ProfileSearch显然取决于Profile模型,我很确定这将是ActiveRecord。

    所以问题是我是否开始指出并构建Profile并在我的测试中开始加载Rails?这似乎是最简单的选择,但有些事情似乎是错误的。我觉得我会设计并构建我的应用尚未特别要求的行为。我不得不考虑字段,关系和存储等,所有这些都是我的应用目前不应该关心的。

    或者我应该在我的Profile规范中对ProfileSearch的所有调用使用存根/模拟,并确保调用正确的方法?这似乎也是错误的,因为我当时并不会真正测试这种行为,并且在切换到Solr或ElasticSearch时我必须重写测试,即使预期会出现相同的行为。

    或者我应该创建一个暂时不使用ActiveRecord的工作Profile模型,但正如Uncle Bob在构建他的wiki时演示的那样正确响应所有正确的方法?这似乎可能是理论上最好的方法,但是知道我将来会使用ActiveRecord它似乎也是多余的。

    或者... f * ck并将所有内容都放在ActiveRecord模型中:\

    我脑子里浮现的模式,原则和最佳实践有很多,我不知道要做什么。

    你会做什么?

1 个答案:

答案 0 :(得分:1)

首先,您需要选择测试方法:

  • Cucumber专为BDD(从外到内)测试而设计。黄瓜场景旨在锻炼整个堆栈(网页,控制器,模型,数据库 - 或任何堆栈),而不是像上面那样用作单元测试。因此,如果您编写的第一件事是Cucumber场景,那么您需要调用所有层以使第一个场景通过。这就是我所做的,无论是使用Cucumber(我最喜欢的)还是RSpec功能规格。完成后,您可以自由地重构以满足您喜欢的任何设计标准,并且您可以通过单元测试来测试详细要求。

  • 或者,您可以选择堆栈中最棘手的部分,对其进行单元测试,然后对图层进行单元测试,最后进行验收测试,直到您拥有一个完整的应用程序。一方面,在投资简单零件之前,证明您可以使用棘手的部件可能是一个好主意。另一方面,我发现当我猜测没有工作验收测试的组件接口时,我总是猜错了,所以我不这样做。

无论哪种方式,您的目标都是ProfileSearch类,它返回Profile个实例,但并不承诺它们是ActiveRecord对象或其他任何持久性特定的对象。如何在测试中处理这些类取决于测试的类型:

  • 如果您使用验收规范(Cucumber或RSpec功能规范),他们将不会使用存根,他们将使用所有真正的实现类,并且他们将加载Rails,因为它'他们的观点以及他们如何设计。对不起,他们会很慢。不要再写下你指定重要场景所需的内容了。

  • 在使用ProfileSearch的类的单元测试中,您可以将其存根并返回PORO以获得速度。 (如果您在这些规范中使用了rspec-rails,require 'spec_helper'而不是rails_helper,以避免在自行运行Rails时加载Rails。)

  • ProfileSearch本身的单元测试通过的最简单方法可能就是让Profile成为ActiveRecord模型并让ProfileSearch使用Profile&#39 ; s ActiveRecord功能并返回Profile的实例。这些规范也需要require 'rails_helper'