查看源代码 mix test (Mix v1.16.2)

运行项目测试。

此任务启动当前应用程序,加载 test/test_helper.exs,然后并行地要求所有与 test/**/*_test.exs 模式匹配的文件。

可以在任务名称后给出文件和/或目录列表,以选择要运行的文件。

$ mix test test/some/particular/file_test.exs
$ mix test test/some/particular/dir

伞形项目中的测试可以从根目录运行,方法是指定完整的套件路径,包括 apps/my_app/test,在这种情况下,将完全跳过其他子应用程序的递归测试。

# To run all tests for my_app from the umbrella root
$ mix test apps/my_app/test

# To run a given test file on my_app from the umbrella root
$ mix test apps/my_app/test/some/particular/file_test.exs

理解测试结果

运行测试套件时,它会打印运行结果以及最后的摘要,如下所示。

$ mix test
...

  1) test greets the world (FooTest)
     test/foo_test.exs:5
     Assertion with == failed
     code:  assert Foo.hello() == :world!
     left:  :world
     right: :world!
     stacktrace:
       test/foo_test.exs:6: (test)

........

Finished in 0.05 seconds (0.00s async, 0.05s sync)
1 doctest, 11 tests, 1 failure

Randomized with seed 646219

对于每个测试,测试套件将打印一个点。失败的测试将立即以下一节中描述的格式打印。

所有测试运行完毕后,我们将打印套件摘要。第一行包含在套件上花费的总时间,然后是花费在异步测试(使用 use ExUnit.Case, async: true 定义)和同步测试上的时间。

Finished in 0.05 seconds (0.00s async, 0.05s sync)

开发人员希望尽可能减少在同步测试上花费的时间,因为同步测试按顺序运行,而异步测试并发运行。

最后,我们运行了多少测试,其中多少个测试失败,多少个测试无效等等。

理解测试失败

首先,它包含失败计数器,然后是测试名称以及定义测试的模块。

1) test greets the world (FooTest)

下一行包含测试在 FILE:LINE 格式中的确切位置。

test/foo_test.exs:5

如果您只想重新运行此测试,您需要做的就是复制上面的行并将其粘贴到 mix test 前面。

$ mix test test/foo_test.exs:5

然后我们显示错误消息、代码片段以及有关失败测试的一般信息。

Assertion with == failed
code:  assert Foo.hello() == :world!
left:  :world
right: :world!

如果您的终端支持颜色(请参见下面的“颜色”部分),通常会在 leftright 侧之间显示差异。最后,我们打印失败的堆栈跟踪。

stacktrace:
  test/foo_test.exs:6: (test)

命令行选项

  • --all-warnings (--no-all-warnings) - 打印所有警告,包括以前的编译(默认情况下为 true,除了错误情况)

  • --color - 在输出中启用颜色

  • --cover - 运行覆盖率工具。请参见下面的“覆盖率”部分

  • --exclude - 排除与过滤器匹配的测试

  • --exit-status - 在测试套件失败时使用备用退出状态(默认值为 2)。

  • --export-coverage - 要将覆盖率结果导出到的文件名称。仅在与 --cover 一起使用时有效

  • --failed - 仅运行上次运行失败的测试

  • --force - 强制编译,无论修改时间如何

  • --formatter - 设置将打印结果的格式化程序模块。默认为 ExUnit 的内置 CLI 格式化程序

  • --include - 包括与过滤器匹配的测试

  • --listen-on-stdin - 运行测试,然后监听 stdin。接收到换行符后,它将重新运行测试。请参见下面的“文件系统监视器”部分

  • --max-cases - 设置异步运行的测试最大数量。只有来自不同模块的测试才能并行运行。默认为核心数量的两倍

  • --max-failures - 当达到此数量的测试失败时,套件将停止评估测试。如果省略,它将运行所有测试

  • --no-archives-check - 不检查存档

  • --no-color - 在输出中禁用颜色

  • --no-compile - 不编译,即使文件需要编译

  • --no-deps-check - 不检查依赖项

  • --no-elixir-version-check - 不检查 mix.exs 中的 Elixir 版本

  • --no-start - 编译后不启动应用程序

  • --only - 仅运行与过滤器匹配的测试

  • --partitions - 设置将测试拆分的分割数量。它必须是一个大于零的数字。如果设置为 1,则它将作为无操作。如果大于 1,则还必须使用 MIX_TEST_PARTITION 环境变量设置当前测试运行中使用的分割。有关更多信息,请参见“操作系统进程分割”部分

  • --preload-modules - 预加载应用程序中定义的所有模块

  • --profile-require time - 分析花费在要求测试文件上的时间。仅用于调试。测试套件不运行。

  • --raise - 如果测试套件失败,则引发异常

  • --seed - 种植用于随机化测试顺序的随机数生成器;--seed 0 禁用随机化,因此单个文件中的测试将始终按定义顺序运行

  • --slowest - 打印 N 个最慢测试的计时信息。自动设置 --trace--preload-modules

  • --stale - 仅运行引用自上次使用 --stale 运行测试以来更改的模块的测试。您可以在下面的“--stale 选项”部分中了解更多关于此选项的信息

  • --timeout - 设置测试的超时时间

  • --trace - 使用详细报告运行测试。自动将 --max-cases 设置为 1。请注意,在跟踪模式下,测试超时将被忽略,因为超时设置为 :infinity

  • --warnings-as-errors - (自 v1.12.0 起)将警告视为错误并返回非零退出状态。此选项仅适用于测试文件。要在编译和测试期间将警告视为错误,请运行

    MIX_ENV=test mix do compile --warnings-as-errors + test --warnings-as-errors

