【发布时间】:2016-01-06 06:17:43
【问题描述】:
我正在编写一个模块来查询在线天气 API。我决定将它作为一个应用程序来实现,并带有受监督的GenServer。
代码如下:
defmodule Weather do
use GenServer
def start_link() do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
def weather_in(city, country) do
GenServer.call(__MODULE__, {:weather_in, city, country_code})
end
def handle_call({:weather_in, city, country}) do
# response = call remote api
{:reply, response, nil}
end
end
在我的测试中,我决定使用setup 回调来启动服务器:
defmodule WeatherTest do
use ExUnit.Case
setup do
{:ok, genserver_pid} = Weather.start_link
{:ok, process: genserver_pid}
end
test "something" do
# assert something using Weather.weather_in
end
test "something else" do
# assert something else using Weather.weather_in
end
end
出于以下几个原因,我决定使用特定名称注册GenServer:
不太可能有人需要多个实例
我可以在我的
Weather模块中定义一个公共 API,它抽象出底层GenServer的存在。用户无需向weather_in函数提供PID/名称即可与底层GenServer进行通信我可以将我的
GenServer放在监督树下
当我运行测试时,因为它们同时运行,setup 回调在每个测试中执行一次。因此,有并发尝试启动我的服务器,但它以{:error, {:already_started, #PID<0.133.0>}} 失败。
我在 Slack 上询问是否有什么我可以做的。也许有一个我不知道的惯用解决方案......
总结讨论的解决方案,在实现和测试GenServer 时,我有以下选择:
未使用特定名称注册服务器以让每个测试启动其自己的 GenServer 实例。 服务器的用户可以手动启动它,但他们必须将它提供给模块的公共 API。服务器也可以放置在监督树中,即使有名称,但模块的公共 API 仍然需要知道要与哪个 PID 通信。给定一个作为参数传递的名称,我猜他们可以找到关联的 PID(我想 OTP 可以做到这一点。)
使用特定名称注册服务器(就像我在示例中所做的那样)。现在只能有一个 GenServer 实例,测试必须按顺序运行 (
async: false),并且每个测试必须启动并终止服务器。使用特定名称注册服务器。如果测试都针对同一个唯一的服务器实例运行,则它们可以同时运行(使用
setup_all,对于整个测试用例,一个实例只能启动一次)。然而,恕我直言,这是一种错误的测试方法,因为所有测试都将针对同一台服务器运行,从而改变其状态并因此相互混淆。
考虑到用户可能不需要创建这个 GenServer 的多个实例,我很想为了简单而放弃测试并发并采用解决方案 2。
[编辑]
尝试解决方案 2,但由于相同原因 :already_started 仍然失败。我再次阅读了有关 async: false 的文档,发现它会阻止 test case 与其他测试用例并行运行。它没有像我想的那样按顺序运行我的测试用例的测试。
救命!
【问题讨论】:
标签: elixir