檢視原始碼 ExUnit.Callbacks (ExUnit v1.16.2)
定義 ExUnit 回呼。
此模組定義 setup/1
、setup/2
、setup_all/1
和 setup_all/2
回呼,以及 on_exit/2
、start_supervised/2
和 stop_supervised/1
函數。
設定回呼可用於定義 測試固定裝置,並執行任何初始化程式碼,協助將系統帶入已知狀態。它們透過巨集定義,每個巨集都可選擇接收包含測試狀態和元資料的映射,通常稱為 context
。選擇性地,設定回呼可以透過傳回適當結構的值來擴充套件在測試中使用的內容 (請參閱下方)。
setup_all
回呼僅在每個模組中呼叫一次,在執行任何測試之前。所有 setup
回呼在每個測試之前執行。如果測試案例沒有測試或已篩選出所有測試,則不會執行任何回呼。
setup
和 setup_all
回呼可以透過區塊、命名為本機函數的原子、{module, function}
叢集或原子/叢集清單來定義。
如果透過區塊定義,兩者都可以選擇透過指定它作為參數來接收目前的內容。用於定義測試設定的函數必須接受內容作為單一引數。
測試模組可以定義多個 setup
和 setup_all
回呼,它們會按出現順序呼叫。
start_supervised/2
用於在監督程式下啟動程序。監督程式連結到目前的測試程序。監督程式以及所有子程序都保證在任何 on_exit/2
回呼執行之前終止。
on_exit/2
回呼會依需求註冊,通常用於取消設定回呼執行的動作。 on_exit/2
也可能會參考,允許在未來覆寫回呼。已註冊的 on_exit/2
回呼將永遠執行,而 setup
和 setup_all
中的失敗將停止所有剩餘設定回呼的執行。
最後,setup_all
回呼會在每個模組的獨立程序中執行,而所有 setup
回呼會在與測試本身相同的程序中執行。on_exit/2
回呼會在獨立程序中執行,正如其名稱所暗示的。測試程序會始終以 :shutdown
原因結束,這表示任何連結到測試程序的程序也會結束,儘管是異步的。因此,建議使用 start_supervised/2
來保證同步終止。
以下是測試程序生命週期的摘要
內容
如果 setup_all
或 setup
傳回關鍵字清單、映射或形狀為 {:ok, keyword() | map()}
的元組,關鍵字清單或映射會合併到目前的內容中,並在所有後續 setup_all
、setup
和 test
本身中可用。
傳回 :ok
會讓內容保持不變(在 setup
和 setup_all
回呼中)。
從 setup_all
傳回任何其他內容會強制所有測試失敗,而 setup
的錯誤回應會導致目前的測試失敗。
範例
defmodule AssertionTest do
use ExUnit.Case, async: true
# "setup_all" is called once per module before any test runs
setup_all do
IO.puts("Starting AssertionTest")
# Context is not updated here
:ok
end
# "setup" is called before each test
setup do
IO.puts("This is a setup callback for #{inspect(self())}")
on_exit(fn ->
IO.puts("This is invoked once the test is done. Process: #{inspect(self())}")
end)
# Returns extra metadata to be merged into context.
# Any of the following would also work:
#
# {:ok, %{hello: "world"}}
# {:ok, [hello: "world"]}
# %{hello: "world"}
#
[hello: "world"]
end
# Same as above, but receives the context as argument
setup context do
IO.puts("Setting up: #{context.test}")
# We can simply return :ok when we don't want to add any extra metadata
:ok
end
# Setups can also invoke a local or imported function that returns a context
setup :invoke_local_or_imported_function
test "always pass" do
assert true
end
test "uses metadata from setup", context do
assert context[:hello] == "world"
assert context[:from_named_setup] == true
end
defp invoke_local_or_imported_function(context) do
[from_named_setup: true]
end
end
將設定定義為一系列函式也很常見,這些函式會透過呼叫 setup
或 setup_all
和函式名稱清單來組合在一起。這些函式中的每個函式都會收到內容,並可以傳回 setup
區塊中允許的任何值
defmodule ExampleContextTest do
use ExUnit.Case
setup [:step1, :step2, :step3, {OtherModule, :step4}]
defp step1(_context), do: [step_one: true]
defp step2(_context), do: {:ok, step_two: true} # return values with shape of {:ok, keyword() | map()} allowed
defp step3(_context), do: :ok # Context not modified
test "context was modified", context do
assert context[:step_one] == true
assert context[:step_two] == true
end
end
最後,如 ExUnit.Case
文件中所討論的,請記住,也可以透過 @tag
來設定初始內容中繼資料,然後可以在 setup
區塊中存取
defmodule ExampleTagModificationTest do
use ExUnit.Case
setup %{login_as: username} do
{:ok, current_user: username}
end
@tag login_as: "max"
test "tags modify context", context do
assert context[:login_as] == "max"
assert context[:current_user] == "max"
end
end
摘要
函式
註冊一個在測試結束後執行的回呼。
定義一個在案例中每個測試之前執行的回呼。
定義一個在案例中每個測試之前執行的回呼。
定義一個在案例中所有測試之前執行的回呼。
定義一個在案例中所有測試之前執行的回呼。
與 start_supervised!/2
相同,但將啟動的程序連結到測試程序。
在測試監督下啟動子程序。
與 start_supervised/2
相同,但如果啟動成功,則傳回 PID,如果未正確啟動,則引發例外。
停止透過 start_supervised/2
啟動的子程序。
與 stop_supervised/1
相同,但如果無法停止,則引發例外。
函式
註冊一個在測試結束後執行的回呼。
callback
是不接收任何引數且在與呼叫者不同的程序中執行的函式。其傳回值不相關,且會被捨棄。
on_exit/2
通常會從 setup/1
和 setup_all/1
回呼中呼叫,通常用於取消在設定期間執行的動作。
不過,on_exit/2
也可能動態呼叫。「ID」(name_or_ref
引數)可用於保證回呼只會被呼叫一次。ExUnit 使用此術語來識別 on_exit/2
處理常式:例如,如果您想要覆寫先前的處理常式,請在多個 on_exit/2
呼叫中使用相同的 name_or_ref
。
如果在 on_exit/2
或測試內部呼叫 setup/1
,它會在測試結束後以封鎖方式執行,並在執行下一個測試之前。這表示在 on_exit/2
回呼執行前一個測試時,不會執行相同測試案例中的任何其他測試。 on_exit/2
會在與測試程序不同的程序中執行。另一方面,如果在 setup_all/1
回呼內部呼叫 on_exit/2
,則 callback
會在執行所有測試後執行(有關更多資訊,請參閱 setup_all/1
)。
範例
setup do
File.write!("fixture.json", "{}")
on_exit(fn -> File.rm!("fixture.json") end)
end
您可以在多個 on_exit/2
呼叫中使用相同的 name_or_ref
來「覆寫」已註冊的處理常式
setup do
on_exit(:drop_table, fn ->
Database.drop_table()
end)
end
test "a test that shouldn't drop the table" do
on_exit(:drop_table, fn -> :ok end)
end
過度依賴覆寫此類回呼可能會導致難以理解的測試案例,並產生過多層間接。不過,在某些情況下或例如對於函式庫作者來說,這可能會很有用。
定義一個在案例中每個測試之前執行的回呼。
接受下列其中一項
- 區塊
- 命名為區域函式的原子
{module, function}
組合- 原子和
{module, function}
組合的清單
可以傳回要合併至內容的值,以設定測試的狀態。有關更多詳細資訊,請參閱上方顯示的「內容」區段。
setup/1
回呼會在與測試程序相同的程序中執行。
範例
defp clean_up_tmp_directory(context) do
# perform setup
:ok
end
setup :clean_up_tmp_directory
setup [:clean_up_tmp_directory, :another_setup]
setup do
[conn: Plug.Conn.build_conn()]
end
setup {MyModule, :my_setup_function}
定義一個在案例中每個測試之前執行的回呼。
這類似於 setup/1
,但第一個參數是內容。 block
參數只能是區塊。
如需更多詳細資訊,請參閱上方所示的「內容」區段。
範例
setup context do
[conn: Plug.Conn.build_conn()]
end
定義一個在案例中所有測試之前執行的回呼。
接受下列其中一項
- 區塊
- 命名為區域函式的原子
{module, function}
組合- 原子和
{module, function}
組合的清單
可以傳回要併入 context
的值,以設定測試的狀態。如需更多詳細資訊,請參閱上方所示的「內容」區段。
setup_all/1
回呼會在與測試不同的處理序中執行。所有 setup_all/1
回呼會在同一個處理序中依序執行。
結束處理常式
您在 setup_all/1
回呼中註冊的結束處理常式會在模組中的所有測試執行完畢後一次執行。它們會全部在同一個處理序中執行,而這個處理序是專門用來執行這些處理常式的。這些處理常式會依據其各自的 setup_all/1
回呼的相反順序執行。
範例
# One-arity function name
setup_all :clean_up_tmp_directory
# A module and function
setup_all {MyModule, :my_setup_function}
# A list of one-arity functions and module/function tuples
setup_all [:clean_up_tmp_directory, {MyModule, :my_setup_function}]
defp clean_up_tmp_directory(_context) do
# perform setup
:ok
end
# A block
setup_all do
[conn: Plug.Conn.build_conn()]
end
setup_all/1
傳回的內容會在所有後續的 setup_all
、setup
和 test
中提供。例如,可以存取前一個範例中的 conn
,方法如下
test "fetches current users", %{conn: conn} do
# ...
end
處理常式
您可以在 setup_all/1
回呼中定義「全域」結束處理常式
setup_all do
Database.create_table_for(__MODULE__)
on_exit(fn ->
Database.drop_table_for(__MODULE__)
end)
:ok
end
上述範例中的處理常式只會在執行模組中的所有測試後執行一次。
定義一個在案例中所有測試之前執行的回呼。
與 setup_all/1
相似,但也會採用一個內容。第二個參數必須是一個區塊。請參閱模組文件中的「內容」區段。
範例
setup_all _context do
[conn: Plug.Conn.build_conn()]
end
@spec start_link_supervised!( Supervisor.child_spec() | module() | {module(), term()}, keyword() ) :: pid()
與 start_supervised!/2
相同,但將啟動的程序連結到測試程序。
如果啟動的程序發生崩潰,崩潰會傳播到測試程序,導致測試失敗並印出崩潰原因。
請注意,如果啟動的程序在連結到測試程序之前終止,此函式會以 :noproc
原因結束。
@spec start_supervised( Supervisor.child_spec() | module() | {module(), term()}, keyword() ) :: Supervisor.on_start_child()
在測試監督下啟動子程序。
它預期一個子程序規格或模組,類似於提供給 Supervisor.start_link/2
的規格。例如,如果您的應用程式透過執行來啟動一個監控樹
Supervisor.start_link([MyServer, {OtherSupervisor, ...}], ...)
您可以透過執行來獨立啟動那些測試中的程序
start_supervised(MyServer)
start_supervised({OtherSupervisor, :initial_value})
如果需要變更給定子程序的子程序規格,也可以提供一個關鍵字清單
start_supervised({MyServer, :initial_value}, restart: :temporary)
請參閱 Supervisor
模組,以了解子程序規格和可用的規格金鑰。
在測試監控程式下啟動程序的優點是,它保證在下一項測試開始之前結束。因此,您不需要透過 stop_supervised/1
在測試結束時移除程序。您只需要在測試中程中想要從監控樹移除程序時使用 stop_supervised/1
,因為僅關閉程序會讓它根據其 :restart
值重新啟動。
啟動的程序未連結到測試程序,而崩潰也不一定會導致測試失敗。若要啟動並連結程序以確保任何崩潰也會導致測試失敗,請使用 start_link_supervised!/2
。
此函數在成功時傳回 {:ok, pid}
,否則傳回 {:error, reason}
。
@spec start_supervised!( Supervisor.child_spec() | module() | {module(), term()}, keyword() ) :: pid()
與 start_supervised/2
相同,但如果啟動成功,則傳回 PID,如果未正確啟動,則引發例外。
@spec stop_supervised(id :: term()) :: :ok | {:error, :not_found}
停止透過 start_supervised/2
啟動的子程序。
此函數預期子項規格中的 id
。例如
{:ok, _} = start_supervised(MyServer)
:ok = stop_supervised(MyServer)
如果存在具有此類 id
的受監督程序,則傳回 :ok
,否則傳回 {:error, :not_found}
。
@spec stop_supervised!(id :: term()) :: :ok
與 stop_supervised/1
相同,但如果無法停止,則引發例外。