v7.0.0
更多資訊請前往 rubyonrails.org: 更多在 Ruby on Rails

測試 Rails 應用程式

本指南包含了 Rails 中用於測試應用程式的內建機制。

閱讀本指南後,您將瞭解:

1 為什麼要為 Rails 應用程式編寫測試?

Rails 使編寫測試變得非常容易。它首先在您建立 models 和 controllers 時產生框架測試程式碼。

通過執行 Rails 測試,即使在進行了一些主要的程式碼重構之後,您也可以確保您的程式碼符合所需的功能。

Rails 測試還可以模擬瀏覽器請求,因此您可以測試應用程式的回應,而無需通過瀏覽器進行測試。

2 測試簡介

測試支援從一開始就被編織到 Rails 結構中。這不是“哦!讓我們支援執行測試,因為它們是新的和酷的”頓悟。

2.1 Rails 從 Word Go 設定測試

一旦您使用 rails new application_name 建立了 Rails 專案,Rails 就會為您建立一個 test 目錄。如果您列出此目錄的內容,那麼您將看到:

$ ls -F test
application_system_test_case.rb  controllers/                     helpers/                         mailers/                         system/
channels/                        fixtures/                        integration/                     models/                          test_helper.rb

Action Cable、mailersmodels 目錄分別用於儲存檢視 helpers、郵件程式和 models 的測試。 channels 目錄用於儲存 Action Cable 連線和通道的測試。 controllers 目錄用於儲存 controllers、路由和 views 的測試。 integration 目錄用於在 controllers 之間儲存對 interactions 的測試。

系統測試目錄儲存系統測試,用於全瀏覽器 測試您的應用程式。系統測試允許您測試您的應用程式 您的使用者體驗它的方式並幫助您測試您的 JavaScript。 系統測試繼承自 Capybara 並在瀏覽器測試中為您執行 應用。

Fixtures 是一種組織測試資料的方式;它們駐留在 fixtures 目錄中。

首次產生相關測試時,還將建立 jobs 目錄。

test_helper.rb 檔案包含測試的預設設定。

application_system_test_case.rb 儲存您系統的預設設定 測試。

2.2 測試環境

預設情況下,每個 Rails 應用程式都有三個環境:開發、測試和生產。

可以類似地修改每個環境的設定。在這種情況下,我們可以通過更改 config/environments/test.rb 中的選項來修改我們的測試環境。

您的測試在 RAILS_ENV=test 下執行。

2.3 Rails遇見Minitest

如果你還記得,我們使用了 bin/rails generate model 命令 Rails 入門 指南。我們建立了我們的第一個 model,除其他外,它在 test 目錄中建立了測試存根:

$ bin/rails generate model article title:string body:text
...
create  app/models/article.rb
create  test/models/article_test.rb
create  test/fixtures/articles.yml
...

test/models/article_test.rb 中的預設測試存根如下所示:

require "test_helper"

class ArticleTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end

逐行檢查此檔案將幫助您瞭解 Rails 測試程式碼和術語。

require "test_helper"

通過需要這個檔案,test_helper.rb 載入了執行我們測試的預設設定。我們將在我們編寫的所有測試中包含它,因此新增到此檔案的任何方法都可用於我們的所有測試。

class ArticleTest < ActiveSupport::TestCase

ArticleTest 類定義了一個 test case,因為它繼承自 ActiveSupport::TestCaseArticleTest 因此具有 ActiveSupport::TestCase 中可用的所有方法。在本指南的後面,我們將看到它為我們提供的一些方法。

在繼承自 Minitest::Test 的類中定義的任何方法 (它是 ActiveSupport::TestCase 的超類)以 test_ 開頭的簡單稱為測試。因此,定義為 test_passwordtest_valid_password 的方法是合法的測試名稱,並在執行測試用例時自動執行。

Rails 還添加了一個 test 方法,該方法採用測試名稱和塊。它產生一個普通的 Minitest::Unit 測試,方法名稱以 test_ 為字首。因此,您不必擔心命名方法,您可以編寫如下內容:

test "the truth" do
  assert true
end

這與編寫此內容大致相同:

def test_the_truth
  assert true
end

儘管您仍然可以使用正常方法定義,但使用 test 巨集可以讓測試名稱更具可讀性。

方法名是用下劃線替換空格產生的。結果不需要是有效的 Ruby 識別符號——名稱可能包含標點符號等。這是因為在 Ruby 技術上,任何字串都可以是方法名稱。這可能需要使用 define_methodsend 呼叫才能正常執行,但正式名稱幾乎沒有限制。

接下來,讓我們看看我們的第一個斷言:

assert true

斷言是一行程式碼,用於評估物件(或表示式)以獲得預期結果。例如,斷言可以檢查:

  • 這個 value = 那個 value 嗎?
  • 這個物件是 nil 嗎?
  • 這行程式碼會丟擲異常嗎?
  • 使用者的密碼是否大於 5 個字元?

每個測試可能包含一個或多個斷言,對允許的斷言數量沒有限制。只有當所有斷言都成功時,測試才會通過。

2.3.1 第一次失敗的測試

要檢視如何報告測試失敗,您可以向 article_test.rb 測試用例新增一個失敗的測試。

test "should not save article without title" do
  article = Article.new
  assert_not article.save
end

讓我們執行這個新新增的測試(其中 6 是定義測試的行數)。

$ bin/rails test test/models/article_test.rb:6
Run options: --seed 44656

# 跑步:

F

Failure:
ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]:
Expected true to be nil or false


rails test test/models/article_test.rb:6



Finished in 0.023918s, 41.8090 runs/s, 41.8090 assertions/s.

1 runs, 1 assertions, 1 failures, 0 errors, 0 skips

在輸出中,F 表示失敗。您可以看到 Failure 下顯示的相應跟蹤以及失敗測試的名稱。接下來的幾行包含堆疊跟蹤,後跟一條訊息,其中提到了斷言的實際 value 和預期的 value。預設斷言訊息提供了足夠的資訊來幫助查明錯誤。為了使斷言失敗訊息更具可讀性,每個斷言都提供了一個可選的訊息引數,如下所示:

test "should not save article without title" do
  article = Article.new
  assert_not article.save, "Saved the article without a title"
end

執行此測試顯示更友好的斷言訊息:

Failure:
ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]:
Saved the article without a title

現在為了讓這個測試通過,我們可以為 title 欄位新增一個 model 級別的驗證。

class Article < ApplicationRecord
  validates :title, presence: true
end

現在測試應該通過了。讓我們再次執行測試來驗證:

$ bin/rails test test/models/article_test.rb:6
Run options: --seed 31252

# 跑步:

.

Finished in 0.027476s, 36.3952 runs/s, 36.3952 assertions/s.

1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

現在,如果你注意到了,我們首先寫了一個測試失敗 功能,然後我們編寫了一些新增功能的程式碼,最後 我們確保我們的測試通過。這種軟體開發方法是 稱為是 測試驅動開發 (TDD)

2.3.2 錯誤的樣子

要檢視錯誤是如何報告的,這裡有一個包含錯誤的測試:

test "should report error" do
  # some_undefined_variable is not defined elsewhere in the test case
  some_undefined_variable
  assert true
end

現在,您可以通過執行測試在控制檯中看到更多輸出:

$ bin/rails test test/models/article_test.rb
Run options: --seed 1808

# 跑步:

.E

Error:
ArticleTest#test_should_report_error:
NameError: undefined local variable or method 'some_undefined_variable' for #<ArticleTest:0x007fee3aa71798>
    test/models/article_test.rb:11:in 'block in <class:ArticleTest>'


rails test test/models/article_test.rb:9



