<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # Rails 程序測試指南 本文介紹 Rails 內建對測試的支持。 讀完本文,你將學到: * Rails 測試術語; * 如何為程序編寫單元測試,功能測試和集成測試; * 常用的測試方法和插件; ### Chapters 1. [為什么要為 Rails 程序編寫測試?](#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E4%B8%BA-rails-%E7%A8%8B%E5%BA%8F%E7%BC%96%E5%86%99%E6%B5%8B%E8%AF%95%EF%BC%9F) 2. [測試簡介](#%E6%B5%8B%E8%AF%95%E7%AE%80%E4%BB%8B) * [測試環境](#%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83) * [Rails Sets up for Testing from the Word Go](#rails-sets-up-for-testing-from-the-word-go) * [固件詳解](#%E5%9B%BA%E4%BB%B6%E8%AF%A6%E8%A7%A3) 3. [為模型編寫單元測試](#%E4%B8%BA%E6%A8%A1%E5%9E%8B%E7%BC%96%E5%86%99%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95) * [維護測試數據庫的模式](#%E7%BB%B4%E6%8A%A4%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E6%A8%A1%E5%BC%8F) * [運行測試](#%E8%BF%90%E8%A1%8C%E6%B5%8B%E8%AF%95) * [單元測試要測試什么](#%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E8%A6%81%E6%B5%8B%E8%AF%95%E4%BB%80%E4%B9%88) * [可用的斷言](#%E5%8F%AF%E7%94%A8%E7%9A%84%E6%96%AD%E8%A8%80) * [Rails 提供的斷言](#rails-%E6%8F%90%E4%BE%9B%E7%9A%84%E6%96%AD%E8%A8%80) 4. [為控制器編寫功能測試](#%E4%B8%BA%E6%8E%A7%E5%88%B6%E5%99%A8%E7%BC%96%E5%86%99%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95) * [功能測試要測試什么](#%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95%E8%A6%81%E6%B5%8B%E8%AF%95%E4%BB%80%E4%B9%88) * [功能測試中可用的請求類型](#%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95%E4%B8%AD%E5%8F%AF%E7%94%A8%E7%9A%84%E8%AF%B7%E6%B1%82%E7%B1%BB%E5%9E%8B) * [可用的四個 Hash](#%E5%8F%AF%E7%94%A8%E7%9A%84%E5%9B%9B%E4%B8%AA-hash) * [可用的實例變量](#%E5%8F%AF%E7%94%A8%E7%9A%84%E5%AE%9E%E4%BE%8B%E5%8F%98%E9%87%8F) * [設置報頭和 CGI 變量](#%E8%AE%BE%E7%BD%AE%E6%8A%A5%E5%A4%B4%E5%92%8C-cgi-%E5%8F%98%E9%87%8F) * [測試模板和布局](#%E6%B5%8B%E8%AF%95%E6%A8%A1%E6%9D%BF%E5%92%8C%E5%B8%83%E5%B1%80) * [完整的功能測試示例](#%E5%AE%8C%E6%95%B4%E7%9A%84%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95%E7%A4%BA%E4%BE%8B) * [測試視圖](#%E6%B5%8B%E8%AF%95%E8%A7%86%E5%9B%BE) 5. [集成測試](#%E9%9B%86%E6%88%90%E6%B5%8B%E8%AF%95) * [集成測試中可用的幫助方法](#%E9%9B%86%E6%88%90%E6%B5%8B%E8%AF%95%E4%B8%AD%E5%8F%AF%E7%94%A8%E7%9A%84%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95) * [集成測試示例](#%E9%9B%86%E6%88%90%E6%B5%8B%E8%AF%95%E7%A4%BA%E4%BE%8B) 6. [運行測試使用的 Rake 任務](#%E8%BF%90%E8%A1%8C%E6%B5%8B%E8%AF%95%E4%BD%BF%E7%94%A8%E7%9A%84-rake-%E4%BB%BB%E5%8A%A1) 7. [MiniTest 簡介](#minitest-%E7%AE%80%E4%BB%8B) 8. [測試前準備和測試后清理](#%E6%B5%8B%E8%AF%95%E5%89%8D%E5%87%86%E5%A4%87%E5%92%8C%E6%B5%8B%E8%AF%95%E5%90%8E%E6%B8%85%E7%90%86) 9. [測試路由](#%E6%B5%8B%E8%AF%95%E8%B7%AF%E7%94%B1) 10. [測試郵件程序](#%E6%B5%8B%E8%AF%95%E9%82%AE%E4%BB%B6%E7%A8%8B%E5%BA%8F) * [確保郵件程序在管控內](#%E7%A1%AE%E4%BF%9D%E9%82%AE%E4%BB%B6%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%AE%A1%E6%8E%A7%E5%86%85) * [單元測試](#%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95) * [功能測試](#%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95) 11. [測試幫助方法](#%E6%B5%8B%E8%AF%95%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95) 12. [其他測試方案](#%E5%85%B6%E4%BB%96%E6%B5%8B%E8%AF%95%E6%96%B9%E6%A1%88) ### 1 為什么要為 Rails 程序編寫測試? 在 Rails 中編寫測試非常簡單,生成模型和控制器時,已經生成了測試代碼骨架。 即便是大范圍重構后,只需運行測試就能確保實現了所需功能。 Rails 中的測試還可以模擬瀏覽器請求,無需打開瀏覽器就能測試程序的響應。 ### 2 測試簡介 測試是 Rails 程序的重要組成部分,不是處于嘗鮮和好奇才編寫測試。基本上每個 Rails 程序都要頻繁和數據庫交互,所以測試時也要和數據庫交互。為了能夠編寫高效率的測試,必須要了解如何設置數據庫以及導入示例數據。 #### 2.1 測試環境 默認情況下,Rails 程序有三個環境:開發環境,測試環境和生產環境。每個環境所需的數據庫在 `config/database.yml` 文件中設置。 測試使用的數據庫獨立于其他環境,不會影響開發環境和生產環境的數據庫。 #### 2.2 Rails Sets up for Testing from the Word Go 執行 `rails new` 命令生成新程序時,Rails 會創建一個名為 `test` 的文件夾。這個文件夾中的內容如下: ``` $ ls -F test controllers/ helpers/ mailers/ test_helper.rb fixtures/ integration/ models/ ``` `modles` 文件夾存放模型測試,`controllers` 文件夾存放控制器測試,`integration` 文件夾存放多個控制器之間交互的測試。 `fixtures` 文件夾中存放固件。固件是一種組織測試數據的方式。 `test_helper.rb` 文件中保存測試的默認設置。 #### 2.3 固件詳解 好的測試應該應該具有提供測試數據的方式。在 Rails 中,測試數據由固件提供。 ##### 2.3.1 固件是什么? 固件代指示例數據,在運行測試之前,把預先定義好的數據導入測試數據庫。固件相互獨立,一個文件對應一個模型,使用 YAML 格式編寫。 固件保存在文件夾 `test/fixtures` 中,執行 `rails generate model` 命令生成新模型時,會在這個文件夾中自動創建一個固件文件。 ##### 2.3.2 YAML 使用 YAML 格式編寫的固件可讀性極高,文件的擴展名是 `.yml`,例如 `users.yml`。 下面舉個例子: ``` # lo & behold! I am a YAML comment! david: name: David Heinemeier Hansson birthday: 1979-10-15 profession: Systems development steve: name: Steve Ross Kellock birthday: 1974-09-27 profession: guy with keyboard ``` 每個附件都有名字,后面跟著一個縮進后的鍵值對列表。記錄之間往往使用空行分開。在固件中可以使用注釋,在行首加上 `#` 符號即可。如果鍵名使用了 YAML 中的關鍵字,必須使用引號,例如 `'yes'` 和 `'no'`,這樣 YAML 解析程序才能正確解析。 如果涉及到關聯,定義一個指向其他固件的引用即可。例如,下面的固件針對 `belongs_to/has_many` 關聯: ``` # In fixtures/categories.yml about: name: About # In fixtures/articles.yml one: title: Welcome to Rails! body: Hello world! category: about ``` ##### 2.3.3 使用 ERB 增強固件 ERB 允許在模板中嵌入 Ruby 代碼。Rails 加載 YAML 格式的固件時,會先使用 ERB 進行預處理,因此可使用 Ruby 代碼協助生成示例數據。例如,下面的代碼會生成一千個用戶: ``` <% 1000.times do |n| %> user_<%= n %>: username: <%= "user#{n}" %> email: <%= "user#{n}@example.com" %> <% end %> ``` ##### 2.3.4 固件實戰 默認情況下,運行模型測試和控制器測試時會自動加載 `test/fixtures` 文件夾中的所有固件。加載的過程分為三步: * 從數據表中刪除所有和固件對應的數據; * 把固件載入數據表; * 把固件中的數據賦值給變量,以便直接訪問; ##### 2.3.5 固件是 Active Record 對象 固件是 Active Record 實例,如前一節的第 3 點所述,在測試用例中可以直接訪問這個對象,因為固件中的數據會賦值給一個本地變量。例如: ``` # this will return the User object for the fixture named david users(:david) # this will return the property for david called id users(:david).id # one can also access methods available on the User class email(david.girlfriend.email, david.location_tonight) ``` ### 3 為模型編寫單元測試 在 Rails 中,單元測試用來測試模型。 本文會使用 Rails 腳手架生成模型、遷移、控制器、視圖和遵守 Rails 最佳實踐的完整測試組件。我們會使用自動生成的代碼,也會按需添加其他代碼。 關于 Rails 腳手架的詳細介紹,請閱讀“[Rails 入門](getting_started.html)”一文。 執行 `rails generate scaffold` 命令生成資源時,也會在 `test/models` 文件夾中生成單元測試文件: ``` $ rails generate scaffold post title:string body:text ... create app/models/post.rb create test/models/post_test.rb create test/fixtures/posts.yml ... ``` `test/models/post_test.rb` 文件中默認的測試代碼如下: ``` require 'test_helper' class PostTest < ActiveSupport::TestCase # test "the truth" do # assert true # end end ``` 下面逐行分析這段代碼,熟悉 Rails 測試的代碼和相關術語。 ``` require 'test_helper' ``` 現在你已經知道,`test_helper.rb` 文件是測試的默認設置,會載入所有測試,因此在所有測試中都可使用其中定義的方法。 ``` class PostTest < ActiveSupport::TestCase ``` `PostTest` 繼承自 `ActiveSupport::TestCase`,定義了一個測試用例,因此可以使用 `ActiveSupport::TestCase` 中的所有方法。后文會介紹其中一些方法。 `MiniTest::Unit::TestCase`(`ActiveSupport::TestCase` 的父類)子類中每個以 `test` 開頭(區分大小寫)的方法都是一個測試,所以,`test_password`、`test_valid_password` 和 `testValidPassword` 都是合法的測試名,運行測試用例時會自動運行這些測試。 Rails 還提供了 `test` 方法,接受一個測試名作為參數,然后跟著一個代碼塊。`test` 方法會生成一個 `MiniTest::Unit` 測試,方法名以 `test_` 開頭。例如: ``` test "the truth" do assert true end ``` 和下面的代碼是等效的 ``` def test_the_truth assert true end ``` 不過前者的測試名可讀性更高。當然,使用方法定義的方式也沒什么問題。 生成的方法名會把空格替換成下劃線。最終得到的結果可以不是合法的 Ruby 標示符,名字中可以包含標點符號等。因為在 Ruby 中,任何字符串都可以作為方法名,奇怪的方法名需要調用 `define_method` 或 `send` 方法,所以沒有限制。 ``` assert true ``` 這行代碼叫做“斷言”(assertion)。斷言只有一行代碼,把指定對象或表達式和期望的結果進行對比。例如,斷言可以檢查: * 兩個值是夠相等; * 對象是否為 `nil`; * 這行代碼是否拋出異常; * 用戶的密碼長度是否超過 5 個字符; 每個測試中都有一個到多個斷言。只有所有斷言都返回真值,測試才能通過。 #### 3.1 維護測試數據庫的模式 為了能運行測試,測試數據庫要有程序當前的數據庫結構。測試幫助方法會檢查測試數據庫中是否有尚未運行的遷移。如果有,會嘗試把 `db/schema.rb` 或 `db/structure.sql` 載入數據庫。之后如果遷移仍處于待運行狀態,會拋出異常。 #### 3.2 運行測試 運行測試執行 `rake test` 命令即可,在這個命令中還要指定要運行的測試文件。 ``` $ rake test test/models/post_test.rb . Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips ``` 上述命令會運行指定文件中的所有測試方法。注意,`test_helper.rb` 在 `test` 文件夾中,因此這個文件夾要使用 `-I` 旗標添加到加載路徑中。 還可以指定測試方法名,只運行相應的測試。 ``` $ rake test test/models/post_test.rb test_the_truth . Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips ``` 上述代碼中的點號(`.`)表示一個通過的測試。如果測試失敗,會看到一個 `F`。如果測試拋出異常,會看到一個 `E`。輸出的最后一行是測試總結。 要想查看失敗測試的輸出,可以在 `post_test.rb` 中添加一個失敗測試。 ``` test "should not save post without title" do post = Post.new assert_not post.save end ``` 我們來運行新添加的測試: ``` $ rake test test/models/post_test.rb test_should_not_save_post_without_title F Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s. 1) Failure: test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]: Failed assertion, no message given. 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips ``` 在輸出中,`F` 表示失敗測試。你會看到相應的調用棧和測試名。隨后還會顯示斷言實際得到的值和期望得到的值。默認的斷言消息提供了足夠的信息,可以幫助你找到錯誤所在。要想讓斷言失敗的消息更具可讀性,可以使用斷言可選的消息參數,例如: ``` test "should not save post without title" do post = Post.new assert_not post.save, "Saved the post without a title" end ``` 運行這個測試后,會顯示一個更友好的斷言失敗消息: ``` 1) Failure: test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]: Saved the post without a title ``` 如果想讓這個測試通過,可以在模型中為 `title` 字段添加一個數據驗證: ``` class Post < ActiveRecord::Base validates :title, presence: true end ``` 現在測試應該可以通過了,再次運行這個測試來驗證一下: ``` $ rake test test/models/post_test.rb test_should_not_save_post_without_title . Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips ``` 你可能注意到了,我們首先編寫一個檢測所需功能的測試,這個測試會失敗,然后編寫代碼,實現所需功能,最后再運行測試,確保測試可以通過。這一過程,在軟件開發中稱為“測試驅動開發”(Test-Driven Development,TDD)。 很多 Rails 開發者都會使用 TDD,這種開發方式可以確保程序的每個功能都能正確運行。本文不會詳細介紹 TDD,如果想學習,可以從 [15 TDD steps to create a Rails application](http://andrzejonsoftware.blogspot.com/2007/05/15-tdd-steps-to-create-rails.html) 這篇文章開始。 要想查看錯誤的輸出,可以在測試中加入一處錯誤: ``` test "should report error" do # some_undefined_variable is not defined elsewhere in the test case some_undefined_variable assert true end ``` 運行測試,很看到以下輸出: ``` $ rake test test/models/post_test.rb test_should_report_error E Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s. 1) Error: test_should_report_error(PostTest): NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x007fe32e24afe0> test/models/post_test.rb:10:in `block in <class:PostTest>' 1 tests, 0 assertions, 0 failures, 1 errors, 0 skips ``` 注意上面輸出中的 `E`,表示測試出錯了。 如果測試方法出現錯誤或者斷言檢測失敗就會終止運行,繼續運行測試組件中的下個方法。測試按照字母順序運行。 測試失敗后會看到相應的調用棧。默認情況下,Rails 會過濾調用棧,只顯示和程序有關的調用棧。這樣可以減少輸出的內容,集中精力關注程序的代碼。如果想查看完整的調用棧,可以設置 `BACKTRACE` 環境變量: ``` $ BACKTRACE=1 rake test test/models/post_test.rb ``` #### 3.3 單元測試要測試什么 理論上,應該測試一切可能出問題的功能。實際使用時,建議至少為每個數據驗證編寫一個測試,至少為模型中的每個方法編寫一個測試。 #### 3.4 可用的斷言 讀到這,詳細你已經大概知道一些斷言了。斷言是測試的核心,是真正用來檢查功能是否符合預期的工具。 斷言有很多種,下面列出了可在 Rails 默認測試庫 `minitest` 中使用的斷言。方法中的 `[msg]` 是可選參數,指定測試失敗時顯示的友好消息。 | 斷言 | 作用 | | --- | --- | | `assert( test, [msg] )` | 確保 `test` 是真值 | | `assert_not( test, [msg] )` | 確保 `test` 是假值 | | `assert_equal( expected, actual, [msg] )` | 確保 `expected == actual` 返回 `true` | | `assert_not_equal( expected, actual, [msg] )` | 確保 `expected != actual` 返回 `true` | | `assert_same( expected, actual, [msg] )` | 確保 `expected.equal?(actual)` 返回 `true` | | `assert_not_same( expected, actual, [msg] )` | 確保 `expected.equal?(actual)` 返回 `false` | | `assert_nil( obj, [msg] )` | 確保 `obj.nil?` 返回 `true` | | `assert_not_nil( obj, [msg] )` | 確保 `obj.nil?` 返回 `false` | | `assert_match( regexp, string, [msg] )` | 確保字符串匹配正則表達式 | | `assert_no_match( regexp, string, [msg] )` | 確保字符串不匹配正則表達式 | | `assert_in_delta( expecting, actual, [delta], [msg] )` | 確保數字 `expected` 和 `actual` 之差在 `delta` 指定的范圍內 | | `assert_not_in_delta( expecting, actual, [delta], [msg] )` | 確保數字 `expected` 和 `actual` 之差不在 `delta` 指定的范圍內 | | `assert_throws( symbol, [msg] ) { block }` | 確保指定的代碼塊會拋出一個 Symbol | | `assert_raises( exception1, exception2, ... ) { block }` | 確保指定的代碼塊會拋出其中一個異常 | | `assert_nothing_raised( exception1, exception2, ... ) { block }` | 確保指定的代碼塊不會拋出其中一個異常 | | `assert_instance_of( class, obj, [msg] )` | 確保 `obj` 是 `class` 的實例 | | `assert_not_instance_of( class, obj, [msg] )` | 確保 `obj` 不是 `class` 的實例 | | `assert_kind_of( class, obj, [msg] )` | 確保 `obj` 是 `class` 或其子類的實例 | | `assert_not_kind_of( class, obj, [msg] )` | 確保 `obj` 不是 `class` 或其子類的實例 | | `assert_respond_to( obj, symbol, [msg] )` | 確保 `obj` 可以響應 `symbol` | | `assert_not_respond_to( obj, symbol, [msg] )` | 確保 `obj` 不可以響應 `symbol` | | `assert_operator( obj1, operator, [obj2], [msg] )` | 確保 `obj1.operator(obj2)` 返回真值 | | `assert_not_operator( obj1, operator, [obj2], [msg] )` | 確保 `obj1.operator(obj2)` 返回假值 | | `assert_send( array, [msg] )` | 確保在 `array[0]` 指定的方法上調用 `array[1]` 指定的方法,并且把 `array[2]` 及以后的元素作為參數傳入,該方法會返回真值。這個方法很奇特吧? | | `flunk( [msg] )` | 確保測試會失敗,用來標記測試還沒編寫完 | Rails 使用的測試框架完全模塊化,因此可以自己編寫新的斷言。Rails 本身就是這么做的,提供了很多專門的斷言,可以簡化測試。 自己編寫斷言屬于進階話題,本文不會介紹。 #### 3.5 Rails 提供的斷言 Rails 為 `test/unit` 框架添加了很多自定義的斷言: Rails adds some custom assertions of its own to the `test/unit` framework: | 斷言 | 作用 | | --- | --- | | `assert_difference(expressions, difference = 1, message = nil) {...}` | 測試 `expressions` 的返回數值和代碼塊的返回數值相差是否為 `difference` | | `assert_no_difference(expressions, message = nil, &amp;block)` | 測試 `expressions` 的返回數值和代碼塊的返回數值相差是否不為 `difference` | | `assert_recognizes(expected_options, path, extras={}, message=nil)` | 測試 `path` 指定的路由是否正確處理,以及 `expected_options` 指定的參數是夠由 `path` 處理。也就是說 Rails 是否能識別 `expected_options` 指定的路由 | | `assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)` | 測試指定的 `options` 能否生成 `expected_path` 指定的路徑。這個斷言是 `assert_recognizes` 的逆測試。`extras` 指定額外的請求參數。`message` 指定斷言失敗時顯示的錯誤消息。 | | `assert_response(type, message = nil)` | 測試響應是否返回指定的狀態碼。可用 `:success` 表示 200-299,`:redirect` 表示 300-399,`:missing` 表示 404,`:error` 表示 500-599。狀態碼可用具體的數字表示,也可用相應的符號表示。詳細信息參見[完整的狀態碼列表](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant),以及狀態碼數字和符號的[對應關系](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant)。 | | `assert_redirected_to(options = {}, message=nil)` | 測試 `options` 是否匹配所執行動作的轉向設定。這個斷言可以匹配局部轉向,所以 `assert_redirected_to(controller: "weblog")` 可以匹配轉向到 `redirect_to(controller: "weblog", action: "show")` 等。還可以傳入具名路由,例如 `assert_redirected_to root_path`,以及 Active Record 對象,例如 `assert_redirected_to @article`。 | | `assert_template(expected = nil, message=nil)` | 測試請求是否由指定的模板文件渲染 | 下一節會介紹部分斷言的用法。 ### 4 為控制器編寫功能測試 在 Rails 中,測試控制器各動作需要編寫功能測試。控制器負責處理程序接收的請求,然后使用視圖渲染響應。 #### 4.1 功能測試要測試什么 應該測試一下內容: * 請求是否成功; * 是否轉向了正確的頁面; * 用戶是否通過了身份認證; * 是否把正確的對象傳給了渲染響應的模板; * 是否在視圖中顯示了相應的消息; 前面我們已經使用 Rails 腳手架生成了 `Post` 資源,在生成的文件中包含了控制器和測試。你可以看一下 `test/controllers` 文件夾中的 `posts_controller_test.rb` 文件。 我們來看一下這個文件中的測試,首先是 `test_should_get_index`。 ``` class PostsControllerTest < ActionController::TestCase test "should get index" do get :index assert_response :success assert_not_nil assigns(:posts) end end ``` 在 `test_should_get_index` 測試中,Rails 模擬了一個發給 `index` 動作的請求,確保請求成功,而且賦值了一個合法的 `posts` 實例變量。 `get` 方法會發起請求,并把結果傳入響應中。可接受 4 個參數: * 所請求控制器的動作,可使用字符串或 Symbol; * 可選的 Hash,指定傳入動作的請求參數(例如,請求字符串參數或表單提交的參數); * 可選的 Hash,指定隨請求一起傳入的會話變量; * 可選的 Hash,指定 Flash 消息的值; 舉個例子,請求 `:show` 動作,請求參數為 `'id' =&gt; "12"`,會話參數為 `'user_id' =&gt; 5`: ``` get(:show, {'id' => "12"}, {'user_id' => 5}) ``` 再舉個例子:請求 `:view` 動作,請求參數為 `'id' =&gt; '12'`,這次沒有會話參數,但指定了 Flash 消息: ``` get(:view, {'id' => '12'}, nil, {'message' => 'booya!'}) ``` 如果現在運行 `posts_controller_test.rb` 文件中的 `test_should_create_post` 測試會失敗,因為前文在模型中添加了數據驗證。 我們來修改 `posts_controller_test.rb` 文件中的 `test_should_create_post` 測試,讓所有測試都通過: ``` test "should create post" do assert_difference('Post.count') do post :create, post: {title: 'Some title'} end assert_redirected_to post_path(assigns(:post)) end ``` 現在你可以運行所有測試,都應該通過。 #### 4.2 功能測試中可用的請求類型 如果熟悉 HTTP 協議就會知道,`get` 是請求的一種類型。在 Rails 功能測試中可以使用 6 種請求: * `get` * `post` * `patch` * `put` * `head` * `delete` 這幾種請求都可作為方法調用,不過前兩種最常用。 功能測試不檢測動作是否能接受指定類型的請求。如果發起了動作無法接受的請求類型,測試會直接退出。 #### 4.3 可用的四個 Hash 使用上述 6 種請求之一發起請求并經由控制器處理后,會產生 4 個 Hash 供使用: * `assigns`:動作中創建在視圖中使用的實例變量; * `cookies`:設置的 cookie; * `flash`:Flash 消息中的對象; * `session`:會話中的對象; 和普通的 Hash 對象一樣,可以使用字符串形式的鍵獲取相應的值。除了 `assigns` 之外,另外三個 Hash 還可使用 Symbol 形式的鍵。例如: ``` flash["gordon"] flash[:gordon] session["shmession"] session[:shmession] cookies["are_good_for_u"] cookies[:are_good_for_u] # Because you can't use assigns[:something] for historical reasons: assigns["something"] assigns(:something) ``` #### 4.4 可用的實例變量 在功能測試中還可以使用下面三個實例變量: * `@controller`:處理請求的控制器; * `@request`:請求對象; * `@response`:響應對象; #### 4.5 設置報頭和 CGI 變量 [HTTP 報頭](http://tools.ietf.org/search/rfc2616#section-5.3) 和 [CGI 變量](http://tools.ietf.org/search/rfc3875#section-4.1)可以通過 `@request` 實例變量設置: ``` # setting a HTTP Header @request.headers["Accept"] = "text/plain, text/html" get :index # simulate the request with custom header # setting a CGI variable @request.headers["HTTP_REFERER"] = "http://example.com/home" post :create # simulate the request with custom env variable ``` #### 4.6 測試模板和布局 如果想測試響應是否使用正確的模板和布局渲染,可以使用 `assert_template` 方法: ``` test "index should render correct template and layout" do get :index assert_template :index assert_template layout: "layouts/application" end ``` 注意,不能在 `assert_template` 方法中同時測試模板和布局。測試布局時,可以使用正則表達式代替字符串,不過字符串的意思更明了。即使布局保存在標準位置,也要包含文件夾的名字,所以 `assert_template layout: "application"` 不是正確的寫法。 如果視圖中用到了局部視圖,測試布局時必須指定局部視圖,否則測試會失敗。所以,如果用到了 `_form` 局部視圖,下面的斷言寫法才是正確的: ``` test "new should render correct layout" do get :new assert_template layout: "layouts/application", partial: "_form" end ``` 如果沒有指定 `:partial`,`assert_template` 會報錯。 #### 4.7 完整的功能測試示例 下面這個例子用到了 `flash`、`assert_redirected_to` 和 `assert_difference`: ``` test "should create post" do assert_difference('Post.count') do post :create, post: {title: 'Hi', body: 'This is my first post.'} end assert_redirected_to post_path(assigns(:post)) assert_equal 'Post was successfully created.', flash[:notice] end ``` #### 4.8 測試視圖 測試請求的響應中是否出現關鍵的 HTML 元素和相應的內容是測試程序視圖的一種有效方式。`assert_select` 斷言可以完成這種測試,其句法簡單而強大。 你可能在其他文檔中見到過 `assert_tag`,因為 `assert_select` 斷言的出現,`assert_tag` 現已棄用。 `assert_select` 有兩種用法: `assert_select(selector, [equality], [message])` 測試 `selector` 選中的元素是否符合 `equality` 指定的條件。`selector` 可以是 CSS 選擇符表達式(字符串),有代入值的表達式,或者 `HTML::Selector` 對象。 `assert_select(element, selector, [equality], [message])` 測試 `selector` 選中的元素和 `element`(`HTML::Node` 實例)及其子元素是否符合 `equality` 指定的條件。 例如,可以使用下面的斷言檢測 `title` 元素的內容: ``` assert_select 'title', "Welcome to Rails Testing Guide" ``` `assert_select` 的代碼塊還可嵌套使用。這時內層的 `assert_select` 會在外層 `assert_select` 塊選中的元素集合上運行斷言: ``` assert_select 'ul.navigation' do assert_select 'li.menu_item' end ``` 除此之外,還可以遍歷外層 `assert_select` 選中的元素集合,這樣就可以在集合的每個元素上運行內層 `assert_select` 了。假如響應中有兩個有序列表,每個列表中都有 4 各列表項,那么下面這兩個測試都會通過: ``` assert_select "ol" do |elements| elements.each do |element| assert_select element, "li", 4 end end assert_select "ol" do assert_select "li", 8 end ``` `assert_select` 斷言很強大,高級用法請參閱[文檔](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/SelectorAssertions.html)。 ##### 4.8.1 其他視圖相關的斷言 There are more assertions that are primarily used in testing views: | 斷言 | 作用 | | --- | --- | | `assert_select_email` | 檢測 Email 的內容 | | `assert_select_encoded` | 檢測編碼后的 HTML,先解碼各元素的內容,然后在代碼塊中調用每個解碼后的元素 | | `css_select(selector)` 或 `css_select(element, selector)` | 返回由 `selector` 選中的所有元素組成的數組,在后一種用法中,首先會找到 `element`,然后在其中執行 `selector` 表達式查找元素,如果沒有匹配的元素,兩種用法都返回空數組 | 下面是 `assert_select_email` 斷言的用法舉例: ``` assert_select_email do assert_select 'small', 'Please click the "Unsubscribe" link if you want to opt-out.' end ``` ### 5 集成測試 集成測試用來測試多個控制器之間的交互,一般用來測試程序中重要的工作流程。 與單元測試和功能測試不同,集成測試必須單獨生成,保存在 `test/integration` 文件夾中。Rails 提供了一個生成器用來生成集成測試骨架。 ``` $ rails generate integration_test user_flows exists test/integration/ create test/integration/user_flows_test.rb ``` 新生成的集成測試如下: ``` require 'test_helper' class UserFlowsTest < ActionDispatch::IntegrationTest # test "the truth" do # assert true # end end ``` 集成測試繼承自 `ActionDispatch::IntegrationTest`,因此可在測試中使用一些額外的幫助方法。在集成測試中還要自行引入固件,這樣才能在測試中使用。 #### 5.1 集成測試中可用的幫助方法 除了標準的測試幫助方法之外,在集成測試中還可使用下列幫助方法: | 幫助方法 | 作用 | | --- | --- | | `https?` | 如果模擬的是 HTTPS 請求,返回 `true` | | `https!` | 模擬 HTTPS 請求 | | `host!` | 設置下次請求使用的主機名 | | `redirect?` | 如果上次請求是轉向,返回 `true` | | `follow_redirect!` | 跟蹤一次轉向 | | `request_via_redirect(http_method, path, [parameters], [headers])` | 發起一次 HTTP 請求,并跟蹤后續全部轉向 | | `post_via_redirect(path, [parameters], [headers])` | 發起一次 HTTP POST 請求,并跟蹤后續全部轉向 | | `get_via_redirect(path, [parameters], [headers])` | 發起一次 HTTP GET 請求,并跟蹤后續全部轉向 | | `patch_via_redirect(path, [parameters], [headers])` | 發起一次 HTTP PATCH 請求,并跟蹤后續全部轉向 | | `put_via_redirect(path, [parameters], [headers])` | 發起一次 HTTP PUT 請求,并跟蹤后續全部轉向 | | `delete_via_redirect(path, [parameters], [headers])` | 發起一次 HTTP DELETE 請求,并跟蹤后續全部轉向 | | `open_session` | 創建一個新會話實例 | #### 5.2 集成測試示例 下面是個簡單的集成測試,涉及多個控制器: ``` require 'test_helper' class UserFlowsTest < ActionDispatch::IntegrationTest fixtures :users test "login and browse site" do # login via https https! get "/login" assert_response :success post_via_redirect "/login", username: users(:david).username, password: users(:david).password assert_equal '/welcome', path assert_equal 'Welcome david!', flash[:notice] https!(false) get "/posts/all" assert_response :success assert assigns(:products) end end ``` 如上所述,集成測試涉及多個控制器,而且用到整個程序的各種組件,從數據庫到調度程序都有。而且,在同一個測試中還可以創建多個會話實例,還可以使用斷言方法創建一種強大的測試 DSL。 下面這個例子用到了多個會話和 DSL: ``` require 'test_helper' class UserFlowsTest < ActionDispatch::IntegrationTest fixtures :users test "login and browse site" do # User david logs in david = login(:david) # User guest logs in guest = login(:guest) # Both are now available in different sessions assert_equal 'Welcome david!', david.flash[:notice] assert_equal 'Welcome guest!', guest.flash[:notice] # User david can browse site david.browses_site # User guest can browse site as well guest.browses_site # Continue with other assertions end private module CustomDsl def browses_site get "/products/all" assert_response :success assert assigns(:products) end end def login(user) open_session do |sess| sess.extend(CustomDsl) u = users(user) sess.https! sess.post "/login", username: u.username, password: u.password assert_equal '/welcome', sess.path sess.https!(false) end end end ``` ### 6 運行測試使用的 Rake 任務 你不用一個一個手動運行測試,Rails 提供了很多運行測試的命令。下表列出了新建 Rails 程序后,默認的 `Rakefile` 中包含的用來運行測試的命令。 | 任務 | 說明 | | --- | --- | | `rake test` | 運行所有單元測試,功能測試和集成測試。還可以直接運行 `rake`,因為默認的 Rake 任務就是運行所有測試。 | | `rake test:controllers` | 運行 `test/controllers` 文件夾中的所有控制器測試 | | `rake test:functionals` | 運行文件夾 `test/controllers`、`test/mailers` 和 `test/functional` 中的所有功能測試 | | `rake test:helpers` | 運行 `test/helpers` 文件夾中的所有幫助方法測試 | | `rake test:integration` | 運行 `test/integration` 文件夾中的所有集成測試 | | `rake test:mailers` | 運行 `test/mailers` 文件夾中的所有郵件測試 | | `rake test:models` | 運行 `test/models` 文件夾中的所有模型測試 | | `rake test:units` | 運行文件夾 `test/models`、`test/helpers` 和 `test/unit` 中的所有單元測試 | | `rake test:all` | 不還原數據庫,快速運行所有測試 | | `rake test:all:db` | 還原數據庫,快速運行所有測試 | ### 7 MiniTest 簡介 Ruby 提供了很多代碼庫,Ruby 1.8 提供有 `Test::Unit`,這是個單元測試框架。前文介紹的所有基本斷言都在 `Test::Unit::Assertions` 中定義。在單元測試和功能測試中使用的 `ActiveSupport::TestCase` 繼承自 `Test::Unit::TestCase`,因此可在測試中使用所有的基本斷言。 Ruby 1.9 引入了 `MiniTest`,這是 `Test::Unit` 的改進版本,兼容 `Test::Unit`。在 Ruby 1.8 中安裝 `minitest` gem 就可使用 `MiniTest`。 關于 `Test::Unit` 更詳細的介紹,請參閱其[文檔](http://ruby-doc.org/stdlib/libdoc/test/unit/rdoc/)。關于 `MiniTest` 更詳細的介紹,請參閱其[文檔](http://ruby-doc.org/stdlib-1.9.3/libdoc/minitest/unit/rdoc/)。 ### 8 測試前準備和測試后清理 如果想在每個測試運行之前以及運行之后運行一段代碼,可以使用兩個特殊的回調。我們以 `Posts` 控制器的功能測試為例,說明這兩個回調的用法: ``` require 'test_helper' class PostsControllerTest < ActionController::TestCase # called before every single test def setup @post = posts(:one) end # called after every single test def teardown # as we are re-initializing @post before every test # setting it to nil here is not essential but I hope # you understand how you can use the teardown method @post = nil end test "should show post" do get :show, id: @post.id assert_response :success end test "should destroy post" do assert_difference('Post.count', -1) do delete :destroy, id: @post.id end assert_redirected_to posts_path end end ``` 在上述代碼中,運行各測試之前都會執行 `setup` 方法,所以在每個測試中都可使用 `@post`。Rails 以 `ActiveSupport::Callbacks` 的方式實現 `setup` 和 `teardown`,因此這兩個方法不僅可以作為方法使用,還可以這么用: * 代碼塊 * 方法(如上例所示) * 用 Symbol 表示的方法名 * Lambda 下面重寫前例,為 `setup` 指定一個用 Symbol 表示的方法名: ``` require 'test_helper' class PostsControllerTest < ActionController::TestCase # called before every single test setup :initialize_post # called after every single test def teardown @post = nil end test "should show post" do get :show, id: @post.id assert_response :success end test "should update post" do patch :update, id: @post.id, post: {} assert_redirected_to post_path(assigns(:post)) end test "should destroy post" do assert_difference('Post.count', -1) do delete :destroy, id: @post.id end assert_redirected_to posts_path end private def initialize_post @post = posts(:one) end end ``` ### 9 測試路由 和 Rails 程序的其他部分一樣,也建議你測試路由。針對前文 `Posts` 控制器中默認生成的 `show` 動作,其路由測試如下: ``` test "should route to post" do assert_routing '/posts/1', {controller: "posts", action: "show", id: "1"} end ``` ### 10 測試郵件程序 測試郵件程序需要一些特殊的工具才能完成。 #### 10.1 確保郵件程序在管控內 和其他 Rails 程序的組件一樣,郵件程序也要做測試,確保其能正常工作。 測試郵件程序的目的是: * 確保處理了郵件(創建及發送) * 確保郵件內容正確(主題,發件人,正文等) * 確保在正確的時間發送正確的郵件; ##### 10.1.1 要全面測試 針對郵件程序的測試分為兩部分:單元測試和功能測試。在單元測試中,單獨運行郵件程序,嚴格控制輸入,然后和已知值(固件)對比。在功能測試中,不用這么細致的測試,只要確保控制器和模型正確的使用郵件程序,在正確的時間發送正確的郵件。 #### 10.2 單元測試 要想測試郵件程序是否能正常使用,可以把郵件程序真正得到的記過和預先寫好的值進行對比。 ##### 10.2.1 固件的另一個用途 在單元測試中,固件用來設定期望得到的值。因為這些固件是示例郵件,不是 Active Record 數據,所以要和其他固件分開,放在單獨的子文件夾中。這個子文件夾位于 `test/fixtures` 文件夾中,其名字來自郵件程序。例如,郵件程序 `UserMailer` 使用的固件保存在 `test/fixtures/user_mailer` 文件夾中。 生成郵件程序時,會為其中每個動作生成相應的固件。如果沒使用生成器,就要手動創建固件。 ##### 10.2.2 基本測試 下面的單元測試針對 `UserMailer` 的 `invite` 動作,這個動作的作用是向朋友發送邀請。這段代碼改進了生成器為 `invite` 動作生成的測試。 ``` require 'test_helper' class UserMailerTest < ActionMailer::TestCase test "invite" do # Send the email, then test that it got queued email = UserMailer.create_invite('me@example.com', 'friend@example.com', Time.now).deliver assert_not ActionMailer::Base.deliveries.empty? # Test the body of the sent email contains what we expect it to assert_equal ['me@example.com'], email.from assert_equal ['friend@example.com'], email.to assert_equal 'You have been invited by me@example.com', email.subject assert_equal read_fixture('invite').join, email.body.to_s end end ``` 在這個測試中,我們發送了一封郵件,并把返回對象賦值給 `email` 變量。在第一個斷言中確保郵件已經發送了;在第二段斷言中,確保郵件包含了期望的內容。`read_fixture` 這個幫助方法的作用是從指定的文件中讀取固件。 `invite` 固件的內容如下: ``` Hi friend@example.com, You have been invited. Cheers! ``` 現在我們稍微深入一點地介紹針對郵件程序的測試。在文件 `config/environments/test.rb` 中,有這么一行設置:`ActionMailer::Base.delivery_method = :test`。這行設置把發送郵件的方法設為 `:test`,所以郵件并不會真的發送出去(避免測試時騷擾用戶),而是添加到一個數組中(`ActionMailer::Base.deliveries`)。 `ActionMailer::Base.deliveries` 數組只會在 `ActionMailer::TestCase` 測試中自動重設,如果想在測試之外使用空數組,可以手動重設:`ActionMailer::Base.deliveries.clear`。 #### 10.3 功能測試 功能測試不只是測試郵件正文和收件人等是否正確這么簡單。在針對郵件程序的功能測試中,要調用發送郵件的方法,檢查相應的郵件是否出現在發送列表中。你可以盡情放心地假定發送郵件的方法本身能順利完成工作。你需要重點關注的是程序自身的業務邏輯,確保能在期望的時間發出郵件。例如,可以使用下面的代碼測試要求朋友的操作是否發出了正確的郵件: ``` require 'test_helper' class UserControllerTest < ActionController::TestCase test "invite friend" do assert_difference 'ActionMailer::Base.deliveries.size', +1 do post :invite_friend, email: 'friend@example.com' end invite_email = ActionMailer::Base.deliveries.last assert_equal "You have been invited by me@example.com", invite_email.subject assert_equal 'friend@example.com', invite_email.to[0] assert_match(/Hi friend@example.com/, invite_email.body) end end ``` ### 11 測試幫助方法 針對幫助方法的測試,只需檢測幫助方法的輸出和預想的值是否一致,所需的測試文件保存在 `test/helpers` 文件夾中。Rails 提供了一個生成器,用來生成幫助方法和測試文件: ``` $ rails generate helper User create app/helpers/user_helper.rb invoke test_unit create test/helpers/user_helper_test.rb ``` 生成的測試文件內容如下: ``` require 'test_helper' class UserHelperTest < ActionView::TestCase end ``` 幫助方法就是可以在視圖中使用的方法。要測試幫助方法,要按照如下的方式混入相應的模塊: ``` class UserHelperTest < ActionView::TestCase include UserHelper test "should return the user name" do # ... end end ``` 而且,因為測試類繼承自 `ActionView::TestCase`,所以在測試中可以使用 Rails 內建的幫助方法,例如 `link_to` 和 `pluralize`。 ### 12 其他測試方案 Rails 內建基于 `test/unit` 的測試并不是唯一的測試方式。Rails 開發者發明了很多方案,開發了很多協助測試的代碼庫,例如: * [NullDB](http://avdi.org/projects/nulldb/):提升測試速度的一種方法,不使用數據庫; * [Factory Girl](https://github.com/thoughtbot/factory_girl/tree/master):固件的替代品; * [Machinist](https://github.com/notahat/machinist/tree/master):另一個固件替代品; * [Fixture Builder](https://github.com/rdy/fixture_builder):運行測試前把預構件(factory)轉換成固件的工具 * [MiniTest::Spec Rails](https://github.com/metaskills/minitest-spec-rails):在 Rails 測試中使用 MiniTest::Spec 這套 DSL; * [Shoulda](http://www.thoughtbot.com/projects/shoulda):對 `test/unit` 的擴展,提供了額外的幫助方法,斷言等; * [RSpec](http://relishapp.com/rspec):行為驅動開發(Behavior-Driven Development,BDD)框架; ### 反饋 歡迎幫忙改善指南質量。 如發現任何錯誤,歡迎修正。開始貢獻前,可先行閱讀[貢獻指南:文檔](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation)。 翻譯如有錯誤,深感抱歉,歡迎 [Fork](https://github.com/ruby-china/guides/fork) 修正,或至此處[回報](https://github.com/ruby-china/guides/issues/new)。 文章可能有未完成或過時的內容。請先檢查 [Edge Guides](http://edgeguides.rubyonrails.org) 來確定問題在 master 是否已經修掉了。再上 master 補上缺少的文件。內容參考 [Ruby on Rails 指南準則](ruby_on_rails_guides_guidelines.html)來了解行文風格。 最后,任何關于 Ruby on Rails 文檔的討論,歡迎到 [rubyonrails-docs 郵件群組](http://groups.google.com/group/rubyonrails-docs)。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看