配置

这些配置可以在 mix.exsdef project 部分设置。

  • :test_coverage - 一组要传递给覆盖机制的选项。请参见“覆盖率”部分以了解更多信息

  • :test_elixirc_options - 加载/编译测试文件时使用的编译器选项。默认情况下,它会禁用调试块和文档块

  • :test_paths - 包含测试文件的路径列表。如果存在 test 目录,则默认为 ["test"];否则,默认为 []。预计所有测试路径都包含一个 test_helper.exs 文件

  • :test_pattern - 加载测试文件的模式。默认为 *_test.exs

  • :warn_test_pattern - 用于匹配可能命名错误的测试文件并显示警告的模式。默认为 *_test.ex

颜色

在大多数 Unix 终端上,颜色默认情况下是启用的。它们也适用于 Windows 10 中的 Windows 控制台,尽管必须通过运行以下命令在注册表中为当前用户显式启用它。

$ reg add HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1

运行完上面的命令后,您必须重新启动当前控制台。

过滤器

ExUnit 提供标签和过滤功能,允许开发人员选择要运行的测试。最常见的功能是在您的测试帮助程序文件中默认排除某些特定测试。

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

然后,只要需要,就可以通过 --include 选项包含这些测试在运行中。

$ mix test --include external:true

上面的示例将运行所有将外部选项设置为 true 的测试。还可以包含具有给定标签的所有示例,无论其值如何。

$ mix test --include external

请注意,默认情况下会包含所有测试,因此,除非首先排除它们(在测试帮助程序中或通过 --exclude 选项),否则 --include 选项将不起作用。

出于这个原因,Mix 还提供了一个 --only 选项,它会排除所有测试并仅包含给定的测试。

$ mix test --only external

这类似于

$ mix test --include external --exclude test

不同之处在于,当使用 --only 选项时,如果未执行任何测试,则测试套件将失败。

如果正在测试单个文件,则可以传递一个或多个特定行号以仅运行这些给定的测试。

$ mix test test/some/particular/file_test.exs:12

这等同于

$ mix test --exclude test --include line:12 test/some/particular/file_test.exs

$ mix test test/some/particular/file_test.exs:12:24

这等同于

$ mix test --exclude test --include line:12 --include line:24 test/some/particular/file_test.exs

如果给定行开始一个 describe 块,则该行过滤器将运行其中的所有测试。否则,它将运行在给定行号或之前最接近的测试。

覆盖率

Elixir 通过 --cover 标志提供内置的基于行的测试覆盖率。测试覆盖率显示了在测试运行期间执行了哪些代码行以及在哪些文件中执行了这些代码行。

局限性

Elixir 中的覆盖率有以下限制

  • 字面量,例如原子、字符串和数字,不会被覆盖率跟踪。例如,如果函数只是返回 :ok,则原子 :ok 本身不会被覆盖率考虑在内;

  • 宏,例如由 defmacro/2defguard/2 定义的宏,以及仅由宏调用的代码永远不会被视为已覆盖,除非它们也在测试本身期间被调用。这是因为宏是在编译时调用的,在测试覆盖率检测开始之前;

配置

