如何在ExUnit测试同进程内实现同步清理以替代on_exit
解决方案
最优方案:轻量重载test宏(无侵入、易维护)
你不需要修改现有任何测试用例代码,也不需要完整复制维护ExUnit的test宏实现,只需要在现有PersistenceTestCase的using块中对原生test宏做一层包裹,自动注入清理逻辑即可:
defmodule PersistenceTestCase do use ExUnit.CaseTemplate using do quote do # 先排除ExUnit默认导入的test宏 import ExUnit.Callbacks, except: [test: 1, test: 2, test: 3] @doc """ 重载test宏,自动在测试体末尾注入同步清理逻辑 接口和原生ExUnit test完全兼容,支持所有原生参数 """ defmacro test(message, var \\ quote(do: _), contents) do # 用try/after包裹原有测试体,确保清理逻辑无论测试结果如何都会执行 wrapped_contents = quote do try do unquote(contents) after # 同步等待所有数据库连接释放,最多等待100ms Ecto.Adapters.SQL.Sandbox.disconnect_all(App.Repo, 100) # 如果有自定义的等待派生进程退出的逻辑,也可以放在这里 end end # 调用原生test宏执行包裹后的测试逻辑 ExUnit.Callbacks.test(unquote(message), unquote(var), unquote(wrapped_contents)) end end end setup tags do # 原有setup逻辑保持不变 Ecto.Adapters.SQL.Sandbox.mode(App.Repo, :manual) :ok = Ecto.Adapters.SQL.Sandbox.checkout(App.Repo) unless tags[:async] do Ecto.Adapters.SQL.Sandbox.mode(App.Repo, {:shared, self()}) end on_exit(fn -> Ecto.Adapters.SQL.Sandbox.mode(App.Repo, :auto) end) end end
方案优势
- 完全无侵入:所有已经
use PersistenceTestCase的测试模块无需做任何修改,自动生效 - 无竞态问题:清理逻辑在测试进程的上下文中同步执行,在测试进程退出前就完成所有数据库资源释放,完全避免Postgrex的连接报错
- 维护成本极低:只是对原生test宏做轻量包裹,不需要复制维护ExUnit的宏源码,后续ExUnit版本升级几乎不会出现兼容问题
备用方案:无需修改宏的临时处理
如果暂时不愿意重载宏,也可以提高Postgrex的错误日志等级过滤该类报错,只需要在config/test.exs中添加配置:
config :logger, :handle_otp_reports, true config :logger, :discard_threshold_for_error_logger, 1000 config :logger, :compile_time_purge_matching, [[level_lower_than: :critical], [application: :postgrex, message: &String.contains?(&1, "owner #PID")]]
该方案不会修复问题本身,只是过滤非致命的报错日志,适合临时快速解决CI报错的场景。
内容的提问来源于stack exchange,提问作者s3cur3
相关产品推荐
相关产品推荐

