资讯专栏INFORMATION COLUMN

[译]我们如何测试 Rails 应用

wenhai.he / 1027人阅读

摘要:无论合适,优先使用的,而不是。这是一个很有用的工具在测试中用来视图化。这点非常好,因为我们不想任何测试数据会对别的测试产生副作用。为了遵照的最佳实践,我们用测试间谍,这样我们的期待会进入最佳的状态。下一步这仅仅是个如何开始测试应用的概览。

Josh Steiner January 14, 2014

我常常被问到,怎样开始测试 Rails 程序。其实最为一个测试新手,最难得地方在于你不知道一些专业术语或者该问怎样的问题。下面所写的是一些概览,关于我们使用什么工具,为什么使用这些工具,和一些需要牢记在心的建议。

RSpec

我们用 RSpec 而不是 Test::Unit,是因为语法更友好,可读性更高。当然,你可以花几天时间争论到底该用哪个框架,每个框架都有自己的优点。最主要的是你在用他们进行测试。

Feature specs

Feature specs,可以测试你整个程序的高级测试工具,保证每个部件都工作正常,挺赞的。他是从用户的角度编写的,比如用户点击或者填写表单。我们用 RSpec 和 Capybara,他们允许你写照这样编写可以和网页进行交互的测试。

这是一个 RSpec feature 测试的栗子:

# spec/features/user_creates_a_foobar_spec.rb

feature "User creates a foobar" do
  scenario "they see the foobar on the page" do
    visit new_foobar_path

    fill_in "Name", with: "My foobar"
    click_button "Create Foobar"

    expect(page).to have_css ".foobar-name", "My foobar"
  end
end

这个测试,模拟了一个用户打开新建foobar的表单,填入信息,点击“Create”。这个测试然后假设页面正如期待,正确地显示刚才创建的foobar

这些对于测试高级功能来说很棒,但是记住,feature specs 跑起来很慢。在用 Capybara 测试应用所有的可能路径的时候,把测试的边缘情况留模型,视图,控制器的 sepecs。

我倾向于关于怎么区分 Rspec 和 Capybara 方法的不同点。Capybara 方法实际上是和页面进行交互,比如点击,表单操作,或者在页面上查找元素。你可以在 Capybara 的finders,matchers,和 actions 查看更多文档。

Model specs

Model specs 经常被用来测试系统中较小的部件,比如类或方法,这点和单元测试挺相似的。有时,他们也会和数据库进行交互。他们运行起来很快,也可以处理正在测试系统(system under test)中的一些边缘情况。

在 RSpec 中,他们看起来像这样:

# spec/models/user_spec.rb

# Prefix class methods with a "."
describe User, ".active" do
  it "returns only active users" do
    # setup
    active_user = create(:user, active: true)
    non_active_user = create(:user, active: false)

    # exercise
    result = User.active

    # verify
    expect(result).to eq [active_user]

    # teardown is handled for you by RSpec
  end
end

# Prefix instance methods with a "#"
describe User, "#name" do
  it "returns the concatenated first and last name" do
    # setup
    user = build(:user, first_name: "Josh", last_name: "Steiner")

    # excercise and verify
    expect(user.name).to eq "Josh Steiner"
  end
end

为了维护的可读性,确保你写的测试是 Four Phase Test


 

The Four-Phase Test is a testing pattern, applicable to all programming languages and unit tests (not so much integration tests).
It takes the following general form:

test do
  setup
  exercise
  verify
  teardown
end

比如:

it "encrypts the password" do
  user = User.new(password: "password")
  user.save
  user.encrypted_password.should_not be_nil
end
Controller specs

当通过一个控制器测试多个路径的时候,我们喜欢用 controller specs 而不是 feature specs,因为他很快,而且写起来容易。

一个好的测试验证的 use case:

# spec/controllers/sessions_controller_spec.rb

describe "POST #create" do
  context "when password is invalid" do
    it "renders the page with error" do
      user = create(:user)

      post :create, session: { email: user.email, password: "invalid" }

      expect(response).to render_template(:new)
      expect(flash[:notice]).to match(/^Email and password do not match/)
    end
  end

  context "when password is valid" do
    it "sets the user in the session and redirects them to their dashboard" do
      user = create(:user)

      post :create, session: { email: user.email, password: user.password }

      expect(response).to redirect_to "/dashboard"
      expect(controller.current_user).to eq user
    end
  end
end
View specs

View specs 对于测试在模板中根据条件显示不同信息来说非常有用,但是很多开发者却忘了这个,而用 feature specs。然后绞尽脑汁为什么他们花了那么长时间跑测试。当然,你可以用 feature spec 覆盖每一个条件视图,但我更喜欢像这样使用 view specs :

