2015-07-30 19 views
5

Zrobiłem trochę google i nie mogę znaleźć niczego na ten temat - albo Elixir jest zbyt młodym językiem, albo szukam niewłaściwych terminów.Najlepsza praktyka dla testujących nadzorców w Elixir

Pracuję poprzez samouczek Jose Valim'a na temat Elixir Portal (https://howistart.org/posts/elixir/1) i buduję testy do ćwiczeń (mam wbudowaną całą funkcjonalność).

Część samouczka tworzy superwizora, aby moduł PortalDoor był odporny na błędy.

Próbuję przetestować odporność na uszkodzenia (np że Inspektor restartuje instancji Portal.Door jeśli jest niewłaściwie shutdown) za pomocą następującego kodu

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 

ale wciąż otrzymuję ten błąd podczas uruchamiania test:

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 

Tak, zastanawiam się, czy jest lepszy sposób to zrobić lub mój kod jest po prostu zły.

Odpowiedz

1

Process.exit/1 wysyła sygnał wyjścia, ale nie czeka na zatrzymanie procesu. Sądząc po wynikach błędu, wygląda na to, że nie powiodło się, ponieważ proces gen_server zakończył się przed odebraniem komunikatu połączenia.

Aby przezwyciężyć ten problem, należy poczekać na zamknięcie procesu i ponownie go uruchomić. Prostym lekarstwem może być krótki sen (powiedzmy 100 ms) przez :timer.sleep/1 po wysłaniu sygnału wyjścia.

Bardziej zaangażowanym podejściem jest oczekiwanie na zakończenie procesu, a następnie ponowne uruchomienie. Pierwszą część można łatwo zrobić, ustawiając monitor (przez Process.monitor/1) i czekać na odpowiedni komunikat :DOWN. W ten sposób sprawdzasz również, czy proces docelowy rzeczywiście się zakończył.

Następnie należy poczekać, aż proces zostanie ponownie uruchomiony, aby można było wysłać żądanie. Może to być trudne, a spanie przez krótki czas jest prawdopodobnie najłatwiejszą opcją. Alternatywnie, jeśli proces jest zarejestrowany w lokalnym aliasie, możesz sondować z Process.whereis/1, aż uzyskasz niezerową wartość, po czym wiesz, że proces jest ponownie uruchamiany.

+0

Właściwie odbieram moje oryginalne oświadczenie. Wydaje się, że kiedy używam IO.inspect do debugowania, powoduje to opóźnienie, które wystarcza do ponownego uruchomienia procesu. Bez tego użycie rekursji do oczekiwania na Process.whereis/1 na zwracanie czegoś innego niż wartość zerowa nie działa (prawdopodobnie moja wina). Tak, idę z: timer.sleep/1 na teraz. – user2577226

0

Oto działający przykład kodu, oparty w dużej mierze na dostarczonych poradach @asasajuric.

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 
Powiązane problemy