如何正确测试GenServer中的handle_cast?

时间:2016-12-07 06:52:27

标签: testing erlang elixir otp gen-server

我有一台GenServer,它负责联系外部资源。调用外部资源的结果并不重要,有时可能会出现失败,因此使用handle_cast似乎适用于代码的其他部分。我确实有一个类似于接口的模块用于该外部资源,我正在使用一个GenServer来访问该资源。到目前为止一切都很好。

但是当我尝试为这个gen_server编写测试时,我无法弄清楚如何测试handle_cast。我有GenServer的接口函数,我尝试测试那些,但它们总是返回:ok,除非GenServer没有运行。我无法测试。

我稍微更改了代码。我将handle_cast中的代码抽象为另一个函数,并创建了类似的handle_call回调。然后我可以轻松地测试handle_call,但这有点像黑客。

我想知道人们通常如何测试异步代码。我的方法是正确的还是可接受的?如果没有,那该怎么办?

2 个答案:

答案 0 :(得分:2)

诀窍是要记住GenServer进程按顺序逐个处理消息。这意味着我们可以确保处理收到并处理了一条消息,确保它处理了我们稍后发送的消息。这反过来意味着我们可以通过跟随同步消息(例如一些调用)将任何异步操作更改为同步操作。

测试场景如下所示:

  1. 发出异步请求。
  2. 发出同步请求并等待结果。
  3. 断言异步请求的影响。
  4. 如果服务器没有任何合适的同步功能,您可以考虑使用:sys.get_state/2 - 一个用于调试目的的调用,由所有特殊进程(包括GenServer)正确处理,并且可能是最重要的是,同步。我认为用它进行测试是完全有效的。

    您可以在GenServer documentation

    中的:sys模块中详细了解其他有用的功能

答案 1 :(得分:2)

投射请求的格式为:

Module:handle_cast(Request, State) -> Result

Types:
Request = term()
State = term()
Result = {noreply,NewState} | 
         {noreply,NewState,Timeout} | 
         {noreply,NewState,hibernate} |
         {stop,Reason,NewState}
NewState = term()
Timeout = int()>=0 | infinity 
Reason = term()

所以直接调用单元测试很容易(甚至不需要启动服务器),提供RequestState,并声明返回的Result 。当然它也可能有一些副作用(比如在ets表中编写,修改进程字典......)所以你需要在之前初始化这些资源,并在断言后检查效果。

例如:

test_add() ->
    {noreply,15} = my_counter:handle_cast({add,5},10).