查看源代码 ExUnit.Case (ExUnit v1.16.2)

定义测试用例的助手函数。

此模块必须在其他模块中使用,作为配置和准备它们进行测试的方式。

使用时,它接受以下选项:

  • :async - 配置此模块中的测试与其他模块中的测试同时运行。同一模块中的测试永远不会同时运行。只有当测试不改变任何全局状态时才应该启用它。默认值为 false

  • :register - 当为 false 时,不会在 ExUnit 服务器中注册此模块。这意味着当 ExUnit 套件运行时,此模块不会运行。

use ExUnit.Case

当你 use ExUnit.Case 时,它将导入来自 ExUnit.AssertionsExUnit.CallbacksExUnit.DocTest 以及本模块本身的功能。

示例

defmodule AssertionTest do
  # Use the module
  use ExUnit.Case, async: true

  # The "test" macro is imported by ExUnit.Case
  test "always pass" do
    assert true
  end
end

上下文

所有测试都接收一个上下文作为参数。上下文对于在回调和测试之间共享信息特别有用。

defmodule KVTest do
  use ExUnit.Case

  setup do
    {:ok, pid} = KV.start_link()
    {:ok, pid: pid}
  end

  test "stores key-value pairs", context do
    assert KV.put(context[:pid], :hello, :world) == :ok
    assert KV.get(context[:pid], :hello) == :world
  end
end

由于上下文是一个 map,它可以被模式匹配以提取信息。

test "stores key-value pairs", %{pid: pid} = _context do
  assert KV.put(pid, :hello, :world) == :ok
  assert KV.get(pid, :hello) == :world
end

标签

上下文用于将信息从回调传递到测试。为了将信息从测试传递到回调,ExUnit 提供了标签。

通过标记测试,可以在上下文中访问标签值,允许开发人员自定义测试。让我们看一个例子:

defmodule FileTest do
  # Changing directory cannot be async
  use ExUnit.Case, async: false

  setup context do
    # Read the :cd tag value
    if cd = context[:cd] do
      prev_cd = File.cwd!()
      File.cd!(cd)
      on_exit(fn -> File.cd!(prev_cd) end)
    end

    :ok
  end

  @tag cd: "fixtures"
  test "reads UTF-8 fixtures" do
    File.read("README.md")
  end
end

在上面的例子中,我们定义了一个名为 :cd 的标签,它在 setup 回调中被读取,以配置测试将要运行的工作目录。

标签在与用例模板一起使用时也非常有效 (ExUnit.CaseTemplate),允许用例模板中的回调自定义测试行为。

请注意,标签可以通过两种不同的方式设置:

@tag key: value
@tag :key       # equivalent to setting @tag key: true

如果一个标签被多次给出,最后一个值将获胜。

模块和 describe 标签

可以通过在每个上下文内分别设置 @moduletag@describetag 来为模块或 describe 块中的所有测试设置标签。

defmodule ApiTest do
  use ExUnit.Case
  @moduletag :external

  describe "makes calls to the right endpoint" do
    @describetag :endpoint

    # ...
  end
end

如果你正在设置 @moduletag@describetag 属性,你必须在调用 use ExUnit.Case 之后设置它们,否则你会看到编译错误。

如果相同的键通过 @tag 设置,则 @tag 值具有更高的优先级。

The setup_all blocks only receive tags that are set using @moduletag.

已知标签

以下标签由 ExUnit 自动设置,因此是保留的:

  • :module - 定义测试的模块

  • :file - 定义测试的文件

  • :line - 定义测试的行号

  • :test - 测试名称

  • :async - 如果测试用例处于异步模式

  • :registered - 用于 ExUnit.Case.register_attribute/3

  • :describe - 测试所属的 describe 块

  • :describe_line - describe 块开始的行号

  • :doctest - 被 doctested 的模块或文件(如果是一个 doctest)

  • :doctest_line - 定义 doctest 的行号(如果是一个 doctest)

  • :doctest_data - 关于 doctest 的额外元数据(如果是一个 doctest)

  • :test_type - 打印测试结果时使用的测试类型。它由 ExUnit 设置为 :test:doctest 等,但可以自定义。

以下标签自定义了测试的行为:

  • :capture_log - 查看下面的“日志捕获”部分

  • :skip - 使用给定的理由跳过测试

  • :timeout - 自定义测试超时时间(以毫秒为单位,默认值为 60000)。接受 :infinity 作为超时值。

  • :tmp_dir - (自 v1.11.0 起)查看下面的“临时目录”部分

过滤器

