erlang开发中使用EUnit进行单元测试

目录:

 

3.1 包含EUnit头文件

3.2 写一个简单的生成函数

3.3 运行EUnit

3.4 写一个测试的生成函数

3.5 一个实例

3.6 关闭测试

3.7 避免编译时期对EUnit的信赖

 

三、开始

 

3.1 包含EUnit头文件

 

在erlang模块中用EUnit最简单的方法是在模块的开头增加如下代码:

 

-include_lib(“eunit/include/eunit.hrl”).

 

增加这个之后会有如下作用:

 

1、自动输出test/0函数(除非你关闭测试,并且你的模块中没有test/0函数),它可以用来运行在本模块所有的单元测试

 

2、所有匹配…_test()或者…_test_()的函数自动输出(除非你关闭测试,或者定义了 EUNIT_NOAUTO宏)

 

3、让EUnit中的所有宏生效来帮助测试

 

注意:要想让-include_lib(…)工作,你的“搜索路径”必须包括eunit/ebin目录。当然如果lib/eunit已经安装在你的系统目录下,它的ebin子目录也就会自动加入到“搜索路径”了,你就不用再做这方面的操作了。否则你就要用erl -pa命令把这个目录加上。当然如果你想在与erlang交互时始终能用EUnit,你只需要这样做:在你的 $HOME/.erlang文件中增加一行:code:add_path(“/path/to/eunit/ebin”).

 

3.2 写一个简单的测试函数

 

用EUnit框架让你在erlang中写一个单元测试非常简单。有好几种方式可以实现。这儿我们先介绍一种最简单的办法:

 

一个以…test()结尾的函数在EUnit中被认为是最简单的函数,它没有参数,它要么执行成功,要么执行失败。执行成功就会返回EUnit抛出的任意值; 执行失败就会抛出一种异常。

 

下面是一种简单的测试函数:

 

reverse_test() -> lists:reverse([1,2,3]).

 

它只是用来测试list:reverse(List)函数不会崩溃。这不是一个好的测试,但很多人写这样简单的函数来测试他们代码的基本功能,而这些测试不用修改就可以直接被EUnit利用,只要是它的函数名匹配(即以_test结尾)。

 

1、用异常来标志出错

 

为写一个比较有趣的测试,我们要让它在得不到我们期待的结果时抛出异常。一种简的方式是用“=”进行模式匹配,如下:

 

reverse_nil_test() -> [] = lists:reverse([]).

reverse_one_test() -> [1] = lists:reverse([1]).

reverse_two_test() -> [2,1] = lists:reverse([1,2]).

 

如上例子可以知道如果得到不是预想的结果就会抛出badmatch异常。注意:Eunit不是智能的,如果你写一个测试只返回一个值,即使它是一个错误的结果,EUnit也只会认为它是成功的。你必须确保你写的测试函数在它得到的结果不是它应该得到的结果时产生崩溃。

 

2、用assert宏

 

用布尔操作来做你的测试,具体查看EUnit 宏

 

例: length_test() -> ?assert(length([1,2,3]) =:= 3).

 

