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
相关产品推荐
相关产品推荐