标签也可以用来识别特定的测试,然后可以使用过滤器来包含或排除这些测试。最常见的功能是排除一些特定的测试,这可以通过 ExUnit.configure/1 完成。

# Exclude all external tests from running
ExUnit.configure(exclude: [external: true])

从现在开始,ExUnit 不会运行任何将 :external 选项设置为 true 的测试。这种行为可以通过 :include 选项来逆转,这个选项通常通过命令行传递。

$ mix test --include external:true

运行 mix help test 以获取有关如何通过 Mix 运行过滤器的更多信息。

标签和过滤器的另一个用例是默认情况下排除所有具有特定标签的测试,无论其值如何,并且只包含某个子集。

ExUnit.configure(exclude: :os, include: [os: :unix])

给定的 include/exclude 过滤器可以多次给出。

ExUnit.configure(exclude: [os: :unix, os: :windows])

请记住,默认情况下所有测试都被包含,所以除非它们首先被排除,否则 include 选项没有效果。

日志捕获

ExUnit 可以选择性地抑制在测试期间生成的日志消息的打印。在运行测试时生成的日志消息被捕获,只有当测试失败时,它们才会被打印出来,以帮助调试。

你可以通过在单个测试上标记 :capture_log 来选择这种行为,或者在 ExUnit 配置中为所有测试启用日志捕获。

ExUnit.start(capture_log: true)

此默认值可以被 @tag capture_log: false@moduletag capture_log: false 覆盖。

由于 setup_all 块不属于特定的测试,因此在其中(或在测试之间)生成的日志消息永远不会被捕获。如果你还想抑制这些消息,可以通过全局设置来删除控制台后端:

config :logger, backends: []

临时目录

ExUnit 会自动为标记为 :tmp_dir 的测试创建一个临时目录,并将该目录的路径放入测试上下文中。该目录在创建之前被删除,以确保我们从空白状态开始。

临时目录路径是唯一的(包含测试模块和测试名称),因此适合于并发运行测试。你可以通过将标签设置为字符串来进一步自定义路径,例如:tmp_dir: "my_path",这将使最终路径为:tmp/<module>/<test>/my_path

示例

defmodule MyTest do
  use ExUnit.Case, async: true

  @tag :tmp_dir
  test "with tmp_dir", %{tmp_dir: tmp_dir} do
    assert tmp_dir =~ "with tmp_dir"
    assert File.dir?(tmp_dir)
  end
end

与其他标签一样,:tmp_dir 也可以设置为 @moduletag@describetag

总结

函数

将测试描述在一起。

返回最近注册的测试用例,作为 %ExUnit.Test{} 结构体。

注册一个新的属性,用于在 ExUnit.Case 测试中使用。

注册一个新的 describe 属性,用于在 ExUnit.Case 测试中使用。

注册一个新的模块属性,用于在 ExUnit.Case 测试中使用。

使用给定的环境注册测试。

注册一个函数,作为此用例的一部分运行。

定义一个使用字符串的未实现测试。

使用 message 定义测试。

类型

函数

链接到此宏

describe(message, list)

查看源代码 (自 1.3.0 起) (宏)

将测试描述在一起。

每个 describe 块都接收一个名称,该名称用作后续测试的前缀。在块内,可以调用 ExUnit.Callbacks.setup/1,它将定义一个 setup 回调,仅针对当前块运行。describe 名称也被添加为标签,允许开发人员运行特定块的测试。

示例

defmodule StringTest do
  use ExUnit.Case, async: true

  describe "String.downcase/1" do
    test "with ascii characters" do
      assert String.downcase("HELLO") == "hello"
    end

    test "with Unicode" do
      assert String.downcase("HÉLLÒ") == "héllò"
    end
  end
end

在使用 Mix 时,你可以通过名称运行 describe 块中的所有测试:

$ mix test --only describe:"String.downcase/1"

或者通过传递 describe 块开始的确切行号:

$ mix test path/to/file:123

请注意,describe 块不能嵌套。与其依赖层次结构进行组合,开发人员应该建立在命名 setup 之上。例如:

defmodule UserManagementTest do
  use ExUnit.Case, async: true

  describe "when user is logged in and is an admin" do
    setup [:log_user_in, :set_type_to_admin]

    test ...
  end

  describe "when user is logged in and is a manager" do
    setup [:log_user_in, :set_type_to_manager]

    test ...
  end

  defp log_user_in(context) do
    # ...
  end
end

通过禁止层次结构以支持命名 setup,开发人员可以很容易地看一眼每个 describe 块,并准确地知道所涉及的 setup 步骤。

