You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Elixir ExUnit:如何在整个测试套件运行前执行函数?

好问题!确实,当涉及到耗时的全局准备操作(比如你这种创建带全索引的非标准数据库架构),setup和setup_all都没法直接满足「整个测试套件只跑一次」的需求——前者每个测试用例都执行,后者每个测试模块执行一次,都不够高效。下面给你几个靠谱的解决方案:

方案1:利用test_helper.exs执行全局一次性初始化

这是最直接的方式,因为test_helper.exs会在整个测试套件启动的最开始执行一次,所有测试模块加载前就完成准备工作。

示例代码:

# test/test_helper.exs
ExUnit.start()

# 执行你的全局准备操作,比如创建带全索引的数据库架构
MyNonStandardDatabase.create_schema_with_full_indexes()

如果你的初始化是异步操作,记得要等待它完成后再让测试启动——比如用数据库客户端提供的等待函数,或者简单的:timer.sleep(不推荐但应急可用)。

方案2:使用Elixir 1.14+的官方全局钩子(推荐)

从Elixir 1.14开始,ExUnit原生支持global_setup和global_teardown选项,专门用来处理整个测试周期只跑一次的操作,比自定义实现更可靠。

示例代码:

# test/test_helper.exs
ExUnit.start(
  global_setup: [
    fn _context ->
      # 全局准备:创建带全索引的架构
      MyNonStandardDatabase.create_schema_with_full_indexes()
      # 返回值会传递给后续的global_teardown
      :ok
    end
  ],
  global_teardown: [
    fn _context ->
      # 可选:测试结束后清理架构(如果需要的话)
      MyNonStandardDatabase.drop_schema()
    end
  ]
)

方案3:自定义全局标记(兼容旧版本Elixir)

如果你的项目还在使用低于1.14的Elixir,可以通过进程标记来确保操作只执行一次,比如在基础测试模板里做:

# test/support/data_case.ex
defmodule MyApp.DataCase do
  use ExUnit.CaseTemplate

  @before_compile __MODULE__

  def before_compile(_env) do
    # 用进程字典标记是否已经执行过初始化
    unless Process.get(:global_schema_initialized) do
      MyNonStandardDatabase.create_schema_with_full_indexes()
      Process.put(:global_schema_initialized, true)
    end
    nil
  end
end

然后让所有测试模块都use MyApp.DataCase,这样初始化操作只会在第一个测试模块编译前执行一次,后续模块会跳过重复操作。

额外注意事项

  • 确保你的初始化操作是幂等的:即使不小心重复执行(比如手动重启测试),也不会报错或者破坏现有架构。
  • 如果测试过程中需要重置数据,尽量在setup_all或setup里做轻量的数据清空,而不是重建架构——后者耗时太多。

内容的提问来源于stack exchange,提问作者Ole Spaarmann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:32:23