檢視原始碼 測試情境
需求:本指南假設你已閱讀測試簡介。
需求:本指南假設你已閱讀情境指南。
在測試簡介指南的結尾,我們使用以下指令為貼文產生 HTML 資源:
$ mix phx.gen.html Blog Post posts title body:text
這讓我們免費取得許多模組,包括 Blog 情境和貼文結構,及它們各自的測試檔案。正如我們在情境指南中所學,Blog 情境只是一個模組,其功能鎖定我們的業務領域的特定區域,而貼文結構則對應資料庫中的特定表格。
在本指南中,我們將探討為情境和結構產生的測試。再做其他事之前,我們可以執行 mix test
來確認我們的測試套件可以順利執行。
$ mix test
................
Finished in 0.6 seconds
21 tests, 0 failures
Randomized with seed 638414
棒!我們有 21 項測試,而且全部通過!
測試貼文
如果你開啟 test/hello/blog_test.exs
,你會看到一個檔案,其中包含以下內容:
defmodule Hello.BlogTest do
use Hello.DataCase
alias Hello.Blog
describe "posts" do
alias Hello.Blog.Post
import Hello.BlogFixtures
@invalid_attrs %{body: nil, title: nil}
test "list_posts/0 returns all posts" do
post = post_fixture()
assert Blog.list_posts() == [post]
end
...
在檔案頂端,我們匯入 Hello.DataCase
,我們將會很快看到,它類似於 HelloWeb.ConnCase
。雖然 HelloWeb.ConnCase
設定了處理連線的輔助工具,在測試控制器和檢視時很有用,但 Hello.DataCase
則提供用於處理情境和結構的功能。
接著,我們定義一個別名,因此我們可以將 Hello.Blog
簡稱為 Blog
。
然後我們開始一個 describe "posts"
方塊。在 ExUnit 中,describe
方塊可以用來將功能相似的測試分組。我們將所有與貼文相關的測試分組在一起的原因,是因為 Phoenix 中的情境能夠將多個結構分組在一起。例如,如果我們執行此指令:
$ mix phx.gen.html Blog Comment comments post_id:references:posts body:text
我們將在 Hello.Blog
情境中取得大量新功能,以及測試檔案中全新的 describe "comments"
方塊。
為我們的定義的測試非常直觀。它們會呼叫我們情境中的函式,並對其結果執行斷言。你可以看到,其中一些測試甚至會在資料庫中建立分錄
test "create_post/1 with valid data creates a post" do
valid_attrs = %{body: "some body", title: "some title"}
assert {:ok, %Post{} = post} = Blog.create_post(valid_attrs)
assert post.body == "some body"
assert post.title == "some title"
end
在這裡,你可能會想:Phoenix 要如何確保在其中一項測試中建立的資料不會影響其他測試?很高興你問了這個問題。我們將透過討論 DataCase
來回答這個問題。
DataCase
如果你開啟 test/support/data_case.ex
,將會看到以下內容:
defmodule Hello.DataCase do
use ExUnit.CaseTemplate
using do
quote do
alias Hello.Repo
import Ecto
import Ecto.Changeset
import Ecto.Query
import Hello.DataCase
end
end
setup tags do
Hello.DataCase.setup_sandbox(tags)
:ok
end
def setup_sandbox(tags) do
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Hello.Repo, shared: not tags[:async])
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
end
def errors_on(changeset) do
...
end
end
Hello.DataCase
是另一個 ExUnit.CaseTemplate
。在 using
區塊中,我們可以看到 DataCase
會將所有別名和匯入引入我們的測試。 DataCase
的 set up
塊與 ConnCase
相似。正如我們所見,大部分的 setup
區塊都是用來設定 SQL Sandbox。
SQL Sandbox 讓我們可以將測試寫入資料庫,而不會影響其他測試。簡而言之,我們會在每個測試開始時在資料庫中開始一個交易。完成測試後,我們會自動回滾交易,這樣等於抹除了在測試中新增的所有資料。
更棒的是,即使 SQL Sandbox 同時與資料庫溝通,它也可以讓多個同時執行。這個功能是提供給 PostgreSQL 資料庫,而且在使用時可以透過新增 async: true
旗標,進一步加快你的環境和 controller 測試。
use Hello.DataCase, async: true
在使用沙盒執行非同步測試時,你需要留意一些注意事項,請參閱 Ecto.Adapters.SQL.Sandbox
以取得更多資訊。
最後,在 DataCase
模組的結尾,我們可以找到一個名為 errors_on
的函式,其中有一些示範說明如何使用它。這個函式是為了要測試我們想新增到範本中的任何驗證而使用的。讓我們透過新增自己的驗證再進行測試來試用這個函式。
測試範本
當我們產生 HTML 文章資源時,Phoenix 會同時產生 Blog 環境和 Post 範本。它會為環境產生測試檔案,但不會為範本產生測試檔案。然而,這並不表示我們不需要測試範本,這只是表示我們到目前為止不需要測試範本。
你可能會想:我們什麼時候直接測試環境,什麼時候直接測試範本?這個問題的解答,跟我們什麼時候將程式碼新增到環境,什麼時候新增到範本的問題解答是一樣的。
通則為將所有無副作用的程式碼放在 schema 裡。換句話說,如果你只是在處理資料結構、schema 和 changeset,那就把它放在 schema 裡。內容通常會有建立和更新 schemas 的程式碼,然後寫入資料庫或 API。
我們將為 schema 模組新增其他驗證,這是一個編寫特定於 schema 的測試的好時機。開啟 lib/hello/blog/post.ex
並在 def changeset
中新增下列驗證:
def changeset(post, attrs) do
post
|> cast(attrs, [:title, :body])
|> validate_required([:title, :body])
|> validate_length(:title, min: 2)
end
新的驗證表示標題需要至少包含 2 個字元。我們來為此編寫一個測試。在 test/hello/blog/post_test.exs
建立一個新檔案,並新增以下內容:
defmodule Hello.Blog.PostTest do
use Hello.DataCase, async: true
alias Hello.Blog.Post
test "title must be at least two characters long" do
changeset = Post.changeset(%Post{}, %{title: "I"})
assert %{title: ["should be at least 2 character(s)"]} = errors_on(changeset)
end
end
就這樣。隨著我們的業務領域成長,我們有了定義良好的地方來測試我們的 context 和 schema。