Finished in 0.040609s, 49.2500 runs/s, 24.6250 assertions/s.

2 runs, 1 assertions, 0 failures, 1 errors, 0 skips

注意輸出中的“E”。它表示有錯誤的測試。

一旦出現任何錯誤或錯誤,每個測試方法的執行就會停止 遇到斷言失敗,測試套件繼續下一個 方法。所有測試方法均以隨機順序執行。這 config.active_support.test_order 選項 可用於設定測試訂單。

當測試失敗時,您會看到相應的回溯。預設情況下 Rails 過濾回溯,只會列印與您的相關的行 應用。這消除了框架噪音並有助於專注於您的 程式碼。但是,在某些情況下,您想檢視完整的 回溯。設定 -b(或 --backtrace)引數以啟用此行為:

$ bin/rails test -b test/models/article_test.rb

如果我們希望這個測試通過,我們可以修改它以使用 assert_raises,如下所示:

test "should report error" do
  # some_undefined_variable is not defined elsewhere in the test case
  assert_raises(NameError) do
    some_undefined_variable
  end
end

這個測試現在應該通過了。

2.4 可用斷言

到現在為止,您已經瞥見了一些可用的斷言。斷言是測試的工蜂。他們是實際執行檢查以確保事情按計劃進行的人。

這是您可以使用的斷言的摘錄 Minitest,預設測試庫 由 Rails 使用。 [msg] 引數是一個可選的字串訊息,您可以 指定使您的測試失敗訊息更清晰。

斷言 目的
assert( test, [msg] ) 確保 test 為真。
assert_not( test, [msg] ) 確保 test 為假。
assert_equal( expected, actual, [msg] ) 確保 expected == actual 為真。
assert_not_equal( expected, actual, [msg] ) 確保 expected != actual 為真。
assert_same( expected, actual, [msg] ) 確保 expected.equal?(actual) 為真。
assert_not_same( expected, actual, [msg] ) 確保 expected.equal?(actual) 為假。
assert_nil( obj, [msg] ) 確保 obj.nil? 為真。
assert_not_nil( obj, [msg] ) 確保 obj.nil? 為假。
assert_empty( obj, [msg] ) 確保 objempty?
assert_not_empty( obj, [msg] ) 確保 obj 不是 empty?
assert_match( regexp, string, [msg] ) 確保字串與正則表示式匹配。
assert_no_match( regexp, string, [msg] ) 確保字串與正則表示式不匹配。
assert_includes( collection, obj, [msg] ) 確保 objcollection 中。
assert_not_includes( collection, obj, [msg] ) 確保 obj 不在 collection 中。
assert_in_delta( expected, actual, [delta], [msg] ) 確保數字 expectedactual 在彼此的 delta 之內。
assert_not_in_delta( expected, actual, [delta], [msg] ) 確保數字 expectedactual 不在彼此的 delta 之內。
assert_in_epsilon ( expected, actual, [epsilon], [msg] ) 確保數字 expectedactual 的相對誤差小於 epsilon
assert_not_in_epsilon ( expected, actual, [epsilon], [msg] ) 確保數字 expectedactual 的相對誤差不小於 epsilon
assert_throws( symbol, [msg] ) { block } 確保給定的塊丟擲 symbol。
assert_raises( exception1, exception2, ... ) { block } 確保給定的塊引發給定的異常之一。
assert_instance_of( class, obj, [msg] ) 確保 objclass 的實例。
assert_not_instance_of( class, obj, [msg] ) 確保 obj 不是 class 的實例。
assert_kind_of( class, obj, [msg] ) 確保 objclass 的一個實例或從它下降。
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_predicate ( obj, predicate, [msg] ) 確保 obj.predicate 為真,例如assert_predicate str, :empty?
assert_not_predicate ( obj, predicate, [msg] ) 確保 obj.predicate 為假,例如assert_not_predicate str, :empty?
flunk( [msg] ) 確保失敗。這對於顯式標記尚未完成的測試很有用。

以上是 minitest 支援的斷言子集。對於詳盡的 & 更多最新列表,請檢視 Minitest API 文件,特別是 Minitest::Assertions

由於測試框架的模組化特性,可以建立自己的斷言。事實上,這正是 Rails 所做的。它包括一些專門的斷言,使您的生活更輕鬆。

建立自己的斷言是一個高階主題,我們不會在本教程中介紹。

2.5 Rails 特定斷言

Rails 向 minitest 框架添加了一些自己的自定義斷言:

斷言 目的
assert_difference(expressions, difference = 1, message = nil) {...} 測試作為結果的表示式的返回 value 之間的數值差異在產生的塊中計算的結果。
assert_no_difference(expressions, message = nil, &block) 斷言在呼叫傳入塊之前和之後計算表示式的數值結果不會改變。
assert_changes(expressions, message = nil, from:, to:, &block) 測試在呼叫傳入的塊後計算表示式的結果是否發生變化。
assert_no_changes(expressions, message = nil, &block) 測試在呼叫傳入塊後評估表示式的結果沒有改變。
assert_nothing_raised { block } 確保給定的塊不會引發任何異常。
assert_recognizes(expected_options, path, extras={}, message=nil) 斷言給定路徑的路由被正確處理並且解析的選項(在 expected_options 雜湊中給出)匹配路徑。基本上,它斷言 Rails 識別由 expected_options 給出的路由。
assert_generates(expected_path, options, defaults={}, extras = {}, message=nil) 斷言提供的選項可用於產生提供的路徑。這與 assert_recognizes 相反。 extras 引數用於告訴請求將在查詢字串中的附加請求引數的名稱和 values。 message 引數允許您為斷言失敗指定自定義錯誤訊息。
assert_response(type, message = nil) 斷言回應帶有特定的狀態程式碼。您可以指定 :success 表示 200-299,:redirect 表示 300-399,:missing 表示 404,或 :error 匹配 500-599 範圍。您還可以傳遞顯式狀態編號或其 symbolic 等效項。有關詳細資訊,請參閱 狀態程式碼的完整列表 以及它們的 對映 的工作原理。
assert_redirected_to(options = {}, message=nil) 斷言回應是重定向到匹配給定選項的 URL。您還可以傳遞命名路由,例如 assert_redirected_to root_path 和 Active Record 物件,例如 assert_redirected_to @article

您將在下一章中看到其中一些斷言的用法。

2.6 關於測試用例的簡要說明

Minitest::Assertions 中定義的 assert_equal 等所有基本斷言也可以在我們自己的測試用例中使用的類中使用。實際上,Rails 提供了以下類供您繼承:

這些類中的每一個都包含 Minitest::Assertions,允許我們在測試中使用所有基本斷言。