# spec/views/products/_product.html.erb_spec.rb

describe "products/_product.html.erb" do
  context "when the product has a url" do
    it "displays the url" do
      assign(:product, build(:product, url: "http://example.com")

      render

      expect(rendered).to have_link "Product", href: "http://example.com"
    end
  end

  context "when the product url is nil" do
    it "displays "None"" do
      assign(:product, build(:product, url: nil)

      render

      expect(rendered).to have_content "None"
    end
  end
end
FactoryGirl

当编写测试代码的时候,你会需要在不同场景往数据库里灌数据。你可以使用内建的User.create,但是当 model 中有很多 validations 时候,这样好乏味啊。通过User.create,即便你的测试代码和这些验证无关,你也不得不指定属性来满足 validations。最重要的是,如果后来你修改了 validations,你还要重新修改测试套件的代码。解决方案就是用 factories(工厂,数据生成器) 或者 fixtures(夹具)来创建模型。

我们更喜欢 factories(和 FactoryGirl)胜过 Rails 的 fixgures,因为 fixtures 是神秘嘉宾。夹具让人很难看到内因和效果,因为部分逻辑在你使用它的时候已经在文件中被定义好了。因为夹具远在在测试之前就已经被生成,他们变得很难控制。

Factories,另一方面来说,把逻辑正确地放到测试中。这让我们很容易地看到正在发生什么,而且对于不同的场景更加灵活。Factories 比夹具更慢,但是从灵活性和可读性上来说,这点牺牲是值得的!

把数据固化到数据库也会减慢测试速度。无论合适,优先使用 FactoryGirl 的 build_stubbed,而不是createbuild_stubbed 会在内存中生成,避免写入磁盘。如果你测试一些查询操作(User.where(admin: true)),你会希望从数据库里进行查找,这就意味着你必须使用create

跑带有 JavaScript 的 specs

你会最终碰到一种场景,你需要测试一些依赖 JavaScript 代码的功能。用默认的驱动跑 specs 不会执行页面中任何 JavaScript 代码。

你需要两个法器,来跑带有 JavaScript 代码的功能 specs

安装一个 JavaScript 驱动

有两种类型的 JavaScript 驱动。比如像 Selenium,它会打开一个 GUI 窗口浏览器,然后在你看着它的时候,在页面上点击。这是一个很有用的工具在测试中用来视图化。但是不幸的是,启动一整个 GUI 窗口浏览器很慢。由于这个原因,我们倾向于使用 headless 浏览器。对于 Rails 来说,你可能会使用 Poltergeist 或者 [Capybara] Webkit(https://github.com/thoughtbot/capybara-webkit)。

对于特定的测试使用 JavaScript 元数据关键字

feature "User creates a foobar" do
   scenario "they see the foobar on the page", js: true do
     ...
   end
 end

用合适的关键字,RSpec 会根据需要运行任何 JavaScript。

清理数据库

默认情况下,跑 Rails 测试的时候,Rails 会把每个场景都存在数据库事务中。这就说明,在每个测试结束的时候,Rails 会回滚所有在测试中的修改。这点非常好,因为我们不想任何测试数据会对别的测试产生副作用。

不幸的是,当我们使用 JavaScript 驱动的时候,这个测试实在另一个线程进行的。这就意味着,它并没有和程序共用同一个数据库连接,而且为了运行程序看到测试结果,你的测试会提交这个事务。为了解决这个问题,我们可以允许数据库提交这些数据,然后接着,在每个 spec 后 Truncate 数据库。这样会比事务慢一点,所以,我们只在需要的时候使用 truncation

这就是 Database Cleaner 的用处。Database Cleaner 允许你配置策略。我建议阅读下 Avdi 的文章 看看血淋淋的细节。这是个极(丧)其(心)详(病)细(狂)的配置过程,所以我通常在项目中来回拷贝这个文件,或者使用 Suspenders,这样就可以轻易搞定了。

doubles 和 stubs 方法

double 方法可以模拟系统中另外一个对象。经常的,你会需要一个替身,并且只测试一个属性,所以不值得加载整个 ActiveRecord 对象。

car = double(:car)

当你使用 stubs,你是在告诉一个对象去响应一个已给出的方法。如果 stub 之前的 double

car.stub(:max_speed).and_return(120)

我们现在可以期待当访问 max_speed 我们的 car 对象总是返回120。临时产生可以响应一个方法的对象而且带有同样的依赖关系,而不用系统中真的存在的对象,这是很棒的方法!在这个栗子中,我们在一个 double 出来的对象上进行了 stub,实际上你还可以在别的对象 stub 任何方法。

我们可以把上边的代码简化为这样:

car = double(:car, max_spped: 120)

测试间谍(Test Spies)

当测试你的程序时,你会碰到你想验证当一个对象接收到一个指定的方法的场景。为了遵照 Four Phase Test 的最佳实践,我们用测试间谍,这样我们的期待会进入最佳的 verify 状态。之前,我们用 Bourne 做到这点,但是 RSpec 已经在 RSpec Mocks 中包括了这项功能。看看文档中的这个栗子:

invitation = double("invitation", accept: true)

user.accept_invitation(invitation)

expect(invitation).to have_received(:accept)
用 Webmock stub 外部请求

基于三方服务的测试套件跑起来很慢的,会因为网络连接的断开导致失败,而且也可能会因为服务频率限制或者缺少沙盒环境导致失败。

确保你的测试套件在用 Webmock stub 外部请求的时候,不会和三方服务交互。这个可以配置在 spec/spec_helper.rb

require "webmock/rspec"
WebMock.disable_net_connect!(allow_localhost: true)

避免使用三方请求,学习怎样 stub 外部服务请求。

下一步

这仅仅是个如何开始测试 Rails 应用的概览。为了促进学习,我非常推荐你上我们的 TDD workshop,在这里,你可以通过从零开始建立两个 Rails 应用,来深度学这些课程。课程覆盖到重构,为确保应用和测试代码的可维护性。TDDworkshop 的学生也可以介入我们的工作时间,你实时地可以问我们攻城狮任何问题。

当我是新手的时候我学习了这个课程,我墙裂推荐!

source: http://www.tuicool.com/articles/JrQzyi

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/8681.html

相关文章

  • 用Jasmine和Sinon测试Backbone应用 ()

    摘要:框架本身可以很好地支持自下而上的单元测试。在中,这些原因可以分为性能真实的操作,依靠定时行为及网络活动减慢了测试隔离单元测试应把重点放在小的一块功能成为可能,并解耦不可靠的或低依赖使用对象是拥抱和的基本组成部分。 最近在慢慢深入Backbone,也试着写一些测试,找一些合适的文档来学习。于是就找到了一个系列的文章 : Testing Backbone applications with...

    ralap 评论0 收藏0
  • 代码级干货 | 如何利用Docker与Rails API gem构建微服务

    摘要:今天小数给大家带来的是一篇代码级干货文章,与大家分享一些利用以微服务形式设置应用的经验与心得。为何选择加在我效力的企业中,我们一直在利用为全部工程师构建开发环境。运行命令,从而利用构建镜像并安装。 今天小数给大家带来的是一篇代码级干货文章,与大家分享一些利用Rails API以微服务形式设置应用的经验与心得。 为何选择Docker加Rails API? 在我效力的企业中,我们一直在利用...

    stefanieliang 评论0 收藏0
  • Vuejs自己的构建工具

    摘要:然而,这些模板并不限制你自己对于使用的架构组织和选择类库。目前可用的模板包括全功能的,包括热加载,静态检测,单元测试一个简易的,以便于快速开始。 最近, 尤大在和人对喷的时候,悄然放出了一个大招,于是为了追赶他的步伐,赶紧试验了下,并且把原文给大家翻译下。 原文地址:Announcing vue-cli 译文源地址: Vuejs自己的构建工具 先上原文翻译: 最近有很多大量关于Reac...

    leoperfect 评论0 收藏0
  • 】PHP:40+开发工具推荐

    摘要:今天,就为开发者介绍个方便的工具。对开发者来说,是一个非常有用的工具,它提供了超过个有用的函数。该工具检查输入源代码和报告任何违反给定的标准。框架是一个开发的工具。它侧重于安全性和性能,绝对是最安全的开发框架之一。 PHP是为Web开发设计的服务器脚本语言,但也是一种通用的编程语言。超过2.4亿个索引域使用PHP,包括很多重要的网站,例如Facebook、Digg和WordPress。...

    dreambei 评论0 收藏0
  • 测试驱动开发:使用 Node.js 和 MongoDB 构建 Todo API

    摘要:首先安装单元测试环境使用模块来模拟定义的模型。根据删除这是单元测试的最后一小节。需要根据需求和单元测试用例来编写应用逻辑,使我们的程序更加稳定。我们会运行自动测试用例,一直重构,直到所有单元测试都通过。 本文转载自:众成翻译译者:文蔺链接:http://www.zcfy.cc/article/746原文:https://semaphoreci.com/community/tutoria...

    邱勇 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<