[译]EUnit——erlang的轻量级单元测试框架(3)
译文:
目录:
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 ofassert
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 theNOTEST
macro when compiling, for example as an option to erlc
, as in:
erlc -DNOTEST my_module.erlor 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.
-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.
原文:
- Including the EUnit header file
- Writing simple test functions
- Running EUnit
- Writing test generating functions
- An example
- Disabling testing
- Avoiding compile-time dependency on EUnit
Including the EUnit header file
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 theEUNIT_NOAUTO
macro is defined) - Makes all the preprocessor macros of EUnit available, to help writing tests
-include_lib(...)
to work, the Erlang module search path must contain a directory whose name ends in eunit/ebin
(pointing to the ebin
subdirectory of the EUnit installation directory). If EUnit is installed as lib/eunit
under your Erlang/OTP system directory, its ebin
subdirectory will be automatically added to the search path when Erlang starts. Otherwise, you need to add the directory explicitly, by passing a -pa
flag to the erl
or erlc
command. For example, a Makefile could contain the following action for compiling .erl
files:
erlc -pa "path/to/eunit/ebin" $(ERL_COMPILE_FLAGS) -o$(EBIN) $<or if you want Eunit to always be available when you run Erlang interactively, you can add a line like the following to your
$HOME/.erlang
file:
code:add_path("/path/to/eunit/ebin").
Writing simple test functions
The EUnit framework makes it extremely easy to write unit tests in Erlang. There are a few different ways of writing them, though, so we start with the simplest:
A function with a name ending in ..._test()
is recognized by EUnit as a simple test function - it takes no arguments, and its execution either succeeds (returning some arbitrary value that EUnit will throw away), or fails by throwing an exception of some kind (or by not terminating, in which case it will be aborted after a while).
reverse_test() -> lists:reverse([1,2,3]).
This just tests that the function lists:reverse(List)
does not crash when List
is [1,2,3]
. It is not a great test, but many people write simple functions like this one to test the basic functionality of their code, and those tests can be used directly by EUnit, without changes, as long as their function names match.
Use exceptions to signal failure
To write more interesting tests, we need to make them crash (throw an exception) when they don't get the result they expect. A simple way of doing this is to use pattern matching with=
, as in the following examples:
reverse_nil_test() -> [] = lists:reverse([]). reverse_one_test() -> [1] = lists:reverse([1]). reverse_two_test() -> [2,1] = lists:reverse([1,2]).
If there was some bug in lists:reverse/1
that made it return something other than [2,1]
when it got [1,2]
as input, then the last test above would throw a badmatch
error. The first two (we assume they do not get a badmatch
) would simply return []
and [1]
, respectively, so both succeed. (Note that EUnit is not psychic: if you write a test that returns a value, even if it is the wrong value, EUnit will consider it a success. You must make sure that the test is written so that it causes a crash if the result is not what it should be.)
Using assert macros
If you want to use Boolean operators for your tests, theassert
macro comes in handy (see EUnit macros for details):
length_test() -> ?assert(length([1,2,3]) =:= 3).
The ?assert(Expression)
macro will evaluate Expression
, and if that does not evaluate to true
, it will throw an exception; otherwise it just returns ok
. In the above example, the test will thus fail if the call to length
does not return 3.
Running EUnit
If you have added the declaration -include_lib("eunit/include/eunit.hrl")
to your module, as described above, you only need to compile the module, and run the automatically exported function test()
. For example, if your module was named m
, then calling m:test()
will run EUnit on all the tests defined in the module. You do not need to write -export
declarations for the test functions. This is all done by magic.
You can also use the function eunit:test/1
to run arbitrary tests, for example to try out some more advanced test descriptors (see EUnit test representation). For example, running eunit:test(m)
does the same thing as the auto-generated function m:test()
, while eunit:test({inparallel, m})
runs the same test cases but executes them all in parallel.
Putting tests in separate modules
If you want to separate your test code from your normal code (at least for testing the exported functions), you can simply write the test functions in a module named m_tests
(note: not m_test
), if your module is named m
. Then, whenever you ask EUnit to test the module m
, it will also look for the module m_tests
and run those tests as well. See ModuleName
in the section Primitives for details.
EUnit captures standard output
If your test code writes to the standard output, you may be surprised to see that the text does not appear on the console when the tests are running. This is because EUnit captures all standard output from test functions (this also includes setup and cleanup functions, but not generator functions), so that it can be included in the test report if errors occur. To bypass EUnit and print text directly to the console while testing, you can write to the user
output stream, as in io:format(user, "~w", [Term])
. The recommended way of doing this is to use the EUnit Debugging macros, which make it much simpler.
Writing test generating functions
A drawback of simple test functions is that you must write a separate function (with a separate name) for each test case. A more compact way of writing tests (and much more flexible, as we shall see), is to write functions that return tests, instead of being tests.
A function with a name ending in ..._test_()
(note the final underscore) is recognized by EUnit as a test generator function. Test generators return arepresentation of a set of tests to be executed by EUnit.
Representing a test as data
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).
Using macros to write tests
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.
Underscore-prefixed macros create test objects
But this example can be made even shorter! Most test macros, such as the family ofassert
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.
An example
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.)
See EUnit test representation for a full list of all the ways you can specify test sets in EUnit.
Disabling testing
Testing can be turned off by defining theNOTEST
macro when compiling, for example as an option to erlc
, as in:
erlc -DNOTEST my_module.erlor by adding a macro definition to the code, before the EUnit header file is included:
-define(NOTEST, 1).
(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.
-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
See Compilation control macros for details about these macros.
Avoiding compile-time dependency on 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.