Quelle est la stratégie idiomatique de test pour GenServers dans Elixir?

j'écris un module pour interroger une API météo en ligne. J'ai décidé de le mettre en œuvre comme une Application avec un supervisé GenServer.

Voici le code:

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

Dans mon test, j'ai décidé d'utiliser un setup rappel pour démarrer le serveur:

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

j'ai décidé d'enregistrer le GenServer avec un nom spécifique pour plusieurs raisons:

  • il est peu probable que quelqu'un aurait besoin de plusieurs les instances

  • je peux définir une API publique dans mon Weather module qui résume l'existence d'un GenServer. Les utilisateurs n'auront pas à fournir un PID/nom à weather_in fonction pour communiquer avec le sous-jacent GenServer

  • je peux placer ma GenServer sous un arbre de supervision

quand je lance les tests, comme ils courent en même temps, le setup rappel est exécuté une fois par test. Donc il y a tentatives simultanées de démarrer mon serveur et il échoue avec {:error, {:already_started, #PID<0.133.0>}}.

j'ai demandé à Slack si je pouvais faire quelque chose. Peut-être y a-t-il une solution idiomatique dont je ne suis pas au courant...

pour résumer les solutions discutées, lors de la mise en oeuvre et de l'essai d'un GenServer, j'ai les options suivantes:

  1. ne pas enregistrer le serveur avec un nom spécifique pour permettre à chaque test de démarrer sa propre instance du serveur gens. Les utilisateurs de la le serveur peut le démarrer manuellement, mais il doit le fournir à l'API publique du module. Le serveur peut également être placé dans une arborescence de supervision, même avec un nom, mais l'API publique du module devra tout de même savoir à quel PID s'adresser. Etant donné un nom passé comme paramètre, je suppose qu'ils pourraient trouver le PID associé (je suppose QU'OTP peut faire cela.)

  2. Enregistrer le serveur avec un nom spécifique (comme je l'ai fait dans mes échantillons). Maintenant il ne peut y avoir qu'une seule instance GenServer, les tests doivent être exécutés de façon séquentielle (async: false) et chaque test doit commencer et terminer le serveur.

  3. Enregistrer le serveur avec un nom spécifique. Les Tests peuvent être exécutés simultanément s'ils sont tous exécutés contre la même instance unique du serveur (en utilisant setup_all, une instance ne peut être lancée qu'une seule fois pour l'ensemble du cas de test). Pourtant, imho c'est une mauvaise approche pour tester car tous les tests vont s'exécuter sur le même serveur, changeant son état et donc jouer avec chaque autre.

considérant que les utilisateurs peuvent ne pas avoir besoin de créer plusieurs instances de ce GenServer, je suis tenté d'échanger la concurrence des tests pour la simplicité et d'aller avec la solution 2.

[Edit] Essayer la solution 2 mais elle échoue toujours pour la même raison :already_started. J'ai lu encore les docs sur async: false et découvert qu'il empêche l' cas de test de fonctionner en parallèle avec d'autres cas d'essai. Il ne fonctionne pas les tests de mon cas de test en séquence comme je le pensais. À l'aide!

18
demandé sur svarlet 2015-10-08 17:40:50

4 réponses

un problème crucial que je note est que vous avez la mauvaise signature pour