有關 Minitest 的更多資訊,請參閱 [其 文件](http://docs.seattlerb.org/minitest)。

2.7 Rails 測試執行器

我們可以使用 bin/rails test 命令一次執行所有測試。

或者我們可以通過將包含測試用例的檔名傳遞給 bin/rails test 命令來執行單個測試檔案。

$ bin/rails test test/models/article_test.rb
Run options: --seed 1559

# 跑步:

..

Finished in 0.027034s, 73.9810 runs/s, 110.9715 assertions/s.

2 runs, 3 assertions, 0 failures, 0 errors, 0 skips

這將執行測試用例中的所有測試方法。

您還可以通過提供測試用例來執行特定的測試方法 -n--name 標誌和測試的方法名稱。

$ bin/rails test test/models/article_test.rb -n test_the_truth
Run options: -n test_the_truth --seed 43583

# 跑步:

.

Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.

1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

您還可以通過提供行號在特定行執行測試。

$ bin/rails test test/models/article_test.rb:6 # run specific test and line

您還可以通過提供目錄的路徑來執行整個測試目錄。

$ bin/rails test test/controllers # run all tests from specific directory

測試執行器還提供了許多其他功能,例如快速失敗、延遲測試輸出 在測試執行結束時等等。檢查測試執行器的文件如下:

$ bin/rails test -h
Usage: rails test [options] [files or directories]

You can run a single test by appending a line number to a filename:

    bin/rails test test/models/user_test.rb:27

You can run multiple files and directories at the same time:

    bin/rails test test/controllers test/integration/login_test.rb

By default test failures and errors are reported inline during a run.

minitest options:
    -h, --help                       Display this help.
        --no-plugins                 Bypass minitest plugin auto-loading (or set $MT_NO_PLUGINS).
    -s, --seed SEED                  Sets random seed. Also via env. Eg: SEED=n rake
    -v, --verbose                    Verbose. Show progress processing files.
    -n, --name PATTERN               Filter run on /regexp/ or string.
        --exclude PATTERN            Exclude /regexp/ or string from run.

Known extensions: rails, pride
    -w, --warnings                   Run with Ruby warnings enabled
    -e, --environment ENV            Run tests in the ENV environment
    -b, --backtrace                  Show the complete backtrace
    -d, --defer-output               Output test failures and errors after the test run
    -f, --fail-fast                  Abort test run on first failure or error
    -c, --[no-]color                 Enable color in the output
    -p, --pride                      Pride. Show your testing pride!

3 並行測試

並行測試允許您並行化測試套件。雖然分叉過程是 預設方法,也支援執行緒。並行執行測試減少了它的時間 執行整個測試套件。

3.1 程序並行測試

預設的並行化方法是使用 Ruby 的 DRb 系統 fork 程序。流程 根據提供的工人數量分叉。預設數字是實際核心數 在您所在的機器上,但可以通過傳遞給並行化方法的數字進行更改。

要啟用並行化,請將以下內容新增到您的 test_helper.rb

class ActiveSupport::TestCase
  parallelize(workers: 2)
end

通過的工人數量是程序將被分叉的次數。你可能想要 以不同於 CI 的方式並行化本地測試套件,因此提供了一個環境變數 能夠輕鬆更改測試執行應使用的工作人員數量:

$ PARALLEL_WORKERS=15 bin/rails test

並行化測試時,Active Record 自動處理建立資料庫並將模式載入到資料庫中的每個 過程。資料庫將以與工作人員對應的編號作為字尾。例如,如果你 有 2 個工人,測試將分別建立 test-database-0test-database-1

如果通過的工人數量為 1 或更少,則不會分叉程序並且不會進行測試 被並行化,測試將使用原始的 test-database 資料庫。

提供了兩個掛載機制,一個在程序分叉時執行,一個在分叉程序關閉之前執行。 如果您的應用程式使用多個數據庫或執行其他依賴於資料庫數量的任務,這些會很有用 工人。

parallelize_setup 方法在程序分叉後立即呼叫。 parallelize_teardown 方法 在程序關閉之前呼叫。

class ActiveSupport::TestCase
  parallelize_setup do |worker|
    # setup databases
  end

  parallelize_teardown do |worker|
    # cleanup databases
  end

  parallelize(workers: :number_of_processors)
end

使用執行緒並行測試時,不需要或不可用這些方法。

3.2 執行緒並行測試

如果您更喜歡使用執行緒或正在使用 JRuby,則提供了執行緒並行化選項。螺紋的 並行器由 Minitest 的 Parallel::Executor 支援。

要更改並行化方法以在分叉上使用執行緒,請將以下內容放入您的 test_helper.rb

class ActiveSupport::TestCase
  parallelize(workers: :number_of_processors, with: :threads)
end

從 JRuby 或 TruffleRuby 產生的 Rails 應用程式將自動包含 with: :threads 選項。

傳遞給 parallelize 的工作執行緒數決定了測試將使用的執行緒數。您可以 想要以不同於 CI 的方式並行化您的本地測試套件,因此提供了一個環境變數 能夠輕鬆更改測試執行應使用的工作人員數量:

$ PARALLEL_WORKERS=15 bin/rails test

3.3 測試並行傳輸actions

Rails 自動將任何測試用例包裝在滾動的資料庫 transaction 中 測試完成後返回。這使得測試用例彼此獨立 並且對資料庫的更改僅在單個測試中可見。

當您要測試線上程中並行執行 transactions 的程式碼時, transactions可以互相阻塞,因為測試下已經嵌套了 transaction。

您可以通過設定在測試用例類中禁用 transactions self.use_transactional_tests = false

class WorkerTest < ActiveSupport::TestCase
  self.use_transactional_tests = false

  test "parallel transactions" do
    # start some threads that create transactions
  end
end

禁用 transactional 測試後,您必須清理所有資料測試 測試完成後不會自動回滾更改。

3.4 並行化測試的閾值

並行執行測試會增加資料庫設定和 夾具載入。因此,Rails 不會並行執行涉及 少於 50 個測試。

您可以在 test.rb 中設定此閾值:

config.active_support.test_parallelization_threshold = 100

並且在測試用例級別設定並行化時:

class ActiveSupport::TestCase
  parallelize threshold: 100
end

4 測試資料庫

幾乎每個 Rails 應用程式都與資料庫進行大量互動,因此,您的測試也需要與資料庫進行互動。要編寫有效的測試,您需要了解如何設定此資料庫並使用示例資料填充它。

預設情況下,每個 Rails 應用程式都有三個環境:開發、測試和生產。他們每個人的資料庫都在 config/database.yml 中設定。

專用的測試資料庫允許您單獨設定測試資料並與之互動。通過這種方式,您的測試可以放心地處理測試資料,而無需擔心開發或生產資料庫中的資料。

4.1 維護測試資料庫模式

為了執行你的測試,你的測試資料庫需要有當前的 結構體。測試 helper 檢查你的測試資料庫是否有任何待處理的 migrations。它將嘗試載入您的 db/schema.rbdb/structure.sql 進入測試資料庫。如果 migrations 仍處於待處理狀態,則會出現錯誤 上調。通常這表示您的架構未完全遷移。跑步 針對開發資料庫 (bin/rails db:migrate) 的 migrations 將 更新架構。

如果對現有的 migrations 進行了修改,則測試資料庫需要 被重建。這可以通過執行 bin/rails db:test:prepare 來完成。

4.2 燈具的低調

為了進行良好的測試,您需要考慮設定測試資料。 在 Rails 中,您可以通過定義和自定義夾具來處理此問題。 您可以在 Fixtures API 文件 中找到全面的文件。

4.2.1 什麼是夾具?

Fixtures 是樣本資料的一個花哨詞。 Fixtures 允許您在測試執行之前用預定義的資料填充測試資料庫。 Fixtures 獨立於資料庫並用 YAML 編寫。每個 model 有一個檔案。

Fixtures 並非的目標在於建立您的測試所需的每個物件,並且在僅用於可應用於常見情況的預設資料時得到最佳管理。

您將在 test/fixtures 目錄下找到燈具。當您執行 bin/rails generate model 以建立新的 model 時,Rails 會自動在此目錄中建立夾具存根。

4.2.2 YAML

YAML 格式的裝置是描述示例資料的一種人性化的方式。這些型別的裝置具有 .yml 副檔名(如 users.yml)。

這是一個示例 YAML 夾具檔案:

# 瞧!我是一個 YAML 評論!
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

每個夾具都有一個名稱,後跟以冒號分隔的 key/value 對的縮排列表。記錄通常由空行分隔。您可以通過在第一列中使用 # 字元將註釋放置在夾具檔案中。

如果您正在使用 associations,您可以 在兩個不同的裝置之間定義一個參考節點。這是一個例子 一個 belongs_to/has_many association:

# test/fixtures/categories.yml
about:
  name: About
# test/fixtures/articles.yml
first:
  title: Welcome to Rails!
  category: about
# test/fixtures/action_text/rich_texts.yml
first_content:
  record: first (Article)
  name: content
  body: <div>Hello, from <strong>a fixture</strong></div>

注意在fixtures/articles.yml中發現的first文章的category key有一個about的value,而在ZHTZW_3_WTHZ的Action Text key中發現了ZHTZW_3_WTH_5的ZHTZW_33_32_WTHZ條目。這提示 Active Record 為前者載入在 fixtures/categories.yml 中找到的類別 about,而 Action Text 為後者載入在 fixtures/articles.yml 中找到的文章 first

為了讓 associations 通過名稱相互引用,您可以使用夾具名稱而不是在關聯的夾具上指定 id: 屬性。 Rails 將自動分配一個主要的 key 以在執行之間保持一致。有關此關聯行為的更多資訊,請閱讀 Fixtures API 文件

4.2.3 檔案附件夾具

與其他由 Active Record 支援的 models、Active Storage 附件記錄一樣 從 ActiveRecord::Base 實例繼承,因此可以由 固定裝置。

考慮一個 Article model,它有一個關聯影象作為 thumbnail 附件,以及夾具資料 YAML:

class Article
  has_one_attached :thumbnail
end
# test/fixtures/articles.yml
first:
  title: An Article

假設有一個 [image/png][] 編碼的檔案在 test/fixtures/files/first.png,以下 YAML 夾具條目將 產生相關的 ActiveStorage::BlobActiveStorage::Attachment 記錄:

# test/fixtures/active_storage/blobs.yml
first_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob filename: "first.png" %>
# test/fixtures/active_storage/attachments.yml
first_thumbnail_attachment:
  name: thumbnail
  record: first (Article)
  blob: first_thumbnail_blob
4.2.4 ERB'in It Up

ERB 允許您在模板中嵌入 Ruby 程式碼。 YAML 夾具格式在 Rails 載入夾具時使用 ERB 進行預處理。這允許您使用 Ruby 來幫助您產生一些示例資料。例如,以下程式碼產生一千個使用者:

<% 1000.times do |n| %>
user_<%= n %>:
  username: <%= "user#{n}" %>
  email: <%= "user#{n}@example.com" %>
<% end %>
4.2.5 燈具在 Action

Rails 自動從 test/fixtures 目錄載入所有燈具 預設。載入包括三個步驟:

1.從表中刪除與夾具對應的任何現有資料 2. 將夾具資料載入到表格中 3. 將夾具資料轉儲到方法中,以防您想直接訪問它

提示:為了從資料庫中刪除現有資料,Rails 嘗試禁用參照完整性觸發器(如外部 keys 和檢查約束)。如果您在執行測試時遇到令人討厭的許可權錯誤,請確保資料庫使用者有權在測試環境中禁用這些觸發器。 (在 PostgreSQL 中,只有超級使用者才能禁用所有觸發器。閱讀有關 PostgreSQL 許可權的更多資訊 此處)。

4.2.6 燈具是 Active Record 物件

Fixtures 是 Active Record 的實例。正如上面第 3 點中提到的,您可以直接訪問該物件,因為它可以自動作為一個方法可用,其範圍是測試用例的本地範圍。例如:

# 這將返回名為 david 的裝置的 User 物件
users(:david)

# 這將返回名為 id 的 david 的屬性
users(:david).id

# 還可以訪問 User 類中可用的方法
david = users(:david)
david.call(david.partner)

要一次獲取多個燈具,您可以傳入燈具名稱列表。例如:

# 這將返回一個包含燈具 david 和 steve 的陣列
users(:david, :steve)

5 Model 測試

Model 測試用於測試應用程式的各種模型。

Rails model 測試儲存在 test/models 目錄下。 Rails 提供 一個產生器來為你建立一個 model 測試骨架。

$ bin/rails generate test_unit:model article title:string body:text
create  test/models/article_test.rb
create  test/fixtures/articles.yml

Model 測試沒有像 ActionMailer::TestCase 這樣的超類。相反,它們繼承自 ActiveSupport::TestCase

6 系統測試

系統測試允許您使用您的應用程式測試使用者 interactions,執行測試 在真實或無頭瀏覽器中。系統測試在幕後使用 Capybara。

為了建立 Rails 系統測試,你使用你的 test/system 目錄 應用。 Rails 提供了一個產生器來為你建立系統測試骨架。

$ bin/rails generate system_test users
      invoke test_unit
      create test/system/users_test.rb

新產生的系統測試如下所示:

require "application_system_test_case"

class UsersTest < ApplicationSystemTestCase
  # test "visiting the index" do
  #   visit users_url
  #
  #   assert_selector "h1", text: "Users"
  # end
end

預設情況下,系統測試使用 Selenium 驅動程式執行,使用 Chrome 瀏覽器,螢幕尺寸為 1400x1400。下一節將解釋如何 更改預設設定。

6.1 更改預設設定

Rails 使更改系統測試的預設設定非常簡單。全部 設定被抽象出來,因此您可以專注於編寫測試。

當你產生一個新的應用程式或 scaffold 時,一個 application_system_test_case.rb 檔案 在 test 目錄中建立。這是您所有設定的地方 系統測試應該有效。

如果要更改預設設定,您可以更改系統的內容 測試是“由”驅動的。假設您要將驅動程式從 Selenium 更改為 赤銅礦。首先將 cuprite gem 新增到您的 Gemfile。然後在你的 application_system_test_case.rb 檔案執行以下操作:

require "test_helper"
require "capybara/cuprite"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :cuprite
end

驅動程式名稱是 driven_by 的必需引數。可選引數 可以傳遞給 driven_by 的是瀏覽器的 :using(這隻會 Selenium 使用), :screen_size 改變螢幕大小 螢幕截圖,以及 :options 可用於設定支援的選項 司機。

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :firefox
end

如果你想使用無頭瀏覽器,你可以使用 Headless Chrome 或 Headless Firefox 通過新增 :using 引數中的 headless_chromeheadless_firefox

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome
end

如果你的 Capybara 設定需要比 Rails 提供的更多的設定,這個 額外的設定可以新增到 application_system_test_case.rb 中 檔案。

請參閱Capybara 的文件 用於其他設定。

6.2 截圖 Helper

ScreenshotHelper 是一個 helper,的目標在於捕獲測試的螢幕截圖。 這對於在測試失敗時瀏覽器的 viewing 很有幫助,或者 到 view 截圖稍後除錯。

提供了兩種方法:take_screenshottake_failed_screenshottake_failed_screenshot 自動包含在 before_teardown 裡面 Rails。

take_screenshot helper 方法可以包含在測試中的任何地方 擷取瀏覽器的螢幕截圖。

6.3 實施系統測試

現在我們要向我們的部落格應用程式新增一個系統測試。我們將演示 通過訪問索引頁面並建立新的部落格文章來編寫系統測試。

如果您使用 scaffold 產生器,系統測試骨架會自動產生 為您建立。如果您沒有使用 scaffold 產生器,請先建立一個 系統測試骨架。

$ bin/rails generate system_test articles

它應該為我們建立了一個測試檔案佔位符。隨著輸出 您應該看到上一個命令:

      invoke  test_unit
      create    test/system/articles_test.rb

現在讓我們開啟該檔案並編寫我們的第一個斷言:

require "application_system_test_case"

class ArticlesTest < ApplicationSystemTestCase
  test "viewing the index" do
    visit articles_path
    assert_selector "h1", text: "Articles"
  end
end

測試應該看到文章索引頁有h1,通過。

執行系統測試。

$ bin/rails test:system

預設情況下,執行 bin/rails test 不會執行您的系統測試。 確保執行 bin/rails test:system 來實際執行它們。 您還可以執行 bin/rails test:all 來執行所有測試,包括系統測試。

6.3.1 建立文章系統測試

現在讓我們測試在我們的部落格中建立新文章的流程。

test "should create Article" do
  visit articles_path

  click_on "New Article"

  fill_in "Title", with: "Creating an Article"
  fill_in "Body", with: "Created this article successfully!"

  click_on "Create Article"

  assert_text "Creating an Article"
end

第一步是呼叫visit articles_path。這將把測試帶到 文章索引頁。

然後 click_on "New Article" 會在頁面上找到“新建文章”按鈕 索引頁。這會將瀏覽器重定向到 /articles/new

然後測試會在文章的標題和正文中填寫指定的 文字。填寫欄位後,單擊“建立文章” 傳送 POST 請求以在資料庫中建立新文章。

我們將被重定向迴文章索引頁面,並在那裡斷言 新文章標題中的文字位於文章索引頁面上。

6.3.2 測試多種螢幕尺寸

如果您想在測試桌面的基礎上測試移動尺寸, 您可以建立另一個繼承自 SystemTestCase 的類並在您的 測試套件。在本例中,建立了一個名為 mobile_system_test_case.rb 的檔案 在 /test 目錄中,設定如下。

require "test_helper"

class MobileSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :chrome, screen_size: [375, 667]
end

要使用此設定,請在繼承自 MobileSystemTestCasetest/system 中建立一個測試。 現在,您可以使用多種不同的設定來測試您的應用程式。

require "mobile_system_test_case"

class PostsTest < MobileSystemTestCase

  test "visiting the index" do
    visit posts_url
    assert_selector "h1", text: "Posts"
  end
end
6.3.3 更進一步

系統測試的美妙之處在於它類似於整合測試 它使用您的 controller、model 和 view 測試使用者的 interaction,但是 系統測試更加健壯,並且實際測試您的應用程式就好像 一個真正的使用者正在使用它。展望未來,您可以測試任何使用者 他們自己會在您的應用程式中進行評論,刪除文章, 發表文章草稿等。

7 整合測試

整合測試用於測試我們應用程式的各個部分如何互動。它們通常用於測試我們應用程式中的重要工作流。

為了建立 Rails 整合測試,我們為我們的應用程式使用 test/integration 目錄。 Rails 提供了一個產生器來為我們建立整合測試框架。

$ bin/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。這使我們可以在整合測試中使用一些額外的 helpers。

7.1 Helpers 可用於整合測試

除了標準測試 helpers 之外,從 ActionDispatch::IntegrationTest 繼承還有一些額外的 helpers 在編寫整合測試時可用。簡單介紹一下可以選擇的helpers的三類。

有關處理整合測試執行程式,請參閱 ActionDispatch::Integration::Runner

在執行請求時,我們將有 ActionDispatch::Integration::RequestHelpers 可供我們使用。

如果我們需要修改 session 或整合測試的狀態,請檢視 ActionDispatch::Integration::Session 以提供幫助。

7.2 實現整合測試

讓我們向我們的部落格應用程式新增一個整合測試。我們將從建立新部落格文章的基本工作流程開始,以驗證一切是否正常。

我們將從產生整合測試框架開始:

$ bin/rails generate integration_test blog_flow

它應該為我們建立了一個測試檔案佔位符。隨著輸出 我們應該看到之前的命令:

      invoke  test_unit
      create    test/integration/blog_flow_test.rb

現在讓我們開啟該檔案並編寫我們的第一個斷言:

require "test_helper"

class BlogFlowTest < ActionDispatch::IntegrationTest
  test "can see the welcome page" do
    get "/"
    assert_select "h1", "Welcome#index"
  end
end

我們將在下面的“測試 Views”部分中檢視 assert_select 以查詢請求的結果 HTML。它用於通過斷言 key HTML 元素及其內容的存在來測試我們請求的回應。

當我們訪問我們的根路徑時,我們應該看到為 view 渲染的 welcome/index.html.erb。所以這個斷言應該通過。

7.2.1 建立文章整合

測試我們在部落格中建立新文章並檢視產生的文章的能力如何。

test "can create an article" do
  get "/articles/new"
  assert_response :success

  post "/articles",
    params: { article: { title: "can create", body: "article successfully." } }
  assert_response :redirect
  follow_redirect!
  assert_response :success
  assert_select "p", "Title:\n  can create"
end

讓我們分解這個測試,以便我們能夠理解它。

我們首先呼叫文章 controller 上的 :new action。這個回應應該是成功的。

在此之後,我們向文章 controller 的 :create action 發出 post 請求:

post "/articles",
  params: { article: { title: "can create", body: "article successfully." } }
assert_response :redirect
follow_redirect!

請求後面的兩行是處理我們在建立新文章時設定的重定向。

如果您打算在進行重定向後進行後續請求,請不要忘記呼叫 follow_redirect!

最後,我們可以斷言我們的回應是成功的,並且我們的新文章在頁面上是可讀的。

7.2.2 更進一步

我們能夠成功測試一個非常小的工作流程,用於訪問我們的部落格和建立新文章。如果我們想更進一步,我們可以新增用於評論、刪除文章或編輯評論的測試。整合測試是為我們的應用程式試驗各種用例的好地方。

8 Controller 的功能測試

在Rails中,測試一個控制器的各種actions是編寫功能測試的一種形式。請記住,您的 controllers 處理傳入的 Web 請求到您的應用程式,並最終以呈現的 view 進行回應。在編寫功能測試時,您正在測試 actions 如何處理請求和預期結果或回應,在某些情況下是 HTML view。

8.1 功能測試中要包含的內容

您應該測試以下內容:

  • 網路請求是否成功?
  • 使用者是否被重定向到正確的頁面?
  • 使用者是否成功通過身份驗證?
  • 是否在 view 中向用戶顯示了適當的訊息?
  • 回應中顯示的資訊是否正確?

在 action 中檢視功能測試的最簡單方法是使用 scaffold 產生器產生 controller:

$ bin/rails generate scaffold_controller article title:string body:text
...
create  app/controllers/articles_controller.rb
...
invoke  test_unit
create    test/controllers/articles_controller_test.rb
...

這將產生 controller 程式碼並測試 Article 資源。 您可以檢視 test/controllers 目錄中的檔案 articles_controller_test.rb

如果你已經有一個 controller 並且只想產生測試 scaffold 程式碼 七個預設為actions,可以使用如下命令:

$ bin/rails generate test_unit:scaffold article
...
invoke  test_unit
create    test/controllers/articles_controller_test.rb
...

讓我們來看看一個這樣的測試,來自檔案 articles_controller_test.rbtest_should_get_index

#articles_controller_test.rb
class ArticlesControllerTest < ActionDispatch::IntegrationTest
  test "should get index" do
    get articles_url
    assert_response :success
  end
end

test_should_get_index測試中,Rails在action上模擬一個叫index的請求,確保請求成功 並確保已產生正確的回應機構。

get 方法啟動 Web 請求並將結果填充到 @response 中。它最多可以接受 6 個引數:

  • 您請求的 controller action 的 URI。 這可以是字串或路由 helper(例如 articles_url)的形式。
  • params:帶有要傳遞到 action 的請求引數雜湊的選項 (例如查詢字串引數或文章變數)。
  • headers:用於設定將隨請求傳遞的標頭。
  • env:用於根據需要自定義請求環境。
  • xhr:請求是否為ajax請求。可以設定為 true 以將請求標記為 Ajax。
  • as:用於對不同內容型別的請求進行編碼。

所有這些 keyword 引數都是可選的。

示例:為第一個 Article 呼叫 :show action,傳入一個 HTTP_REFERER 標頭:

get article_url(Article.first), headers: { "HTTP_REFERER" => "http://example.com/home" }

另一個例子:為最後一個 Article 呼叫 :update action,為 params 中的 title 傳入新文字,作為 Ajax 請求:

patch article_url(Article.last), params: { article: { title: "updated" } }, xhr: true

再舉一個例子:呼叫:create action新建一篇文章,傳入 paramstitle 的文字,作為 JSON 請求:

post articles_path, params: { article: { title: "Ahoy!" } }, as: :json

如果您嘗試從 articles_controller_test.rb 執行 test_should_create_article 測試,它將由於新新增的 model 級別驗證而失敗,這是正確的。

讓我們修改 articles_controller_test.rb 中的 test_should_create_article 測試,讓我們所有的測試都通過:

test "should create article" do
  assert_difference("Article.count") do
    post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } }
  end

  assert_redirected_to article_path(Article.last)
