在Elixir中测试异步代码

Chr*_*ris 20 elixir

我想测试一个正在使用的函数 Task.async

为了使我的测试通过,我需要让它在断言前保持100ms睡眠,否则测试过程在异步任务执行之前被杀死.

有没有更好的办法?

编辑,添加代码示例:

我想测试的代码(大致):

def search(params) do
  RateLimiter.rate_limit(fn ->
    parsed_params = ExTwitter.Parser.parse_request_params(params)
    json = ExTwitter.API.Base.request(:get, "1.1/search/tweets.json", parsed_params)
    Task.async(fn -> process_search_output(json) end)
    new_max_id(json)
  end)
end
Run Code Online (Sandbox Code Playgroud)

我已经写过的测试(仅用于调用睡眠)

test "processes and store tweets" do
  with_mock ExTwitter.API.Base, [request: fn(_,_,_) -> json_fixture end] do
    with_mock TwitterRateLimiter, [rate_limit: fn(fun) -> fun.() end] do
      TSearch.search([q: "my query"])
      :timer.sleep(100)
      # assertions 
      assert called TStore.store("some tweet from my fixtures")
      assert called TStore.store("another one")
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

Jos*_*lim 36

既然问题有点模糊,我会在这里给出一般答案.通常的技术是监视进程并等待关闭消息.像这样的东西:

task = Task.async(fn -> "foo" end)
ref  = Process.monitor(task.pid)
assert_receive {:DOWN, ^ref, :process, _, :normal}, 500
Run Code Online (Sandbox Code Playgroud)

一些重要的事情:

  • 元组的第五个元素是退出原因.我断言任务退出是:normal.如果您期望再次退出,请相应地更改.

  • 第二个值assert_receive是超时.鉴于您目前有100毫秒的睡眠时间,500毫秒听起来像是一个合理的数量.

  • 让我赞成这一点.您的测试是您的代码的消费者,与您的应用程序的任何其他部分一样.确保您为测试提供正确的结果通常是一个很好的指标,您的应用程序的其他部分也将正确使用该代码.例如,查看测试,我不知道返回结果是什么.如果我达到API限制会怎样?如果我不这样做会怎么样?似乎这个功能是关于副作用的,并且返回任务会表明:嘿,我将在稍后完成,如果你关心它,请观察这个任务. (9认同)

Chr*_*ris 9

当我无法使用José的方法时assert_receive,我会使用一个小助手重复执行断言/睡眠,直到断言通过或最后超时.

这是辅助模块

defmodule TimeHelper do

  def wait_until(fun), do: wait_until(500, fun)

  def wait_until(0, fun), do: fun.()

  def wait_until(timeout, fun) defo
    try do
      fun.()
    rescue
      ExUnit.AssertionError ->
        :timer.sleep(10)
        wait_until(max(0, timeout - 10), fun)
    end
  end

end
Run Code Online (Sandbox Code Playgroud)

在前面的例子中它可以像这样使用:

TSearch.search([q: "my query"])
wait_until fn ->
  assert called TStore.store("some tweet from my fixtures")
  assert called TStore.store("another one")
end
Run Code Online (Sandbox Code Playgroud)