6

我目前正在为一个模块编写一个测试,该模块在一个以spawn_link(?MODULE, init, [self()]).

在我的 eunit 测试中,我定义了一个 setup 和 teardown 函数以及一组测试生成器。

all_tests_test_() ->
    {inorder, {
        foreach,
        fun setup/0,
        fun teardown/1,
        [
            fun my_test/1
        ]}
    }.

设置乐趣创建了被测过程:

setup() ->
    {ok, Pid} = protocol:start_link(),
    process_flag(trap_exit,true),
    error_logger:info_msg("[~p] Setting up process ~p~n", [self(), Pid]),
    Pid.

测试看起来像这样:

my_test(Pid) ->
    [ fun() ->
            error_logger:info_msg("[~p] Sending to ~p~n", [self(), Pid]),
            Pid ! something,
            receive
                Msg -> ?assertMatch(expected_result, Msg)
            after
                500 -> ?assert(false)
            end
        end ].

我的大多数模块都是 gen_server 但为此我认为没有所有 gen_server 样板代码会更容易......

测试的输出如下所示:

=INFO REPORT==== 31-Mar-2014::21:20:12 ===
[<0.117.0>] Setting up process <0.122.0>

=INFO REPORT==== 31-Mar-2014::21:20:12 ===
[<0.124.0>] Sending to <0.122.0>

=INFO REPORT==== 31-Mar-2014::21:20:12 ===
[<0.122.0>] Sending expected_result to <0.117.0>
protocol_test: my_test...*failed*
in function protocol_test:'-my_test/1-fun-0-'/0 (test/protocol_test.erl, line 37)
**error:{assertion_failed,[{module,protocol_test},
                   {line,37},
                   {expression,"false"},
                   {expected,true},
                   {value,false}]}

从 Pids 中,您可以看到运行 setup (117) 的任何进程与运行测试用例 (124) 的进程不同。然而,被测试的过程是相同的(122)。这会导致测试用例失败,因为接收端永远不会收到消息并且会超时。

这是 eunit 生成一个新进程来运行测试用例的预期行为吗?

一般来说,是否有更好的方法来测试进程或其他异步行为(如强制转换)?或者你会建议总是使用 gen_server 来拥有一个同步接口?

谢谢!

[编辑]

澄清一下,协议是如何知道这个过程的,这很start_link/0有趣:

start_link() ->
    Pid = spawn_link(?MODULE, init, [self()]),
    {ok, Pid}.

该协议与调用者紧密相连。如果他们中的任何一个崩溃,我希望另一个也死。我知道我可以使用 gen_server 和 supervisors 并且实际上它在应用程序的某些部分中做到了,但是对于这个模块,我认为它有点过头了。

4

3 回答 3

4

你试过了吗:

all_tests_test_() ->
    {inorder, {
        foreach,
        local,
        fun setup/0,
        fun teardown/1,
        [
            fun my_test/1
        ]}
    }.

从文档来看,这似乎是您所需要的。

于 2014-04-01T13:05:16.277 回答
4

简单的解决方案

就像在 Pascal 的回答中一样,在local测试描述中添加标志可能会解决您的一些问题,但它可能会在将来给您带来一些额外的问题,尤其是当您将自己链接到创建的进程时。

测试过程

Erlang 的一般做法是,虽然进程抽象对于编写(设计和思考)程序至关重要,但它不是你会暴露给代码用户(即使是你)的东西。而不是期望有人向您发送带有正确数据的消息,而是将其包装在函数调用中

get_me_some_expected_result(Pid) ->
    Pid ! something,
    receive
         Msg -> 
              Msg
    after 500
         timeouted             
    end

然后测试这个功能,而不是“手动”接收一些东西。

为了区分真正的超时和接收到的timeouted原子,可以使用一些模式匹配,并让它在错误的情况下失败

get_me_some_expected_result(Pid) ->
    Pid ! something,
    receive
         Msg -> 
              {ok, Msg}
    after 500
         timeouted             
    end


in_my_test() ->
    {ok, ValueToBeTested} = get_me_some_expected_result().

此外,由于您的进程可能同时收到许多不同的消息,因此您可以确保收到您认为收到的内容,而几乎没有模式匹配和本地参考

get_me_some_expected_result(Pid) ->
    Ref = make_ref(),
    Pid ! {something, Ref},
    receive
         {Ref, Msg} -> 
              {ok, Msg}
    after 500
         timeouted             
    end

现在接收将忽略(留给信)所有与Reg您发送到进程不同的消息。

主要问题

我不太明白的一件事是,您正在测试的进程如何知道将收到的消息发回哪里?唯一合乎逻辑的解决方案是在初始化期间获取其创建者的 pid(调用self/0内部protocol:start_link/0函数)。但是我们的新进程只能与它的创建者通信,这可能不是您所期望的,也不是测试运行的方式。

所以最简单的解决方案是在每次通话时发送“返回地址”;这又可以在我们的包装函数中完成。

get_me_some_expected_result(Pid) ->
    Ref = make_ref(),
    Pid ! {something, Ref, self()},
    receive
         {Ref, Msg} -> 
              {ok, Msg}
    after 500
         timeouted             
    end

同样,任何将使用此get_me_some_expected_result/1功能的人都不必担心消息传递,并且测试此类功能会使事情变得非常容易。

希望这至少有一点帮助。

于 2014-04-01T18:16:27.990 回答
0

也许这仅仅是因为您使用foreachEUnit固定装置代替了固定装置setup

setup那里,试试夹具:使用{setup, Setup, Cleanup, Tests}而不是{inorder, {foreach, …}}

于 2014-03-31T23:15:05.367 回答