end

現在您可以嘗試執行所有測試,它們應該會通過。

如果您按照 Basic Authentication 部分中的步驟進行操作,則需要為每個請求標頭新增授權以使所有測試都通過:

post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } }, headers: { Authorization: ActionController::HttpAuthentication::Basic.encode_credentials("dhh", "secret") }

8.2 功能測試的可用請求型別

如果您熟悉 HTTP 協議,就會知道 get 是一種請求。 Rails 功能測試支援 6 種請求型別:

  • get
  • post
  • patch
  • put
  • head
  • delete

所有請求型別都有您可以使用的等效方法。在典型的 C.R.U.D.應用程式中,您將更頻繁地使用 getpostputdelete

功能測試不驗證指定的請求型別是否被 action 接受,我們更關心結果。此用例存在請求測試,以使您的測試更有目的性。

8.3 測試 XHR (AJAX) 請求

要測試 AJAX 請求,您可以將 xhr: true 選項指定為 getpostpatchputdelete 方法。例如:

test "ajax request" do
  article = articles(:one)
  get article_url(article), xhr: true

  assert_equal "hello world", @response.body
  assert_equal "text/javascript", @response.media_type
end

8.4 啟示錄的三個雜湊

發出並處理請求後,您將有 3 個可供使用的 Hash 物件:

  • cookies - 設定的任何 cookies
  • flash - 任何生活在快閃記憶體中的物體
  • session - 生活在 session 變數中的任何物件

