[譯]咱們如何測試 Rails 應用

Josh Steiner January 14, 2014

我經常被問到,怎樣開始測試 Rails 程序。其實最爲一個測試新手,最可貴地方在於你不知道一些專業術語或者該問怎樣的問題。下面所寫的是一些概覽,關於咱們使用什麼工具,爲何使用這些工具,和一些須要牢記在心的建議。css

RSpec

咱們用 RSpec 而不是 Test::Unit,是由於語法更友好,可讀性更高。固然,你能夠花幾天時間爭論到底該用哪一個框架,每一個框架都有本身的優勢。最主要的是你在用他們進行測試。html

Feature specs

Feature specs,能夠測試你整個程序的高級測試工具,保證每一個部件都工做正常,挺讚的。他是從用戶的角度編寫的,好比用戶點擊或者填寫表單。咱們用 RSpec 和 Capybara,他們容許你寫照這樣編寫能夠和網頁進行交互的測試。git

這是一個 RSpec feature 測試的栗子:github

# 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」。這個測試而後假設頁面正如期待,正確地顯示剛纔建立的foobarweb

這些對於測試高級功能來講很棒,可是記住,feature specs 跑起來很慢。在用 Capybara 測試應用全部的可能路徑的時候,把測試的邊緣狀況留模型,視圖,控制器的 sepecs。數據庫

我傾向於關於怎麼區分 Rspec 和 Capybara 方法的不一樣點。Capybara 方法其實是和頁面進行交互,好比點擊,表單操做,或者在頁面上查找元素。你能夠在 Capybara 的findersmatchers,和 actions 查看更多文檔。瀏覽器

Model specs

Model specs 常常被用來測試系統中較小的部件,好比類或方法,這點和單元測試挺類似的。有時,他們也會和數據庫進行交互。他們運行起來很快,也能夠處理正在測試系統(system under test)中的一些邊緣狀況。ruby

在 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 Testsession

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

  1. 安裝一個 JavaScript 驅動

有兩種類型的 JavaScript 驅動。好比像 Selenium,它會打開一個 GUI 窗口瀏覽器,而後在你看着它的時候,在頁面上點擊。這是一個頗有用的工具在測試中用來視圖化。可是不幸的是,啓動一整個 GUI 窗口瀏覽器很慢。因爲這個緣由,咱們傾向於使用 headless 瀏覽器。對於 Rails 來講,你可能會使用 Poltergeist 或者 [Capybara] Webkit(https://github.com/thoughtbot/capybara-webkit)。

  1. 對於特定的測試使用 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

相關文章
相關標籤/搜索