Ruby on Rails 2.1 - 單元測試



簡介

在繼續之前,讓我們快速瀏覽幾個定義:

  • 測試 - 它們是測試應用程式,產生一致的結果,並證明 Rails 應用程式符合預期。測試與實際應用程式同時開發。

  • 斷言 - 這是評估物件(或表示式)是否符合預期結果的一行程式碼。例如 - 此值 = 該值?此物件是否為 nil?

  • 測試用例 - 這是一個從 Test::Unit::TestCase 繼承的類,包含由上下文相關的測試組成的測試策略

  • 測試套件 - 這是測試用例的集合。執行測試套件時,它將依次執行屬於它的每個測試。

Rails 測試

當您執行輔助指令碼script/generate 來建立控制器模型時,Rails 會生成單元測試和功能測試的框架。透過為編寫的功能填寫框架中的測試,您可以獲得相當不錯的測試覆蓋率。在 Rails 應用程式中需要測試兩個重要方面:

  • 測試模型

  • 測試控制器

本教程將簡要介紹這兩種測試。因此,讓我們建立一個testapp來理解這個概念。

C:\ruby> rails -d mysql testapp

資料庫設定

到目前為止,我們只使用了 Rails 應用程式的開發資料庫,但現在您需要確保測試資料庫也已建立,並且您的 config/database.yml 檔案中的相應部分已正確設定。

讓我們建立開發和測試資料庫,如下所示:

mysql> create database testapp_test;
Query OK, 1 row affected (0.01 sec)

mysql> create database testapp_development;
Query OK, 1 row affected (0.01 sec)

mysql> use testapp_test;
Database changed

mysql> grant all privileges on testapp_test.* 
   to 'root'@'localhost' identified by 'password';