链接到此函数

get_last_registered_test(mod)

查看源代码 (自 1.15.0 起)
@spec get_last_registered_test(env()) :: ExUnit.Test.t() | nil

返回最近注册的测试用例,作为 %ExUnit.Test{} 结构体。

这被第三方实用程序使用,允许使用测试标签进行编译时配置,而无需在运行时显式传递测试上下文。它旨在在测试模块编译之前在宏中调用。

如果使用已编译的模块调用,则会引发异常。

链接到此函数

register_attribute(env, name, opts \\ [])

查看源代码 (自 1.3.0 起)
@spec register_attribute(env(), atom(), keyword()) :: :ok

注册一个新的属性,用于在 ExUnit.Case 测试中使用。

属性值可以通过 context.registered 获取。注册的值在每次 test/3 后被清除,类似于 @tag

此函数接受与 Module.register_attribute/3 相同的选项。

示例

defmodule MyTest do
  use ExUnit.Case

  ExUnit.Case.register_attribute(__MODULE__, :fixtures, accumulate: true)

  @fixtures :user
  @fixtures {:post, insert: false}
  test "using custom attribute", context do
    assert context.registered.fixtures == [{:post, insert: false}, :user]
  end

  test "custom attributes are cleared per test", context do
    assert context.registered.fixtures == []
  end
end
链接到此函数

register_describe_attribute(env, name, opts \\ [])

查看源代码 (自 1.10.0 起)
@spec register_describe_attribute(env(), atom(), keyword()) :: :ok

注册一个新的 describe 属性,用于在 ExUnit.Case 测试中使用。

属性值可以通过 context.registered 获取。与 @describetag 类似,注册的值会在每个 describe/2 之后清除。

此函数接受与 Module.register_attribute/3 相同的选项。

示例

defmodule MyTest do
  use ExUnit.Case

  ExUnit.Case.register_describe_attribute(__MODULE__, :describe_fixtures, accumulate: true)

  describe "using custom attribute" do
    @describe_fixtures :user
    @describe_fixtures {:post, insert: false}

    test "has attribute", context do
      assert context.registered.describe_fixtures == [{:post, insert: false}, :user]
    end
  end

  describe "custom attributes are cleared per describe" do
    test "doesn't have attributes", context do
      assert context.registered.describe_fixtures == []
    end
  end
end
链接到此函数

register_module_attribute(env, name, opts \\ [])

查看源代码 (自 1.10.0 起)
@spec register_module_attribute(env(), atom(), keyword()) :: :ok

注册一个新的模块属性,用于在 ExUnit.Case 测试中使用。

属性值可以通过 context.registered 获取。

此函数接受与 Module.register_attribute/3 相同的选项。

示例

defmodule MyTest do
  use ExUnit.Case

  ExUnit.Case.register_module_attribute(__MODULE__, :module_fixtures, accumulate: true)

  @module_fixtures :user
  @module_fixtures {:post, insert: false}

  test "using custom attribute", context do
    assert context.registered.module_fixtures == [{:post, insert: false}, :user]
  end

  test "still using custom attribute", context do
    assert context.registered.module_fixtures == [{:post, insert: false}, :user]
  end
end
链接到此函数

register_test(map, test_type, name, tags)

查看源代码 (自 1.3.0 起)
此函数已弃用。请使用 register_test/6 代替。

使用给定的环境注册测试。

此函数已被 register_test/6 弃用,后者在密集循环中执行得更好,因为它避免了 __ENV__

链接到此函数

register_test(mod, file, line, test_type, name, tags)

查看源代码 (自 1.10.0 起)

注册一个函数,作为此用例的一部分运行。

这被像 QuickCheck 这样的第三方项目用来实现像 property/3 这样的宏,它与 test 类似,但定义的是一个属性。查看 test/3 的实现以了解调用此函数的示例。

测试类型将被转换为字符串并进行复数化以进行显示。您可以使用 ExUnit.plural_rule/2 设置自定义复数化。

定义一个使用字符串的未实现测试。

提供了一个方便的宏,允许用字符串定义测试,但尚未实现。生成的测试将始终失败并打印“未实现”错误消息。生成的测试用例也标记为 :not_implemented

示例

test "this will be a test in future"
链接到此宏

test(message, var \\ quote do _ end, contents)

查看源代码 (宏)

使用 message 定义测试。

测试还可以定义一个模式,该模式将与测试上下文匹配。有关上下文的更多信息,请参见 ExUnit.Callbacks

示例

test "true is equal to true" do
  assert true == true
end