Я пишу модуль для запроса онлайн-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
с определенным именем по нескольким причинам:
-
маловероятно, что кому-то понадобится несколько экземпляров
-
Я могу определить публичный API в модуле
Weather
, который абстрагирует существование базовогоGenServer
. Пользователям не нужно предоставлять PID/Name функцииweather_in
для связи с базовымGenServer
-
Я могу разместить
GenServer
под деревом надзора
Когда я запускаю тесты, так как они запускаются одновременно, обратный вызов setup
выполняется один раз для каждого теста. Поэтому есть параллельные попытки запустить мой сервер, и он терпит неудачу с {:error, {:already_started, #PID<0.133.0>}}
.
Я спросил Slack, есть ли что-нибудь, что я могу с этим поделать. Возможно, есть идиоматическое решение, о котором я не знаю...
Чтобы обобщить обсуждаемые решения, при реализации и тестировании GenServer
у меня есть следующие параметры:
-
Не регистрировать сервер с определенным именем, чтобы каждый тест запускал свой собственный экземпляр GenServer. Пользователи сервера могут запускать его вручную, но они должны предоставить его публичному API модуля. Сервер также может быть помещен в дерево контроля, даже с именем, но общедоступный API модуля все равно должен знать, с каким PID разговаривать. Учитывая имя, переданное как параметр, я предполагаю, что они могут найти связанный PID (я полагаю, OTP может это сделать.)
-
Регистрация сервера с определенным именем (как в моих примерах). Теперь может быть только один экземпляр GenServer, тесты должны выполняться последовательно (
async: false
), и каждый тест должен начинаться с и. -
Регистрация сервера с определенным именем. Тесты могут выполняться одновременно, если все они выполняются с одним и тем же уникальным экземпляром сервера (с помощью
setup_all
экземпляр можно запустить только один раз для всего тестового примера). Тем не менее, imho это неправильный подход к тестированию, поскольку все тесты будут выполняться против одного и того же сервера, изменяя его состояние и, следовательно, возиться друг с другом.
Учитывая, что пользователям не нужно создавать несколько экземпляров этого GenServer, я испытываю желание обменять тесты concurrency для простоты и пойти с решением 2.
[Изменить]
Попробуйте решение 2, но оно по-прежнему не работает по той же причине :already_started
. Я снова прочитал документы о async: false
и выяснил, что он предотвращает запуск тестового случая параллельно с другими тестовыми примерами. Он не запускает тесты моего тестового примера последовательно, как я думал.
Помогите!