Erlang EUnit

Erlang单元测试框架

EUnit头文件

-include_lib("eunit/include/eunit.hrl").
在Erlang模块中使用EUnit,需要在模块中包含EUnit头文件(位于模块声明之后,函数声明之前)。

xx.erl模块中声明了EUnit头文件会产生3种影响:

  1. 自动导出一个 xx:test/0 函数
  2. 自动导出xx模块中全部函数名以 _test/0_test_/0 结尾的函数
  3. 可以在xx模块中使用EUnit的宏定义

为了使EUnit头文件生效,Erlang的模块搜索路径必须包含一个以eunit/ebin结尾的目录(指向EUnit安装目录的ebin子目录)。如果EUnit作为lib/eunit安装在Erlang/OTP系统目录下,那么当Erlang启动时,EUnit的ebin子目录会自动添加到搜索路径;否则,就需要通过向erlerlc命令传递-pa参数来显式的添加目录:
e.g.erlc -pa "path/to/eunit/ebin" $(ERL_COMPILE_FLAGS) -o$(EBIN) $<
如果希望EUnit始终可用,则可以在$HOME/.erlang文件中添加一行code:add_path("/path/to/eunit/ebin").

_test/0

一个名称以_test/0结尾的函数会被EUnit识别成一个简单的测试函数:
测试函数返回任意值都会被EUnit丢弃,会被认为测试通过;
如果测试函数抛出异常,或者死循环(这种情况下它会在一段时间后被终止),会被认为测试未通过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
reverse_list(List) ->
lists:reverse(List).
%% 返回任意值 测试通过
reverse_list_test() ->
reverse_list([1,2,3]).
%% 抛出异常 测试未通过
reverse_list_throw_test() ->
[] = reverse_list([1, 2, 3]).
%% EUnit宏
%% 表达式不为 true 则抛出异常
length_test() ->
?assert(length([1,2,3]) =:= 3).
%% 如果 length([1,2,3]) =:= 3 为 false
%% 将抛出异常,测试不通过

Running EUnit

xx.erl包含了EUnit头文件,并且添加了测试代码后,可以在Erlang shell中使用xx:test().或者eunit:test(xx).运行测试代码。
并行执行eunit:test({inparallel, xx}).

将测试代码放在单独模块中

xx.erl编写测试模块xx_tests.erl(NOTxx_test.erl),当请求EUnit对xx模块进行测试,它也会寻找xx_tests模块。

EUnit捕获标准输出

EUnit会捕获测试代码中全部的标准输出,在测试代码中产生标准输出,不会出现在Erlang shell中
如果要在测试代码中产生标准输出,可以用使用io:format(user, "~w", [X])写入用户输出流,或者使用EUnit宏

_test_/0

_test/0的缺点是必须为每一个测试用例编写一个独立的函数(还得具有独立的名字),而使用_test_/0可以编写返回测试的函数(test generator function)。

1
2
3
4
5
basic_test_() ->
fun() -> ?assert(1 + 1 =:= 2) end.
%% 等同于
simple_test() ->
?assert(1 + 1 =:= 2).

事实上,EUnit处理所有的简单测试_test/0,就像处理fun表达式一样,将它们放在一个列表中,并逐个运行它们。

1
2
3
4
5
6
7
8
%% _test宏
%% 将任何表达式作为参数,并将其放在fun表达式中
%% 主体可以是任何类型的测试表达式,就像一个简单的测试函数的主体
basic_test_() ->
?_test(?assert(1 + 1 =:= 2)).
%% 等同于
basic_test_() ->
?_assert(1 + 1 =:= 2).

?_assert()?_test(?assert())的简写。

An example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-module(fib).
-export([fib/1]).
-include_lib("eunit/include/eunit.hrl").
fib(0) -> 1;
fib(1) -> 1;
fib(N) when N > 1 -> fib(N-1) + fib(N-2).
fib_test_() ->
[
?_assert(fib(0) =:= 1),
?_assert(fib(1) =:= 1),
?_assert(fib(2) =:= 2),
?_assert(fib(3) =:= 3),
?_assert(fib(4) =:= 5),
?_assert(fib(5) =:= 8),
?_assertException(error, function_clause, fib(-1)),
?_assert(fib(31) =:= 2178309)
].

禁用测试

EUnit可以通过在编译时定义NOTEST宏来关闭
erlc -DNOTEST xx.erl
或者在包含EUnit头文件之前添加宏定义
-define(NOTEST, 1).该值不重要,通常是1或者true

除非定义了EUNIT_NOAUTO宏,否则NOTEST宏会自动将代码中没有明确声明导出的测试函数删除

采用条件编译来避免EUnit对编译的依赖

1
2
3
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.