Hound ExUnit中assert_raise拖慢Phoenix测试?求原因及优化方案
assert_raise断言这么慢?以及更好的测试方式 先说说慢的原因
你遇到的6秒耗时,大概率是ExUnit的assert_raise默认超时机制在搞鬼。ExUnit的assert_raise默认会等5秒(没错,就是那个timeout参数,默认5000ms),直到捕获到指定异常,或者超时才会结束。
如果你的测试场景是在HTTP请求流程里用它(比如提交表单后等后端抛出异常),那这个等待过程就会拉满——因为后端处理请求、数据库做约束检查这些步骤,哪怕只花1秒,剩下的4秒assert_raise都会傻等,直到确认异常真的抛出,最后总耗时就接近6秒了。
另外还有个问题:如果这是端到端的feature测试(比如用Wallaby模拟浏览器操作),那用assert_raise完全是用错了工具。这类测试应该关注用户能看到的页面变化,而不是底层代码抛出的异常。
更优的测试方案:直接验证页面/请求状态
你的核心需求是测试“页面元素不再显示”(或者说,无效凭据提交后,注册流程没走完,页面保持在表单状态),完全不需要用assert_raise,直接测试页面的实际表现就行,速度快还更贴合真实用户场景:
1. 控制器测试(针对后端逻辑)
如果是在控制器测试里,直接检查请求的返回结果和数据库状态:
test "拒绝重复邮箱的注册请求" do # 先创建一个已有用户 existing_user = insert(:user) # 构造重复邮箱的注册参数 params = %{"user" => %{"email" => existing_user.email, "password" => "test1234"}} # 发送注册请求 conn = post(conn, Routes.user_path(conn, :create), params) # 验证请求重定向回注册页面 assert redirected_to(conn) == Routes.user_path(conn, :new) # 验证页面显示错误提示 assert get_flash(conn, :error) == "邮箱已被使用" # 验证数据库没有新增用户 assert Repo.all(User) |> length() == 1 end
2. Feature测试(模拟用户操作)
如果是用Wallaby这类工具做端到端测试,模拟真实用户操作后验证页面状态:
test "重复邮箱注册时显示错误并停留在注册页" do # 先创建一个已有用户 insert(:user, email: "already@taken.com") # 访问注册页面 visit(socket, Routes.user_path(socket, :new)) # 填写表单并提交 fill_in(socket, "邮箱", with: "already@taken.com") fill_in(socket, "密码", with: "password123") click(socket, "注册") # 验证还在注册页(比如标题不变) assert has_title?(socket, "用户注册") # 验证错误提示出现 assert has_text?(socket, "邮箱已被使用") # 验证注册按钮/表单元素还存在(也就是你要测的“元素没有消失”) assert has_button?(socket, "注册") end
3. 什么时候该用assert_raise?
只有当你需要验证底层函数抛出特定异常时才用它,比如在模型层测试Ecto的约束:
test "重复邮箱插入时抛出唯一约束异常" do existing_user = insert(:user) invalid_user = %User{email: existing_user.email, password: "test123"} assert_raise Ecto.ConstraintError, fn -> Repo.insert!(invalid_user) end end
这种测试不涉及HTTP请求,直接操作数据层,速度会非常快,不会有超时等待的问题。
总结
你之前的测试慢,是因为用错了工具——用assert_raise(用来捕获底层异常的工具)去测试页面行为,导致它一直在等超时阈值。换成直接验证页面状态或请求结果的方式,既能更准确地反映真实用户体验,测试速度也会回到正常水平(和你之前的0.7秒量级差不多)。
内容的提问来源于stack exchange,提问作者David B.

