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

时间:2008-12-20 11:24:36

标签: unit-testing concurrency erlang parallel-processing

我在Erlang上花了一些时间,我想将TDD应用于我正在写的代码。

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

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

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

6 个答案:

答案 0 :(得分:9)

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

另外值得一提的是 Dialyzer (针对ERlang的DIscrepancy AnaLYZer)(PapersTutorialManual),这是一个静态分析工具查找错误的代码。这也支持检测一些并发错误(参见paper)。

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

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

答案 1 :(得分: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]).

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

deep_sum_test() ->
     ?assertEqual(0,  deep_sum([0,  0,  0,  0])),
     ?assertEqual(40, deep_sum([10, 10, 10, 10]).

现在,假设我们对此功能有一些更明确的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.

在测试时,我们必须处理伪造此流程池:

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

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

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

答案 2 :(得分:2)

我所知道的一个可用于在并发场景下测试Erlang程序的工具是QuickCheck:http://www.quviq.com/

您可以使用属性指定预期行为,并且该工具可以在一系列输入和时间上运行您的Erlang程序,以确保满足属性。

不幸的是,这是一个商业工具

另请参阅ProTest项目:http://www.protest-project.eu

更新:ProTest项目发布了一项调查"Results for: Erlang testing tools survey for the ProTest project"

答案 3 :(得分:1)

因为并发代码中的错误会根据各个部分的执行顺序显示出来,所以我认为最好不要依赖测试来发现代码并发部分中的错误。这种虫子很容易漏过,很难找到。例如,请参阅双锁技术,它被广泛引用并用作在多线程环境中实现延迟初始化的有效方法was later found to be broken。最好使用适当的抽象和技术,使代码的并发部分“按设计”纠正。

答案 4 :(得分:0)

除了Diomidis所说的,对你的并发代码获得一些信心的唯一真正方法是在不断变化的条件下运行扩展测试;即便如此,它只是证明它在这些条件下没有失败

答案 5 :(得分:-1)

您可以使用以下功能隔离出您要使用模拟测试的部分:http://github.com/charpi/erl_mock/tree/master

我没有使用erl_mock的经验。