在 Elixir 中测试主管的最佳实践
Best practice for testing supervisors in Elixir
我在谷歌上搜索了很多,但找不到关于这个主题的任何信息 - 要么 Elixir 是一种太年轻的语言,要么我在搜索错误的术语。
我正在学习 Jose Valim 的 Elixir 门户教程 (https://howistart.org/posts/elixir/1) 并正在构建测试以供练习(我已经构建了所有功能)。
本教程的一部分是构建主管以使 Portal.Door 模块容错。
我正在尝试使用以下代码
测试容错能力(例如,如果 Portal.Door 实例未正确关闭,Supervisor 会重新启动它)
defmodule PortalTest do
use ExUnit.Case, async: true
...
test "supervisor restarts doors" do
{:ok, pid} = Portal.shoot(:third)
Process.unlink(pid)
Process.exit(pid, :shutdown)
assert Portal.Door.get(:third) == [] #new doors initialize with an empty array
end
end
但是当我 运行 测试时,我一直收到这个错误:
1) test supervisor restarts doors (PortalTest)
test/portal_test.exs:35
** (exit) exited in: GenServer.call(:third, {:get, #Function<3.47016826/1 in Portal.Door.get/1>}, 5000)
** (EXIT) shutdown
stacktrace:
(elixir) lib/gen_server.ex:356: GenServer.call/3
test/portal_test.exs:39
所以,我想知道是否有更好的方法来执行此操作,或者我的代码很糟糕。
Process.exit/1
发送退出信号但不等待进程停止。从您的错误输出来看,看起来 Portal.Door.get/1
然后失败了,因为 gen_server 进程在收到调用消息之前终止。
要克服这个问题,您需要等待进程关闭,然后再次重新启动。一个简单的补救措施可能是在发出退出信号后通过 :timer.sleep/1
进行短暂的睡眠(比如 100 毫秒)。
更复杂的方法是等待进程终止,然后再次重新启动。第一部分可以通过设置监视器(通过 Process.monitor/1
)轻松完成,并等待相应的 :DOWN
消息。通过这样做,您还可以验证目标进程确实已终止。
然后需要等待进程再次重启,才能发出请求。这可能很棘手,短暂的睡眠可能是最简单的选择。或者,如果该进程在本地别名下注册,您可以使用 Process.whereis/1
进行轮询,直到获得非零值,此时您知道该进程再次运行。
这是一个工作代码示例,主要基于@sasajuric 提供的提示。
defmodule Namer.Worker.Test do
use ExUnit.Case
test "supervisor restarts worker on server crash" do
pid = Process.whereis(Namer.Worker)
ref = Process.monitor(pid)
Process.exit(pid, :kill)
receive do
{:DOWN, ^ref, :process, ^pid, :killed} ->
:timer.sleep 1
assert is_pid(Process.whereis(Namer.Worker))
after
1000 ->
raise :timeout
end
end
end
我在谷歌上搜索了很多,但找不到关于这个主题的任何信息 - 要么 Elixir 是一种太年轻的语言,要么我在搜索错误的术语。
我正在学习 Jose Valim 的 Elixir 门户教程 (https://howistart.org/posts/elixir/1) 并正在构建测试以供练习(我已经构建了所有功能)。
本教程的一部分是构建主管以使 Portal.Door 模块容错。
我正在尝试使用以下代码
测试容错能力(例如,如果 Portal.Door 实例未正确关闭,Supervisor 会重新启动它)defmodule PortalTest do
use ExUnit.Case, async: true
...
test "supervisor restarts doors" do
{:ok, pid} = Portal.shoot(:third)
Process.unlink(pid)
Process.exit(pid, :shutdown)
assert Portal.Door.get(:third) == [] #new doors initialize with an empty array
end
end
但是当我 运行 测试时,我一直收到这个错误:
1) test supervisor restarts doors (PortalTest)
test/portal_test.exs:35
** (exit) exited in: GenServer.call(:third, {:get, #Function<3.47016826/1 in Portal.Door.get/1>}, 5000)
** (EXIT) shutdown
stacktrace:
(elixir) lib/gen_server.ex:356: GenServer.call/3
test/portal_test.exs:39
所以,我想知道是否有更好的方法来执行此操作,或者我的代码很糟糕。
Process.exit/1
发送退出信号但不等待进程停止。从您的错误输出来看,看起来 Portal.Door.get/1
然后失败了,因为 gen_server 进程在收到调用消息之前终止。
要克服这个问题,您需要等待进程关闭,然后再次重新启动。一个简单的补救措施可能是在发出退出信号后通过 :timer.sleep/1
进行短暂的睡眠(比如 100 毫秒)。
更复杂的方法是等待进程终止,然后再次重新启动。第一部分可以通过设置监视器(通过 Process.monitor/1
)轻松完成,并等待相应的 :DOWN
消息。通过这样做,您还可以验证目标进程确实已终止。
然后需要等待进程再次重启,才能发出请求。这可能很棘手,短暂的睡眠可能是最简单的选择。或者,如果该进程在本地别名下注册,您可以使用 Process.whereis/1
进行轮询,直到获得非零值,此时您知道该进程再次运行。
这是一个工作代码示例,主要基于@sasajuric 提供的提示。
defmodule Namer.Worker.Test do
use ExUnit.Case
test "supervisor restarts worker on server crash" do
pid = Process.whereis(Namer.Worker)
ref = Process.monitor(pid)
Process.exit(pid, :kill)
receive do
{:DOWN, ^ref, :process, ^pid, :killed} ->
:timer.sleep 1
assert is_pid(Process.whereis(Namer.Worker))
after
1000 ->
raise :timeout
end
end
end