查看源代码 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/2setup/1 或测试内部调用,则它在测试退出后并在运行下一个测试之前以阻塞方式执行。这意味着来自同一测试用例的任何其他测试在之前测试的 on_exit/2 回调运行时将不会运行。 on_exit/2 在与测试进程不同的进程中执行。另一方面,如果 on_exit/2setup_all/1 回调内部调用,则 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
链接到此宏

setup_all(block)

查看源代码 (宏)

定义在用例中所有测试之前运行的回调。

接受以下内容之一

  • 代码块
  • 命名本地函数的原子
  • {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/1a>。因为简单地关闭进程会导致它根据其 :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 相同,但如果无法停止则引发异常。