为什么 Eunit 不需要导出测试函数?

Why does Eunit not require test functions to be exported?

我正在阅读 Learn You Some Erlang 中的 EUnit 一章,我从所有代码示例中注意到的一件事是测试函数从未在 -export() 子句中声明。

为什么 EUnit 能够提取这些测试函数?

From the documentation:

The simplest way to use EUnit in an Erlang module is to add the following line at the beginning of the module (after the -module declaration, but before any function definitions):

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

This will have the following effect:

  • Creates an exported function test() (unless testing is turned off, and the module does not already contain a test() function), that can be used to run all the unit tests defined in the module

  • Causes all functions whose names match ..._test() or ..._test_() to be automatically exported from the module (unless testing is turned off, or the EUNIT_NOAUTO macro is defined)

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

首先查看 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 参考手册的模块章节 (Pre-Defined Module Attributes):

-compile(Options).
Compiler options. Options is a single option or a list of options. This attribute is added to the option list when compiling the module. See the compile(3) manual page in Compiler.

compile(3):

{parse_transform,Module}
Causes the parse transformation function Module:parse_transform/2 to be applied to the parsed code before the code is checked for errors.

来自 erl_id_trans 模块:

This module performs an identity parse transformation of Erlang code. It is included as an example for users who wants to write their own parse transformers. If option {parse_transform,Module} is passed to the compiler, a user-written function parse_transform/2 is called by the compiler before the code is checked for errors.

基本上,如果模块 M 包含 {parse_transform, Module} 编译选项,那么 M 的所有函数和属性都可以通过使用 Module:parse_transform/2 的实现进行迭代。它的第一个参数是Forms,也就是Erlang的abstract format (described in Erlang Run-Time System Application (ERTS) User's Guide.

中描述的M的模块声明

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 builds a module declaration from the original Forms (given to eunit_autoexport:parse_transform/2 as the first argument) and the list of functions to be exported (that was supplied by form/5 above). On line 82 it injects the test/0 function mentioned in the EUnit documentation