Query OK, 0 rows affected (0.00 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

配置 database.yml

請按如下方式配置您的 config/database.yml:

development:
   adapter: mysql
   encoding: utf8
   database: testapp_development
   username: root
   password: password
   host: localhost
test:
   adapter: mysql
   encoding: utf8
   database: testapp_test
   username: root
   password: password
   host: localhost
production:
   adapter: mysql
   encoding: utf8
   database: testapp_production
   username: root
   password: password
   host: localhost

生成遷移

假設您有一個包含書籍的表,其中包括它們的標題、價格和簡短描述。以下遷移設定此表:

testapp > ruby script/generate migration books

現在修改 testapp/db/migrate/20080616170315_books.rb 檔案,如下所示:

class Books < ActiveRecord::Migration
   def self.up
      create_table :books do |t|
         t.string     :title, :limit => 32, :null => false
         t.float      :price
         t.text       :description
         t.timestamp  :created_at
      end
   end
  
   def self.down
      drop_table :books
   end
end

現在執行遷移,如下所示:

testapp > rake db:migrate

這將在 testapp_development 資料庫中建立books表。此後,我們需要使用rake命令設定您的測試資料庫,如下所示:

C:\ruby\testapp > rake db:test:clone_structure

這會將testapp_development資料庫克隆到testapp_test資料庫中。這意味著開發資料庫中擁有的任何資料,現在測試資料庫中也會擁有相同的資料。

測試模型

當您使用 generate 指令碼生成模型時,Rails 還會在測試目錄中為該模型生成一個單元測試指令碼。它還會建立一個fixture,一個包含要載入到 testapp_test 資料庫中的測試資料的YAML檔案。這是單元測試將針對其執行的資料:

testapp > ruby script/generate model Book
   exists  app/models/
   exists  test/unit/
   exists  test/fixtures/
   create  app/models/book.rb
   create  test/unit/book_test.rb
   create  test/fixtures/books.yml
   create  db/migrate
   create  db/migrate/20080616164236_create_books.rb

當您在模型類中編寫程式碼時,您將在這些檔案中編寫相應的測試。因此,讓我們使用 YAML 在 test/fixtures/books.yml 中建立兩條測試書籍記錄,如下所示:

perl_cb:
   id: 1
   title: 'Ruby Tutorial'
   price: 102.00
   description : 'This is a nice Ruby tutorial'
java_cb:
   id: 2
   title: 'Java Programming'
   price: 62.00
   description : 'Java Programming for the beginners'

現在讓我們用以下程式碼替換單元測試檔案 test/unit/book_test.rb 中的現有程式碼:

require File.dirname(__FILE__) + '/../test_helper'

class BookTest < ActiveSupport::TestCase
   fixtures :books

   def test_book

      perl_book = Book.new :title => books(:perl_cb).title, 
         :price => books(:perl_cb).price,
         :description => books(:perl_cb).description,
         :created_at => books(:perl_cb).created_at

      assert perl_book.save

      perl_book_copy = Book.find(perl_book.id)

      assert_equal perl_book.title, perl_book_copy.title

      perl_book.title = "Ruby Tutorial"

      assert perl_book.save
      assert perl_book.destroy
   end
end

最後,執行測試方法,如下所示:

testapp > ruby test/unit/book_test.rb

以下是成功測試用例的輸出:

testapp > ruby test/unit/book_test_crud.rb 
Loaded suite ./test/unit/book_test
Started
.
Finished in 0.0625 seconds.

1 tests, 4 assertions, 0 failures, 0 errors

讓我們分析一下這裡發生了什麼:

  • BookTest 方法首先使用文字 fixture/books.yml 中第一條記錄中的標題和其他欄位建立一個新的 Book 物件。生成的物件儲存在 perl_book 例項變數中。

  • 第一個斷言測試儲存 Book 物件是否成功。

  • 接下來,使用find方法檢索 book 物件並將其儲存在名為 perl_book_copy 的另一個例項變數中。下一個斷言測試此檢索的成功,它比較兩個 book 物件的標題。此時,我們已經測試了建立和讀取資料庫記錄的能力。

  • 解決方案透過為儲存在 perl_book 中的物件分配一個新標題來測試更新,然後斷言更改儲存成功。

  • 最後,測試了銷燬 Book 物件的能力。

這就是我們測試 Rails 模型的方法。

測試控制器

控制器測試也稱為功能測試。功能測試測試控制器的以下型別功能:

  • 響應是否按預期重定向?
  • 是否呈現了預期的模板?
  • 路由是否符合預期?
  • 響應是否包含預期的標籤?

Rails 框架支援五種型別的請求:

  • get
  • post
  • put
  • head
  • delete

要編寫功能測試,您需要模擬控制器將處理的五種 HTTP 請求型別中的任何一種。

請求型別“get”和“post”在控制器測試中最常用。所有這些方法都接受四個引數:

  • 控制器的操作
  • 可選的請求引數雜湊
  • 可選的會話雜湊
  • 可選的快閃記憶體雜湊

在本教程中,我們將看到如何使用get方法測試我們的控制器。您可以透過類似的方式測試其餘的方法。

當您使用 generate 生成控制器時,Rails 會如下建立控制器的功能測試指令碼:

testapp > ruby script/generate controller Book
   exists  app/controllers/
   exists  app/helpers/
   create  app/views/book
   exists  test/functional/
   create  app/controllers/book_controller.rb
   create  test/functional/book_controller_test.rb
   create  app/helpers/book_helper.rb

當您在控制器類中編寫程式碼時,您將在這些檔案中編寫相應的測試。在此之前,讓我們在app/controllers/book_controller.rb中定義控制器函式list、showsearch,如下所示:

class BookController < ApplicationController
   def list
      @book_pages, @books = paginate :books, :per_page => 10
   end

   def show
      @book = Book.find(params[:id])
   end

   def search
      @book = Book.find_by_title(params[:title])
      if @book
         redirect_to :action => 'show', :id => @book.id
      else    
         flash[:error] = 'No such book available'
         redirect_to :action => 'list'
      end
   end
end

注意 - 您將需要showlist方法的兩個檢視模板。您可以定義這些檢視並對其進行測試,但現在,我們將不定義這些檢視繼續進行。

現在讓我們重用位於test/fixtures/books.yml檔案中的測試夾具,如下所示:

perl_cb:
   id: 1
   title: 'Ruby Tutorial'
   price: 102.00
   description : 'This is a nice Ruby tutorial'
java_cb:
  id: 2
  title: 'Java Programming'
  price: 62.00
  description : 'Java Programming for the beginners'

將以下test_search_booktest_search_not_found方法新增到test/functional/book_controller_test.rb中,以測試 Book Controller 的 search 操作的功能。

require File.dirname(__FILE__) + '/../test_helper'
require 'book_controller'

# Re-raise errors caught by the controller.
class BookController
   def rescue_action(e) 
      raise e 
   end
end

class BookControllerTest < Test::Unit::TestCase
   fixtures :books
   def setup
      @controller = BookController.new
      @request    = ActionController::TestRequest.new
      @response   = ActionController::TestResponse.new
   end

   def test_search_book
      get :search, :title => 'Ruby Tutorial'
      assert_not_nil assigns(:book)
      assert_equal books(:perl_cb).title, assigns(:book).title
      assert_valid assigns(:book)
      assert_redirected_to :action => 'show'
   end

   def test_search_not_found
      get :search, :title => 'HTML Tutorial'
      assert_redirected_to :action => 'list'
      assert_equal 'No such book available', flash[:error]
   end
end

現在執行您的測試用例,如下所示:

testapp > ruby test/functional/book_controller_test.rb 

它給出以下輸出:

Loaded suite test/functional/book_controller_test
Started
..
Finished in 0.422 seconds.

2 tests, 7 assertions, 0 failures, 0 errors

讓我們分析一下這裡發生了什麼:

  • setup方法是建立控制器、請求和響應物件的預設方法。它們將由 Rails 在內部使用。

  • 第一個測試方法test_search_book向 search 操作生成一個get請求,傳入一個title引數。

  • 接下來的兩個斷言驗證一個Book物件是否已儲存在名為@book的例項變數中,以及該物件是否透過可能存在的任何 Active Record 驗證。

  • 第一個方法中的最終斷言測試請求是否已重定向到控制器的 show 操作。

  • 第二個測試方法test_search_not_found執行另一個get請求,但傳入一個無效的標題。

  • 第一個斷言測試是否發出了到list操作的重定向。

  • 如果前面的斷言透過,則快閃記憶體雜湊中應該有一條訊息,您可以使用 assert_equal.. 進行測試。

要獲取有關斷言的更多資訊,請參考Rails 標準文件

使用 Rake 進行測試

您可以使用rake實用程式來測試您的應用程式。下面列出了一些重要的命令。

  • $rake test - 測試所有單元測試和功能測試(以及整合測試,如果存在)。

  • $rake test:functionals - 執行所有功能測試。

  • $rake test:units - 執行所有單元測試。

  • $rake test:integration - 執行所有整合測試。

  • $rake test:plugins - 執行 ./vendor/plugins/**/test 中的所有測試。

  • $rake test:recent - 執行過去 10 分鐘內修改過的模型和控制器的測試:

  • $rake test:uncommitted - 對於 Subversion 中的專案,執行自上次提交以來在模型和控制器中發生更改的測試:

廣告