與普通 Hash 物件的情況一樣,您可以通過字串引用 keys 來訪問 values。您也可以通過 symbol 名稱引用它們。例如:

flash["gordon"]               flash[:gordon]
session["shmession"]          session[:shmession]
cookies["are_good_for_u"]     cookies[:are_good_for_u]

8.5 實例變數可用

發出請求後,您還可以訪問功能測試中的三個實例變數:

  • @controller - controller 處理請求
  • @request - 請求物件
  • @response - 回應物件
class ArticlesControllerTest < ActionDispatch::IntegrationTest
  test "should get index" do
    get articles_url

    assert_equal "index", @controller.action_name
    assert_equal "application/x-www-form-urlencoded", @request.media_type
    assert_match "Articles", @response.body
  end
end

8.6 設定標題和 CGI​​ 變數

HTTP 標頭CGI 變數 可以作為標題傳遞:

# 設定一個 HTTP 頭
get articles_url, headers: { "Content-Type": "text/plain" } # simulate the request with custom header

# 設定一個 CGI 變數
get articles_url, headers: { "HTTP_REFERER": "http://example.com/home" } # simulate the request with custom env variable

8.7 測試 flash 通知

如果您還記得之前,啟示錄的三個雜湊值之一是 flash

我們想在有人時向我們的部落格應用程式新增一條 flash 訊息 成功建立新文章。