在宏?assert(Expression) 中,它将会计算Expression的值,如果不是true它就会抛出一个异常,否则它就会返回一个ok。在上面的例子中如果 length([1,2,3]的值不是3的话,就会失败。

 

3.3 运行EUnit

 

首先要先按照上面的步骤把声明  -include_lib(“eunit/include/eunit.hrl”)加到你的模块里面, 你只需要编译这个模块,它就会自动输出这个模块的test()函数,而不需要再用-export进行声明,一切都已经自动做好了。

 

这儿你也可以用eunit:test/1函数来运行任意的测试。例如:你用eunit:test(Mod).和用Mod:test().执行的结果是一样的。但eunit:test/1方法可以进行更高级的测试,如:eunit:test({inparallel, Mod}).这个命令和Mod:test()运行的结果是一样的,只不过它是要让它们全部并行的运行。

 

1、把源码和测试代码放在不同的模块里

 

如果你想把你的测试代码和源码分开(当然这儿的测试代码就只能用于测试源码中输出的函数),你可以写一个名为Mod_tests的模块(注意这儿是Mod_tests而不是Mod_test)。这样当您想测试模块Mod时,你就可以运行Mod_tests中的测试函数来实现这个功能了。

 

2、EUnit会捕捉到标准的输出

 

如果你想测试标准的输出函数,你会惊奇的发现在控制台上不会显示出这些文本。因为EUnit捕捉到了所有的标准输出。为了在测试时挠开EUnit好把文本直接打印到控制台,你可以用io:format(user, “~w”, [Term]).把文本打印到输出流user上。这儿推荐的方式是用EUnit的调试宏,它会让这实现的更为简单。

 

3.4 写一个测试的生成函数

 

简单测试函数的缺点是你必须为每个测试实例写一个测试函数。一种更紧凑的方式不是写一个函数来进行测试,而是写一个函数来返回测试结果。

 

一个以…_test_()结尾的函数在EUnit中被认为是一个测试生成函数。测试生成函数返回一系列的测试函数。

 

这儿说下我自己的理解:

 

简单函数只能证明这个函数对应的测试没有问题,如果这个简单函数里面有N条语句进行测试,只有当所有这N条函数都执行成功时才返回成功。而测试生成函数会为每条语句生成一个测试结果,你就可以很方便的知道是哪个函数出现了问题了。

 

1、用数据的方式展现测试

The most basic representation of a test is a single fun-expression that takes no arguments. For example, the following test generator:

 

basic_test_() ->

fun () -> ?assert(1 + 1 =:= 2) end.

 

will have the same effect as the following simple test:

 

simple_test() ->

?assert(1 + 1 =:= 2).

 

(in fact, EUnit will handle all simple tests just like it handles fun-expressions: it will put them in a list, and run them one by one).

 

这儿最后一句提到,它会把每一条语句放到一个列表中,然后一个个的执行它。其实就相当于为每个语句生成一个简单的测试函数。在上面的那两个例子表示效果一样的原因是它们都只有一个函数。

 

2、用宏来写测试

To make tests more compact and readable, as well as automatically add information about the line number in the source code where a test occurred (and reduce the number of characters you have to type), you can use the _test macro (note the initial underscore character), like this:

 

basic_test_() ->

?_test(?assert(1 + 1 =:= 2)).

 

The _test macro takes any expression (the “body”) as argument, and places it within a fun-expression (along with some extra information). The body can be any kind of test expression, just like the body of a simple test function.

 

注:用宏来写的原因主要是为了让代码最紧凑而且减少代码的数量

 

3、用有下划线前缀的宏来创建测试对象

But this example can be made even shorter! Most test macros, such as the family of assert macros, have a corresponding form with an initial underscore character, which automatically adds a ?_test(…) wrapper. The above example can then simply be written:

 

basic_test_() ->

?_assert(1 + 1 =:= 2).

 

which has exactly the same meaning (note the _assert instead of assert). You can think of the initial underscore as signalling test object.

 

注:用_assert宏可以让代码更加紧凑,而且它和前面的简单测试中用到的assert宏用法一样,更加方便。

 

这儿 ?_test(?_assert(1+1 =:= 2)).的功能和?_assert(1+1 =:= 2).是一样的。

 

3.5 一个实例

Sometimes, an example says more than a thousand words. The following small Erlang module shows how EUnit can be used in practice.

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

].

 

(Author’s note: When I first wrote this example, I happened to write a * instead of + in the fib function. Of course, this showed up immediately when I ran the tests.)

3.6 关闭测试

Testing can be turned off by defining the NOTEST macro when compiling, for example as an option to erlc, as in:

 

erlc -DNOTEST my_module.erl

 

or by adding a macro definition to the code, before the EUnit header file is included:

 

-define(NOTEST, 1).//这儿的值不重要,为1或true都没关系!

 

当你想关闭测试时有两种办法:一种是在编译时定义宏NOTEST, 或者在EUnit头文件被包含之前在源文件里增加对宏的声明。

 

(the value is not important, but should typically be 1 or true).

 

Note that unless the EUNIT_NOAUTO macro is defined, disabling testing will also automatically strip all test functions from the code, except for any that are explicitly declared as exported.

For instance, to use EUnit in your application, but with testing turned off by default, put the following lines in a header file:

 

-define(NOTEST, true).

-include_lib(“eunit/include/eunit.hrl”).

 

and then make sure that every module of your application includes that header file. This means that you have a only a single place to modify in order to change the default setting for testing. To override the NOTEST setting without modifying the code, you can define TEST in a compiler option, like this:

 

erlc -DTEST my_module.erl

 

3.7 避免EUnit在编译时期的依赖性

If you are distributing the source code for your application for other people to compile and run, you probably want to ensure that the code compiles even if EUnit is not available. Like the example in the previous section, you can put the following lines in a common header file:

 

-ifdef(TEST).

-include_lib(“eunit/include/eunit.hrl”).

-endif.

 

and, of course, also make sure that you place all test code that uses EUnit macros within -ifdef(TEST) or -ifdef(EUNIT) sections.

猜你喜欢