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

发布于 2024-07-10 19:37:50 字数 346 浏览 8 评论 0原文

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

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

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

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

I'm spending a bit of time with Erlang, and I'm wanting to apply TDD to code I'm writing.

While EUnit in the standard lib provides a nice traditional unit testing framework for testing regular style code, there doesn't seem to be anything to help specifically with testing concurrent code, which is used a LOT in Erlang.

Note that we're talking Erlang here, which uses message passing (as opposed to shared state) for communication between concurrent processes, so techniques for unit testing concurrent code in shared state languages may not be applicable.

Anyone found a good way to test concurrent code in Erlang?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

森林很绿却致人迷途 2024-07-17 19:37:50

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

另外值得一提的是 Dialyzer(ERlang 的 DIscrepancy AnaLYZer)(论文教程手册) ,这是一个对代码进行静态分析以查找错误的工具。 这也支持检测一些并发错误(请参阅 )。

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

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

I just found some cool newly (as of 2011) developed software for testing concurrent Erlang applications called Concuerror. There is a few papers on it and a repository on github.
Apparently it works by using its own scheduler and systematically testing different interleaving between the processes.

Also worth mentioning is Dialyzer (DIscrepancy AnaLYZer for ERlang) (Papers, Tutorial, Manual) , which is a tool for static analysis of the code for finding errors. This has support for detecting some concurrency-errors too (see paper).

I have not tested any of these myself, though dialyzer seems to be relatively mature software. Both programs have a GUI for working with the tests.

PS. For the non-concurrent parts, EUnit and QuickCheck (there is a free version also) should work fine.

硬不硬你别怂 2024-07-17 19:37:50

这个问题有点模糊(“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 与您是否测试并发代码是正交的,因此上述测试技术也可以用于正常的单元测试。

The question is a bit vague ("Erlang is concurrent, test it with Erlang!") but I'll try to elaborate a bit.

Testing Erlang code can range from the straightforwardly simple (the right input produces the right output) to setting up complex test harnesses that verify your component behaves the way it should. What is best for your given situation depends totally on the requirements you have and the amount of black box / white box testing you want to do.

Part of the beauty of Erlang is the ability to make concurrency transparent. Consider the following example (a function that parallelizes the summation of a list of lists):

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

You would typically test this with a very simple EUnit test case:

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

Now, let's say we have a bit more explicit API to this functionality: a process pool as argument:

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.

When testing this, we have to deal with faking this process pool:

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

As you can see, testing concurrency programs in Erlang can take different shapes. These are extremely simple examples, but with Erlang's built in concurrency primitives it is very easy to create the kind of test harness you want, abstracting at the right levels.

I usually find TDD to be orthogonal to wether you're testing concurrent code or not, so said testing techniques can be used for normal unit testing as well.

沩ん囻菔务 2024-07-17 19:37:50

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

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

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

另请查看 ProTest 项目:http://www.protest-project.eu< /a>

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

The one tool I know of that you can use to test Erlang programs under concurrent scenarios is QuickCheck: http://www.quviq.com/

You can specify your expected behaviour using properties, and the tool can exercise your Erlang program over a range of inputs and time to ensure the properties are satisfied.

Unfortunately it's a commercial tool

Also have a look at the ProTest project: http://www.protest-project.eu

Update: The ProTest project have released a survey, "Results for: Erlang testing tools survey for the ProTest project"

柏林苍穹下 2024-07-17 19:37:50

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

Because errors in concurrent code manifest themselves depending on the order various parts get executed, I think it's best not to rely on testing to uncover your bugs in the concurrent part of your code. Such bugs are very easy to slip through and extremely difficult to locate. See for instance how the double-locking technique, which was widely cited and used as an efficient method for implementing lazy initialization in a multithreaded environment, was later found to be broken. It's far better to use appropriate abstractions and techniques to make the concurrent parts of your code correct "by design".

一口甜 2024-07-17 19:37:50

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

Adding to what Diomidis said, the only real way to gain some confidence with your concurrent code is to run extended tests under constantly varying conditions; and even then, it's only proof that it didn't fail under those conditions.

辞别 2024-07-17 19:37:50

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

不过,我没有 erl_mock 的经验。

You could isolate out the part you would like to test with mocks by using this: http://github.com/charpi/erl_mock/tree/master

I have no experience with erl_mock though.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文