讓我們首先將此斷言新增到我們的 test_should_create_article 測試中:

test "should create article" do
  assert_difference("Article.count") do
    post articles_url, params: { article: { title: "Some title" } }
  end

  assert_redirected_to article_path(Article.last)
  assert_equal "Article was successfully created.", flash[:notice]
end

如果我們現在執行我們的測試,我們應該看到一個失敗:

$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Run options: -n test_should_create_article --seed 32266

# 跑步:

F

Finished in 0.114870s, 8.7055 runs/s, 34.8220 assertions/s.

  1) Failure:
ArticlesControllerTest#test_should_create_article [/test/controllers/articles_controller_test.rb:16]:
--- expected
+++ actual
@@ -1 +1 @@
-"Article was successfully created."
+nil

1 runs, 4 assertions, 1 failures, 0 errors, 0 skips

現在讓我們在 controller 中實現 flash 訊息。我們的 :create action 現在應該是這樣的:

def create
  @article = Article.new(article_params)

  if @article.save
    flash[:notice] = "Article was successfully created."
    redirect_to @article
  else
    render "new"
  end
end

現在,如果我們執行我們的測試,我們應該看到它通過了:

$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Run options: -n test_should_create_article --seed 18981

# 跑步:

.

Finished in 0.081972s, 12.1993 runs/s, 48.7972 assertions/s.

1 runs, 4 assertions, 0 failures, 0 errors, 0 skips

8.8 放在一起

此時我們的文章 controller 測試了 :index 以及 :new:create actions。如何處理現有資料?

讓我們為 :show action 寫一個測試:

test "should show article" do
  article = articles(:one)
  get article_url(article)
  assert_response :success
end

記住我們之前關於裝置的討論,articles() 方法將使我們能夠訪問我們的文章裝置。

刪除現有文章怎麼樣?

test "should destroy article" do
  article = articles(:one)
  assert_difference("Article.count", -1) do
    delete article_url(article)
  end

  assert_redirected_to articles_path
end

我們還可以新增更新現有文章的測試。

test "should update article" do
  article = articles(:one)

  patch article_url(article), params: { article: { title: "updated" } }

  assert_redirected_to article_path(article)
  # Reload association to fetch updated data and assert that title is updated.
  article.reload
  assert_equal "updated", article.title
end

請注意,我們開始在這三個測試中看到一些重複,它們都訪問相同的 Article 夾具資料。我們可以 D.R.Y.這是通過使用 ActiveSupport::Callbacks 提供的 setupteardown 方法來完成的。

我們的測試現在應該如下所示。暫時先忽略其他測試,為了簡潔起見,我們將它們排除在外。

require "test_helper"

class ArticlesControllerTest < ActionDispatch::IntegrationTest
  # called before every single test
  setup do
    @article = articles(:one)
  end

  # called after every single test
  teardown do
    # when controller is using cache it may be a good idea to reset it afterwards
    Rails.cache.clear
  end

  test "should show article" do
    # Reuse the @article instance variable from setup
    get article_url(@article)
    assert_response :success
  end

  test "should destroy article" do
    assert_difference("Article.count", -1) do
      delete article_url(@article)
    end

    assert_redirected_to articles_path
  end

  test "should update article" do
    patch article_url(@article), params: { article: { title: "updated" } }

    assert_redirected_to article_path(@article)
    # Reload association to fetch updated data and assert that title is updated.
    @article.reload
    assert_equal "updated", @article.title
  end
end

