2

I'm going through the EUnit chapter in Learn You Some Erlang and one thing I am noticing from all the code samples is the test functions are never declared in -export() clauses.

Why is EUnit able to pick these test functions up?

4

2 回答 2

2

从文档中

在 Erlang 模块中使用 EUnit 的最简单方法是在模块的开头添加以下行(在-module声明之后,但在任何函数定义之前):

-include_lib("eunit/include/eunit.hrl").

这将产生以下效果:

  • 创建一个导出函数 test()(除非关闭了测试,并且模块还没有包含 test() 函数),该函数可用于运行模块中定义的所有单元测试

  • 导致名称匹配 ..._test()..._test_() 从模块中自动导出的所​​有函数(除非关闭测试,或EUNIT_NOAUTO定义宏)

于 2016-03-26T16:33:29.477 回答
0

很高兴我找到了这个问题,因为它给了我一种有意义的拖延方式,我想知道函数是如何动态创建和导出的。

首先查看 Erlang/OTP Github 存储库中影响 EUnit 的最新提交,即4273cbd。(这样做的唯一原因是找到一个相对稳定的锚点而不是 git 分支。)

0.包含EUnit的头文件

根据EUnit's User's Guide,第一步是-include_lib("eunit/include/eunit.hrl").在测试模块中,所以我认为这就是魔法发生的地方。

1. otp/lib/eunit/include/eunit.hrl(第 79 - 91 行)

%% Parse transforms for automatic exporting/stripping of test functions.
%% (Note that although automatic stripping is convenient, it will make
%% the code dependent on this header file and the eunit_striptests
%% module for compilation, even when testing is switched off! Using
%% -ifdef(EUNIT) around all test code makes the program more portable.)

-ifndef(EUNIT_NOAUTO).
-ifndef(NOTEST).
-compile({parse_transform, eunit_autoexport}).
-else.
-compile({parse_transform, eunit_striptests}).
-endif.
-endif.

1.1 什么-compile({parse_transform, eunit_autoexport}).意思?

从 Erlang 参考手册的模块章节(预定义模块属性):

-compile(Options).
编译器选项。选项是单个选项或选项列表。该属性在编译模块时添加到选项列表中。请参阅编译器中的compile(3)手册页。

编译(3):

{parse_transform,Module}
使解析转换函数 Module:parse_transform/2 在检查代码错误之前应用于已解析的代码。

erl_id_trans模块:

该模块执行 Erlang 代码的身份解析转换。它是作为示例供想要编写自己的解析转换器的用户使用的。如果将选项{parse_transform,Module}传递给编译器,parse_transform/2则编译器会在检查代码是否有错误之前调用用户编写的函数。

基本上,如果模块 M 包含{parse_transform, Module}compile 选项,那么 M 的所有函数和属性都可以通过使用您的Module:parse_transform/2. 它的第一个参数是Forms,它是以 Erlang 的抽象格式描述的 M 的模块声明(在Erlang Run-Time System Application (ERTS) User's Guide中描述。

2.otp/lib/eunit/src/eunit_autoexport.erl

该模块仅导出parse_transfrom/2以满足{parse_transform, Module}编译选项,其首要任务是找出测试用例函数和生成器的配置后缀是什么。如果未手动设置,则分别使用_test_test_(通过lib/eunit/src/eunit_internal.hrl)。

然后它使用 扫描模块的所有函数和属性eunit_autoexport:form/5,并构建一个要导出的函数列表,其中上面的后缀匹配(加上原始函数。我可能错了这个......)。

最后,eunit_autoexport:rewrite/2从原始Formseunit_autoexport:parse_transform/2作为第一个参数给出)和要导出的函数列表(由form/5上面提供)构建一个模块声明。在第 82 行,它注入了EUnit 文档test/0中提到的函数。

于 2018-11-14T00:43:10.983 回答