【问题标题】:Testing asynchronous code in Elixir在 Elixir 中测试异步代码
【发布时间】:2023-03-19 05:49:01
【问题描述】:

我想测试一个正在使用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

以及我已经编写的测试(仅使用睡眠调用)

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

【问题讨论】:

  • 您能否向我们展示一个最小的失败示例来说明您想要进行的特定类型的断言?
  • 示例代码将极大地帮助您提供一个好的答案。
  • 好的,刚刚添加了代码示例
  • 对我的代码的任何其他评论都可以:)(尤其是模拟部分)
  • 如果不打算使用任务的结果,就不要使用Task.async/1,可以直接使用Task.start_link/1。

标签: elixir


【解决方案1】:

由于问题有点模糊,我将在这里给出一般性的答案。通常的技术是监视进程并等待停机消息。像这样的:

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

一些重要的事情:

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

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

【讨论】:

  • 谢谢何塞!刚刚在我的问题中添加了一些代码示例。我想我需要让我的搜索功能返回一个包含 Task pid 的元组? (我不喜欢仅出于测试目的更改我的代码:/)
  • 让我支持这一点。您的测试是您的代码的消费者,就像您的应用程序的任何其他部分一样。确保您为测试提供正确的结果通常是一个很好的指标,您的应用程序的其他部分也将正确使用该代码。例如,查看测试,我不知道返回结果是什么。如果我达到 API 限制会怎样?如果我不这样做会怎样?看起来这个函数是关于副作用的,返回一个任务会表明:嘿,我稍后会完成,如果你在乎的话,看这个任务。
【解决方案2】:

当我无法使用 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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-25
    • 1970-01-01
    • 1970-01-01
    • 2015-08-19
    • 2014-09-29
    • 2012-01-18
    • 2018-04-30
    • 2021-07-04
    相关资源
    最近更新 更多