與 Rails 中的其他 callbacks 類似,setupteardown 方法也可以通過傳遞塊、lambda 或方法名稱作為 symbol 來呼叫。

8.9 測試 helpers

為避免程式碼重複,您可以新增自己的測試helpers。 登入 helper 就是一個很好的例子:

# test/test_helper.rb

module SignInHelper
  def sign_in_as(user)
    post sign_in_url(email: user.email, password: user.password)
  end
end

class ActionDispatch::IntegrationTest
  include SignInHelper
end
require "test_helper"

class ProfileControllerTest < ActionDispatch::IntegrationTest

  test "should show profile" do
    # helper is now reusable from any controller test case
    sign_in_as users(:david)

    get profile_url
    assert_response :success
  end
end
8.9.1 使用單獨的檔案

如果您發現 helpers 雜亂無章 test_helper.rb,您可以將它們提取到單獨的檔案中。 儲存它們的一個好地方是 test/libtest/test_helpers

# test/test_helpers/multiple_assertions.rb
module MultipleAssertions
  def assert_multiple_of_forty_two(number)
    assert (number % 42 == 0), 'expected #{number} to be a multiple of 42'
  end
end

這些 helpers 然後可以根據需要明確要求並根據需要包含

require "test_helper"
require "test_helpers/multiple_assertions"

class NumberTest < ActiveSupport::TestCase
  include MultipleAssertions

  test "420 is a multiple of forty two" do
    assert_multiple_of_forty_two 420
  end
end

或者它們可以繼續直接包含在相關的父類中

# test/test_helper.rb
require "test_helpers/sign_in_helper"

class ActionDispatch::IntegrationTest
  include SignInHelper
end
8.9.2 迫切需要 Helpers

您可能會發現在 test_helper.rb 中急切地要求 helpers 很方便,因此您的測試檔案可以隱式訪問它們。這可以使用萬用字元來完成,如下所示

# test/test_helper.rb
Dir[Rails.root.join("test", "test_helpers", "**", "*.rb")].each { |file| require file }

這樣做的缺點是會增加啟動時間,而不是在您的個人測試中手動僅需要必要的檔案。

9 測試路線

與 Rails 應用程式中的所有其他內容一樣,您可以測試您的路線。路由測試駐留在 test/controllers/ 中或者是 controller 測試的一部分。

如果你的應用有複雜的路由,Rails 提供了許多有用的 helpers 來測試它們。

有關 Rails 中可用的路由斷言的更多資訊,請參閱 ActionDispatch::Assertions::RoutingAssertions 的 API 文件。

10 測試 Views

通過斷言 key HTML 元素及其內容的存在來測試對您的請求的回應是測試應用程式 views 的常用方法。與路由測試一樣,檢視測試駐留在 test/controllers/ 中或者是 controller 測試的一部分。 assert_select 方法允許您使用簡單而強大的語法查詢回應的 HTML 元素。

assert_select 有兩種形式:

assert_select(selector, [equality], [message]) 確保通過選擇器在被選元素上滿足相等條件。選擇器可以是一個 CSS 選擇器表示式(字串)或一個帶有替換 values 的表示式。

assert_select(element, selector, [equality], [message]) 通過從 elementNokogiri::XML::NodeNokogiri::XML::NodeSet 的實例)及其後代開始的選擇器確保所有選定元素都滿足相等條件。

例如,您可以使用以下命令驗證回應中標題元素的內容:

assert_select "title", "Welcome to Rails Testing Guide"

您還可以使用巢狀的 assert_select 塊進行更深入的調查。

在以下示例中,li.menu_item 的內部 assert_select 執行 在外部塊選擇的元素集合中:

assert_select "ul.navigation" do
  assert_select "li.menu_item"
end

可以迭代選定元素的集合,以便可以為每個元素單獨呼叫 assert_select

例如,如果回應包含兩個有序列表,每個列表都有四個巢狀的列表元素,那麼以下測試都將通過。

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

這個說法非常有力。更高階的用法,參考它的文件

10.1 其他根據 View 的斷言

還有更多斷言主要用於測試views:

斷言 目的
assert_select_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

11 測試 Helpers

helper 只是一個簡單的 module,您可以在其中定義方法 在您的 views 中可用。

為了測試 helpers,您需要做的就是檢查輸出 helper 方法符合您的期望。與 helpers 相關的測試是 位於 test/helpers 目錄下。

鑑於我們有以下 helper:

module UsersHelper
  def link_to_user(user)
    link_to "#{user.first_name} #{user.last_name}", user
  end
end

我們可以像這樣測試這個方法的輸出:

class UsersHelperTest < ActionView::TestCase
  test "should return the user's full name" do
    user = users(:david)

    assert_dom_equal %{<a href="/user/#{user.id}">David Heinemeier Hansson</a>}, link_to_user(user)
  end
end

此外,由於測試類從 ActionView::TestCase 擴充套件,你有 訪問 Rails' helper 方法,例如 link_topluralize

12 測試您的郵件程式

測試郵件程式類需要一些特定的工具來完成一項徹底的工作。

12.1 控制郵遞員

您的郵件程式類 - 就像您的 Rails 應用程式的所有其他部分一樣 - 應該進行測試以確保它們按預期工作。

測試郵件程式類的目標是確保:

  • 正在處理電子郵件(建立和傳送)
  • 郵件內容正確(主題、發件人、正文等)
  • 在正確的時間傳送正確的電子郵件
12.1.1 來自四面八方

測試郵件程式有兩個方面,單元測試和功能測試。在單元測試中,您使用嚴格控制的輸入隔離執行郵件程式,並將輸出與已知的 value(一個裝置)進行比較。在功能測試中,您不會過多地測試郵件程式產生的微小細節;相反,我們測試我們的 controllers 和 models 是否以正確的方式使用郵件程式。您測試以證明正確的電子郵件是在正確的時間傳送的。

12.2 單元測試

為了測試您的郵件程式是否按預期工作,您可以使用單元測試將郵件程式的實際結果與應該產生的預先編寫的示例進行比較。

12.2.1 賽程的復仇

出於對郵件程式進行單元測試的目的,fixtures 用於提供輸出 should 外觀的示例。因為這些是示例電子郵件,而不是像其他裝置那樣的 Active Record 資料,所以它們與其他裝置分開儲存在自己的子目錄中。 test/fixtures 內的目錄名稱直接對應郵件程式的名稱。因此,對於名為 UserMailer 的郵件程式,夾具應位於 test/fixtures/user_mailer 目錄中。

如果您產生了郵件程式,則產生器不會為郵件程式 actions 建立存根裝置。您必須如上所述自己建立這些檔案。

12.2.2 基本測試用例

這是一個單元測試,用於測試名為 UserMailer 的郵件程式,其 action invite 用於向朋友傳送邀請。它是由產生器為 invite action 建立的基礎測試的改編版本。

require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # Create the email and store it for further assertions
    email = UserMailer.create_invite("me@example.com",
                                     "friend@example.com", Time.now)

    # Send the email, then test that it got queued
    assert_emails 1 do
      email.deliver_now
    end

    # 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 中 多變的。然後我們確保它被髮送(第一個斷言),然後,在 第二批斷言,我們確保電子郵件確實包含我們 預計。 helper read_fixture 用於從該檔案中讀取內容。

email.body.to_s 當只存在一個(HTML 或文字)部分時存在。 如果郵件提供了兩者,您可以針對特定部件測試您的夾具 與 email.text_part.body.to_semail.html_part.body.to_s

這是 invite 夾具的內容:

Hi friend@example.com,

You have been invited.

Cheers!

