单元测试并发Erlang代码的最佳方法是什么?

mad*_*lep 17 parallel-processing concurrency erlang unit-testing

我在Erlang上花了一些时间,我想将TDD应用到我正在编写的代码中.

虽然标准库中的EUnit为测试常规样式代码提供了一个很好的传统单元测试框架,但似乎没有什么可以帮助测试并发代码,这在Erlang中使用了很多.

请注意,我们在这里讨论的是Erlang,它使用消息传递(而不是共享状态)来进行并发进程之间的通信,因此使用共享状态语言对并发代码进行单元测试的技术可能不适用.

有人找到了在Erlang中测试并发代码的好方法吗?

Hju*_*lle 9

我刚刚发现了一些很酷的(截至2011年)开发的软件,用于测试名为Concuerror的并发Erlang应用程序.有一些 论文github上的存储库.显然它通过使用自己的调度程序并系统地测试进程之间的不同交错来工作.

另外值得一提的是透析器(差异分析仪二郎)(论文,教程,手册),这是寻找错误代码的静态分析的工具.这也支持检测一些并发错误(参见文章).

虽然透析器似乎是比较成熟的软件,但我自己还没有测试过这些.两个程序都有一个用于处理测试的GUI.

PS.对于非并发部分,EUnit和QuickCheck(也有免费版本)应该可以正常工作.


Ada*_*erg 5

问题有点模糊("Erlang是并发的,用Erlang测试它!")但我会尝试详细说明一下.

测试Erlang代码的范围可以从简单直接(正确的输入产生正确的输出)到设置复杂的测试工具,以验证组件的行为方式.什么是最适合您的具体情况完全取决于您的要求和您想要做的黑盒子/白盒测试的数量.

Erlang的部分优点是能够使并发透明.请考虑以下示例(并行化列表列表总和的函数):

deep_sum(ListOfLists) ->
     Parent = self(),
     [spawn(fun() -> Parent ! lists:sum(List) end) || List <- ListOfLists],
     lists:sum([receive Sum -> Sum end || _ <- ListOfLists]).
Run Code Online (Sandbox Code Playgroud)

您通常会使用一个非常简单的EUnit测试用例来测试它:

deep_sum_test() ->
     ?assertEqual(0,  deep_sum([0,  0,  0,  0])),
     ?assertEqual(40, deep_sum([10, 10, 10, 10]).
Run Code Online (Sandbox Code Playgroud)

现在,假设我们对此功能有一些更明确的API:作为参数的进程池:

deep_sum(Pool, ListOfLists) ->
     distribute_lists(Pool, ListOfLists),
     lists:sum([receive Sum -> Sum end || _ <- ListOfLists]).

distribute_lists(Pool, ListOfLists) -> distribute_lists(Pool, Pool, ListOfLists).

distribute_lists([P|Pool], All, [L|ListOfLists]) ->
     P ! {self(), L},
     distribute_lists(Pool, All, ListOfLists);
distribute_lists([], All, ListOfLists) ->
     distribute_lists(All, All, ListOfLists);
distribute_lists(_Pool, _All, []) ->
     ok.
Run Code Online (Sandbox Code Playgroud)

在测试时,我们必须处理伪造这个进程池:

deep_sum_test() ->
     Pool = [spawn_link(fun() -> fake_pool(1) end) || _ <- lists:seq(1, 3)],
     ?assertEqual(4, deep_sum(Pool, [lists:seq(1, 3) || _ <- list:seq(1, 4)]),
     ?assertEqual(7, deep_sum(Pool, [lists:seq(1, 3) || _ <- list:seq(1, 7)]),
     [P ! stop || P <- Pool].

fake_pool(CannedResponse) ->
     receive
         {From, _L} -> From ! CannedResponse;
         stop -> ok
     end,
     fake_pool(CannedResponse).
Run Code Online (Sandbox Code Playgroud)

如您所见,在Erlang中测试并发程序可以采用不同的形式.这些是非常简单的示例,但是使用Erlang内置的并发原语,可以非常轻松地创建所需的测试工具,并在适当的级别进行抽象.

我通常发现TDD与您正在测试并发代码是正交的,因此所述测试技术也可以用于正常的单元测试.


Dio*_*lis 1

由于并发代码中的错误会根据各个部分的执行顺序而显现出来,因此我认为最好不要依赖测试来发现代码并发部分中的错误。此类错误很容易被忽视,但极难定位。例如,双锁技术曾被广泛引用并用作在多线程环境中实现延迟初始化的有效方法,但后来发现它被破坏了。最好使用适当的抽象和技术来使代码的并发部分“按设计”正确。