檢視原始碼 ExUnit.Callbacks (ExUnit v1.16.2)

定義 ExUnit 回呼。

此模組定義 setup/1setup/2setup_all/1setup_all/2 回呼,以及 on_exit/2start_supervised/2stop_supervised/1 函數。

設定回呼可用於定義 測試固定裝置,並執行任何初始化程式碼,協助將系統帶入已知狀態。它們透過巨集定義,每個巨集都可選擇接收包含測試狀態和元資料的映射,通常稱為 context。選擇性地,設定回呼可以透過傳回適當結構的值來擴充套件在測試中使用的內容 (請參閱下方)。

setup_all 回呼僅在每個模組中呼叫一次,在執行任何測試之前。所有 setup 回呼在每個測試之前執行。如果測試案例沒有測試或已篩選出所有測試,則不會執行任何回呼。

setupsetup_all 回呼可以透過區塊、命名為本機函數的原子、{module, function} 叢集或原子/叢集清單來定義。

如果透過區塊定義,兩者都可以選擇透過指定它作為參數來接收目前的內容。用於定義測試設定的函數必須接受內容作為單一引數。

測試模組可以定義多個 setupsetup_all 回呼,它們會按出現順序呼叫。

start_supervised/2 用於在監督程式下啟動程序。監督程式連結到目前的測試程序。監督程式以及所有子程序都保證在任何 on_exit/2 回呼執行之前終止。

on_exit/2 回呼會依需求註冊,通常用於取消設定回呼執行的動作。 on_exit/2 也可能會參考,允許在未來覆寫回呼。已註冊的 on_exit/2 回呼將永遠執行,而 setupsetup_all 中的失敗將停止所有剩餘設定回呼的執行。

最後,setup_all 回呼會在每個模組的獨立程序中執行,而所有 setup 回呼會在與測試本身相同的程序中執行。on_exit/2 回呼會在獨立程序中執行,正如其名稱所暗示的。測試程序會始終以 :shutdown 原因結束,這表示任何連結到測試程序的程序也會結束,儘管是異步的。因此,建議使用 start_supervised/2 來保證同步終止。

以下是測試程序生命週期的摘要

  1. 產生測試程序
  2. 執行 setup/2 回呼
  3. 執行測試本身
  4. 停止所有受監督的程序
  5. 測試程序以 :shutdown 原因結束
  6. on_exit/2 回呼會在獨立程序中執行

內容

如果 setup_allsetup 傳回關鍵字清單、映射或形狀為 {:ok, keyword() | map()} 的元組,關鍵字清單或映射會合併到目前的內容中,並在所有後續 setup_allsetuptest 本身中可用。

傳回 :ok 會讓內容保持不變(在 setupsetup_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

將設定定義為一系列函式也很常見,這些函式會透過呼叫 setupsetup_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 相同,但如果無法停止,則引發例外。

函式

連結到此函式

on_exit(name_or_ref \\ make_ref(), callback)

檢視原始碼
@spec on_exit(term(), (-> term())) :: :ok

註冊一個在測試結束後執行的回呼。

callback 是不接收任何引數且在與呼叫者不同的程序中執行的函式。其傳回值不相關,且會被捨棄。

on_exit/2 通常會從 setup/1setup_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

過度依賴覆寫此類回呼可能會導致難以理解的測試案例,並產生過多層間接。不過,在某些情況下或例如對於函式庫作者來說,這可能會很有用。

連結至這個巨集

setup(block_or_functions)

檢視原始碼 (巨集)

定義一個在案例中每個測試之前執行的回呼。

接受下列其中一項

  • 區塊
  • 命名為區域函式的原子
  • {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(context, block)

檢視原始碼 (巨集)

定義一個在案例中每個測試之前執行的回呼。

這類似於 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_allsetuptest 中提供。例如,可以存取前一個範例中的 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(context, block)

檢視原始碼 (巨集)

定義一個在案例中所有測試之前執行的回呼。

setup_all/1 相似,但也會採用一個內容。第二個參數必須是一個區塊。請參閱模組文件中的「內容」區段。

範例

setup_all _context do
  [conn: Plug.Conn.build_conn()]
end
連結到此函式

start_supervised(child_spec_or_module, opts \\ [])

檢視原始碼 (自 1.5.0 起)
@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}

連結到此函式

start_supervised!(child_spec_or_module, opts \\ [])

檢視原始碼 (自 1.6.0 起)
@spec start_supervised!(
  Supervisor.child_spec() | module() | {module(), term()},
  keyword()
) :: pid()

start_supervised/2 相同,但如果啟動成功,則傳回 PID,如果未正確啟動,則引發例外。

連結到此函式

stop_supervised(id)

檢視原始碼 (自 1.5.0 起)
@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}

連結到此函式

stop_supervised!(id)

檢視原始碼 (自 1.10.0 起)
@spec stop_supervised!(id :: term()) :: :ok

stop_supervised/1 相同,但如果無法停止,則引發例外。