如何在 GenServer 中正确测试 handle_cast?

How to test handle_cast in a GenServer properly?

我有一个GenServer,负责联系外部资源。调用外部资源的结果并不重要,偶尔失败是可以接受的,所以使用 handle_cast 似乎适用于代码的其他部分。我确实有一个用于该外部资源的类似接口的模块,并且我正在使用一个 GenServer 来访问该资源。到目前为止一切顺利。

但是当我尝试为此 gen_server 编写测试时,我不知道如何测试 handle_cast。我有 GenServer 的接口函数,我试图测试那些,但它们总是 return :ok,除非 GenServer 不是 运行。我无法测试。

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

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

诀窍是记住 GenServer 进程按顺序一条一条地处理消息。这意味着我们可以确保进程接收并处理了消息,方法是确保它处理了我们稍后发送的消息。这反过来意味着我们可以将任何异步操作更改为同步操作,方法是在它后面加上同步消息,例如一些调用。

测试场景如下所示:

  1. 发出异步请求。
  2. 发出同步请求,等待结果
  3. 断言异步请求的效果。

如果服务器没有任何合适的同步功能,您可以考虑使用 :sys.get_state/2 - 一个用于调试目的的调用,由所有特殊进程(包括 GenServer)正确处理,并且是,什么可能是最重要的事情,同步。我认为将它用于测试是完全有效的。

您可以从 GenServer documentation on debugging 中的 :sys 模块中阅读更多关于其他有用功能的信息。

投射请求的格式为:

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()

因此执行单元测试非常容易,只需直接调用它(甚至不需要启动服务器),提供一个 Request 和一个 State,并断言返回的 [=14] =].当然它也可能有一些副作用(比如写一个etstable,修改process dictionary...)所以你需要先初始化那些资源,assert后检查效果。

例如:

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