:test_coverage 配置覆盖工具并接受以下选项

  • :output - 覆盖结果的输出目录。默认为 "cover"

  • :tool - 指定要使用的覆盖工具的模块。

  • :summary - 在每次覆盖运行结束时,将打印每个模块的摘要,结果以红色或绿色显示,具体取决于百分比是否低于或高于给定阈值。如果总覆盖率低于阈值,则任务将以状态 1 退出。 :summary 选项允许您自定义摘要生成,默认为 [threshold: 90],但可以设置为 false 以禁用此类报告。

  • :export - 要将结果导出到的文件名,而不是动态生成覆盖结果。 .coverdata 扩展名将自动添加到给定文件。此选项是通过 --export-coverage 选项或使用进程分割自动设置的。请参见 mix test.coverage 以从多个导出文件中编译报告。

  • :ignore_modules - 要从生成报告和摘要中忽略的模块。它是一个模块名称(作为原子)和正则表达式的列表,这些正则表达式与模块名称匹配。

  • :local_only - 默认情况下,覆盖率仅跟踪本地调用。如果计划跨节点运行覆盖率,请将此选项设置为 false。

默认情况下,使用 OTP 的 cover 的包装器作为默认覆盖率工具。您可以在 mix test.coverage 文档中了解更多有关其工作原理的信息。您可以选择以下工具

def project() do
  [
    ...
    test_coverage: [tool: CoverModule]
    ...
  ]
end

CoverModule 可以是任何导出 start/2 的模块,接收编译路径和 test_coverage 选项作为参数。它必须返回 nil 或一个零元匿名函数,该函数将在测试套件完成后运行。

操作系统进程分区

虽然 ExUnit 支持在同一个 Elixir 实例中并发运行测试的能力,但并非总是可以并发运行所有测试。例如,某些测试可能依赖于全局资源。

出于这个原因,mix test 支持将测试文件跨不同 Elixir 实例进行分区。这可以通过将 --partitions 选项设置为一个整数来完成,该整数表示分区数量,并设置 MIX_TEST_PARTITION 环境变量来控制特定实例运行的测试分区。如果您想将测试分布到多台机器上,这也很有用。

例如,要将测试套件拆分为 4 个分区并运行它们,您将使用以下命令

$ MIX_TEST_PARTITION=1 mix test --partitions 4
$ MIX_TEST_PARTITION=2 mix test --partitions 4
$ MIX_TEST_PARTITION=3 mix test --partitions 4
$ MIX_TEST_PARTITION=4 mix test --partitions 4

测试文件在预先以循环方式进行排序。请注意,分区本身作为环境变量给出,以便可以在配置文件和测试脚本中访问它。例如,它可以用于在 config/test.exs 中为每个分区设置不同的数据库实例。

如果启用了分区并且使用了 --cover,则不会生成覆盖率报告,因为它们只包含覆盖率数据的子集。相反,覆盖率数据将导出到文件中,例如 cover/MIX_TEST_PARTITION.coverdata。在您获得 cover/ 中所有分区的結果后,您可以运行 mix test.coverage 来获取统一的报告。

--stale 选项

--stale 命令行选项尝试仅运行引用自上次运行此任务(使用 --stale)以来已更改的模块的测试文件。

第一次使用 --stale 运行此任务时,将运行所有测试并生成清单。在后续运行中,如果测试文件引用的任何模块(以及这些模块递归引用的任何模块)自上次使用 --stale 运行以来已修改,则该测试文件将被标记为“陈旧”。如果测试文件自上次使用 --stale 运行以来已更改,则该测试文件也将被标记为“陈旧”。

--stale 选项对于软件迭代非常有用,因为它允许您在对代码库进行更改时仅运行相关的测试。

文件系统监视器

您可以通过命令行使用 --listen-on-stdin 选项将 mix test 与文件系统监视器集成。例如,您可以使用 fswatch 或类似工具在每次发生更改时发出换行符,这将导致您的测试套件重新运行

$ fswatch lib test | mix test --listen-on-stdin

这可以与 --stale 选项结合使用,以仅重新运行已更改的测试文件以及由于 lib 中的更改而变得陈旧的测试。

中止套件

可以使用 Ctrl+\ 中止测试套件,这会向 Erlang VM 发送 SIGQUIT 信号。ExUnit 将拦截此信号以显示所有已中止的测试并打印到目前为止收集的结果。

如果套件卡住了,并且您不想等到超时时间过去(默认值为 30 秒),这将非常有用。