$30 off During Our Annual Pro Sale. View Details »

Testing Asynchronous OTP

Testing Asynchronous OTP

Andrea Leopardi

September 09, 2021
Tweet

More Decks by Andrea Leopardi

Other Decks in Programming

Transcript

  1. View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. View Slide

  22. defmodule RollingAverage do


    use GenServer


    def start_link(limit), do: # ...


    def add(pid, n), do: # ...



    def average(pid), do: # ...


    end

    View Slide

  23. View Slide

  24. View Slide

  25. test "add/2" do


    {:ok, pid} = RollingAverage.start_link()


    RollingAverage.add(pid, 3)


    # Now what?


    end

    View Slide

  26. View Slide

  27. test "add/2 and average/1" do


    {:ok, pid} = RollingAverage.start_link(2)


    RollingAverage.add(pid, 3)


    RollingAverage.add(pid, 4)


    assert RollingAverage.average(pid) == 3.5


    RollingAverage.add(pid, 5)


    assert RollingAverage.average(pid) == 4.5


    end

    View Slide

  28. View Slide

  29. defmodule RollingList do


    def new(limit), do: {limit, []}


    def add({limit, list}, n), do: # ...




    def average({_limit, list}), do: # ...


    end

    View Slide

  30. test "add/2" do


    rolling_list = RollingList.new(2)


    assert RollingList.add(rolling_list, 3) ==


    {_limit = 2, [3]}


    end

    View Slide

  31. View Slide

  32. test "add/2" do


    {:ok, pid} = RollingAverage.start_link(2)


    RollingAverage.add(pid, 3)


    state = :sys.get_state(pid)


    assert state == {_limit = 2, [3]}


    end

    View Slide

  33. View Slide

  34. View Slide

  35. test "add/2 and average/1" do


    {:ok, pid} =


    start_supervised(


    {RollingAverage, _limit = 2}


    )


    # Same as before


    end

    View Slide

  36. View Slide

  37. def background_work do


    Task.start(fn ->


    do_something()


    end)


    end

    View Slide

  38. View Slide

  39. View Slide

  40. def background_work(parent, ref) do


    Task.start(fn ->


    do_something()


    send(parent, {ref, :done})


    end)


    end

    View Slide

  41. test "background_work/0" do


    ref = make_ref()


    background_work(self(), ref)


    assert_receive {^ref, :done}


    end

    View Slide

  42. View Slide

  43. def background_work do


    Task.start(fn ->


    :ok = twitter_module.post()


    end)


    end

    View Slide

  44. test "background_work/0" do


    parent = self()


    ref = make_ref()


    Mox.expect(TwitterMock, :post, fn ->


    send(parent, ref)


    :ok


    end)


    background_work()


    assert_receive ^ref


    end

    View Slide

  45. View Slide

  46. defmodule WeatherChecker do


    use GenServer


    def init(_) do


    :timer.send_interval(5000, self(), :tick)


    end


    def handle_info(:tick, _) do


    check_weather()


    end


    end

    View Slide

  47. test "checking weather periodically" do


    {:ok, _pid} = WeatherChecker.start_link()


    Process.sleep(5000 * 1.5)


    assert weather_checked?()


    end

    View Slide

  48. View Slide

  49. defmodule WeatherChecker do


    use GenServer


    def init(_) do


    :timer.send_interval(5000, self(), :tick)


    end


    def tick(pid), do: send(pid, :tick)


    def handle_info(:tick, _) do


    check_weather()


    end


    end

    View Slide

  50. test "checking weather periodically" do


    {:ok, pid} = WeatherChecker.start_link()


    WeatherChecker.tick(pid)


    assert weather_checked?()


    end

    View Slide

  51. defmodule WeatherChecker do


    use GenServer


    def init(:interval) do


    :timer.send_interval(5000, self(), :tick)


    end


    def init(:manual) do


    # No send_interval


    end


    end

    View Slide

  52. View Slide

  53. @mix_env Mix.env()


    def start(_type, _args) do


    Supervisor.start_link(children(@mix_env), ...)


    end


    defp children(:test), do: [...]


    defp children(_env), do: [WeatherChecker | ...]

    View Slide

  54. View Slide

  55. View Slide

  56. View Slide

  57. View Slide

  58. test "cleanup after crashes" do


    Process.flag(:trap_exit, true)


    {:ok, pid} = start_supervised(MyServer)


    Process.exit(pid, :kill)


    assert cleanup_successful?()


    end

    View Slide

  59. View Slide

  60. View Slide

  61. View Slide

  62. View Slide

  63. View Slide

  64. View Slide

  65. View Slide

  66. View Slide

  67. View Slide

  68. View Slide