現在是瞭解更多有關為您的測試編寫測試的合適時機 郵寄者。行 ActionMailer::Base.delivery_method = :testconfig/environments/test.rb 將交付方式設定為測試模式,以便 電子郵件實際上不會被髮送(有助於避免在傳送垃圾郵件時向用戶傳送垃圾郵件) 測試),但它會被附加到一個數組中 (ActionMailer::Base.deliveries)。

ActionMailer::Base.deliveries 陣列僅在 ActionMailer::TestCaseActionDispatch::IntegrationTest 測試。 如果你想在這些測試用例之外有一個乾淨的石板,你可以重置它 手動使用:ActionMailer::Base.deliveries.clear

12.3 功能和系統測試

單元測試允許我們測試電子郵件的屬性,而功能和系統測試允許我們測試使用者 interactions 是否適當地觸發要傳送的電子郵件。例如,您可以檢查邀請好友操作是否正確傳送電子郵件:

# 整合測試
require "test_helper"

class UsersControllerTest < ActionDispatch::IntegrationTest
  test "invite friend" do
    # Asserts the difference in the ActionMailer::Base.deliveries
    assert_emails 1 do
      post invite_friend_url, params: { email: "friend@example.com" }
    end
  end
end
# 系統測試
require "test_helper"

class UsersTest < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome

  test "inviting a friend" do
    visit invite_users_url
    fill_in "Email", with: "friend@example.com"
    assert_emails 1 do
      click_on "Invite"
    end
  end
end

assert_emails 方法與特定的傳送方法無關,並且可以處理使用 deliver_nowdeliver_later 方法傳送的電子郵件。如果我們明確想斷言電子郵件已入隊,我們可以使用 assert_enqueued_emails 方法。更多資訊可以在此處的文件 中找到。

13 測試工作

由於您的自定義作業可以在應用程式內的不同級別排隊, 你需要測試這兩個工作本身(他們排隊時的行為) 並且其他實體正確地將它們排入佇列。

13.1 一個基本的測試用例

預設情況下,當您產生作業時,也會產生關聯的測試 test/jobs 目錄下。以下是計費作業的示例測試:

require "test_helper"

class BillingJobTest < ActiveJob::TestCase
  test "that account is charged" do
    BillingJob.perform_now(account, product)
    assert account.reload.charged_for?(product)
  end
end

這個測試非常簡單,只斷言工作完成了 正如預期的那樣。

預設情況下,ActiveJob::TestCase 會將佇列介面卡設定為 :test,以便 您的工作是內聯執行的。它還將確保所有先前執行的 和排隊的作業在任何測試執行之前被清除,因此您可以安全地假設 在每個測試的範圍內還沒有執行任何作業。

13.2 其他元件中的自定義斷言和測試作業

Active Job 附帶了一堆自定義斷言,可用於減少測試的冗長。有關可用斷言的完整列表,請參閱 ActiveJob::TestHelper 的 API 文件。

確保您的作業正確排隊或執行是一種很好的做法 無論您在何處呼叫它們(例如在您的 controllers 中)。這正是 Active Job 提供的自定義斷言非常有用。例如, 在 model 內:

require "test_helper"

class ProductTest < ActiveSupport::TestCase
  include ActiveJob::TestHelper

  test "billing job scheduling" do
    assert_enqueued_with(job: BillingJob) do
      product.charge(account)
    end
  end
end

14 測試 Action Cable

由於 Action Cable 在您的應用程式中的不同級別使用, 您需要測試通道、連線類本身以及其他 實體廣播正確的訊息。

14.1 連線測試用例

預設情況下,當您使用 Action Cable 產生新的 Rails 應用程式時,還會在 test/channels/application_cable 目錄下產生對基本連線類 (Action Cable) 的測試。

連線測試的目標在於檢查連線的識別符號是否被正確分配 或拒絕任何不正確的連線請求。下面是一個例子:

class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
  test "connects with params" do
    # Simulate a connection opening by calling the `connect` method
    connect params: { user_id: 42 }

    # You can access the Connection object via `connection` in tests
    assert_equal connection.user_id, "42"
  end

  test "rejects connection without params" do
    # Use `assert_reject_connection` matcher to verify that
    # connection is rejected
    assert_reject_connection { connect }
  end
end

您還可以像在整合測試中一樣指定請求 cookies:

test "connects with cookies" do
  cookies.signed[:user_id] = "42"

  connect

  assert_equal connection.user_id, "42"
end

有關詳細資訊,請參閱 ActionCable::Connection::TestCase 的 API 文件。

14.2 通道測試用例

預設情況下,當您產生頻道時,也會產生關聯的測試 test/channels 目錄下。這是一個使用聊天頻道的示例測試:

require "test_helper"

class ChatChannelTest < ActionCable::Channel::TestCase
  test "subscribes and stream for room" do
    # Simulate a subscription creation by calling `subscribe`
    subscribe room: "15"

    # You can access the Channel object via `subscription` in tests
    assert subscription.confirmed?
    assert_has_stream "chat_15"
  end
end

這個測試非常簡單,只斷言通道訂閱了特定流的連線。

您還可以指定基礎連線識別符號。這是一個使用網路通知頻道的示例測試:

require "test_helper"

class WebNotificationsChannelTest < ActionCable::Channel::TestCase
  test "subscribes and stream for user" do
    stub_connection current_user: users(:john)

    subscribe

    assert_has_stream_for users(:john)
  end
end

有關詳細資訊,請參閱 ActionCable::Channel::TestCase 的 API 文件。

14.3 自定義斷言和其他元件內部的測試廣播

Action Cable 附帶了一堆自定義斷言,可用於減少測試的冗長。有關可用斷言的完整列表,請參閱 Action Cable 的 API 文件。

確保在其他元件內(例如在您的 controllers 內)廣播了正確的訊息是一種很好的做法。這正是 Action Cable 提供的自定義斷言非常有用。例如, 在 model 內:

require "test_helper"

class ProductTest < ActionCable::TestCase
  test "broadcast status after charge" do
    assert_broadcast_on("products:#{product.id}", type: "charged") do
      product.charge(account)
    end
  end
end

如果你想測試用 Channel.broadcast_to 製作的廣播,你應該使用 Channel.broadcasting_for 產生底層流名稱:

# app/jobs/chat_relay_job.rb
class ChatRelayJob < ApplicationJob
  def perform_later(room, message)
    ChatChannel.broadcast_to room, text: message
  end
end
# test/jobs/chat_relay_job_test.rb
require "test_helper"

class ChatRelayJobTest < ActiveJob::TestCase
  include ActionCable::TestHelper

  test "broadcast message to room" do
    room = rooms(:all)

    assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Hi!") do
      ChatRelayJob.perform_now(room, "Hi!")
    end
  end
end

15 其他測試資源

15.1 測試時間相關程式碼

Rails 提供內建的 helper 方法,使您能夠斷言您的時間敏感程式碼按預期工作。

這是使用 travel_to helper 的示例:

# 假設使用者在註冊一個月後就有資格獲得禮物。
user = User.create(name: "Gaurish", activation_date: Date.new(2004, 10, 24))
assert_not user.applicable_for_gifting?
travel_to Date.new(2004, 11, 24) do
  assert_equal Date.new(2004, 10, 24), user.activation_date # inside the `travel_to` block `Date.current` is mocked
  assert user.applicable_for_gifting?
end
assert_equal Date.new(2004, 10, 24), user.activation_date # The change was visible only inside the `travel_to` block.

請參閱ActiveSupport::Testing::TimeHelpers API 文件 有關可用時間 helpers 的深入資訊。

回饋

我們鼓勵您幫助提高本指南的品質。

如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。

您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。

如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue

關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區