17

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

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

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

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

4

6 回答 6

9

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

另外值得一提的是Dialyzer (DIScrepancy AnaLYZer for ERlang) ( Papers , Tutorial , Manual ) ,它是一个对代码进行静态分析以查找错误的工具。这也支持检测一些并发错误(参见论文)。

我自己没有测试过这些,尽管透析器似乎是相对成熟的软件。这两个程序都有一个用于测试的 GUI。

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

于 2011-10-10T13:38:59.190 回答
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 与您是否正在测试并发代码是正交的,因此所述测试技术也可以用于正常的单元测试。

于 2009-01-16T12:26:18.920 回答
2

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

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

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

还可以查看 ProTest 项目: http: //www.protest-project.eu

更新:ProTest 项目发布了一项调查,“结果:ProTest 项目的 Erlang 测试工具调查”

于 2009-01-07T15:42:05.110 回答
1

因为并发代码中的错误取决于各个部分的执行顺序,所以我认为最好不要依赖测试来发现代码并发部分中的错误。这样的错误很容易溜走,而且极难定位。例如,参见双锁技术,它被广泛引用并用作在多线程环境中实现延迟初始化的有效方法,后来发现它被破坏了。最好使用适当的抽象和技术来使代码的并发部分“按设计”正确。

于 2008-12-20T11:42:55.987 回答
0

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

于 2008-12-20T12:04:19.017 回答
-1

您可以使用以下方法隔离您想用模拟测试的部分:http: //github.com/charpi/erl_mock/tree/master

不过,我没有使用 erl_mock 的经验。

于 2009-01-11T16:28:24.477 回答