class: center, middle
Pre-alpha Kottans courses
class: center, middle
EUnit is unit testing framework that builds on ideas from JUnit(Java) and SUnit(Smalltalk). But EUnit was developed especially to be used in functional concurrent programming language. It is as laconic as Erlang is.
EUnit is unit testing framework that builds on ideas from JUnit(Java) and SUnit(Smalltalk). But EUnit was developed especially to be used in functional concurrent programming language. It is as laconic as Erlang is.
EUnit was developed to be least intrusive as it possibly can, so adding tests to your code will not change it. Anyway, if you plan to test only exported functions of module, you can place your tests separately - and it's widely used by application structure proposed by rebar
build system.
- Unit testing - testing that a program unit behaves as it supposed to do, according to its specifications.
- Unit testing - testing that a program unit behaves as it supposed to do, according to its specifications.
- Regression testing - running a set of tests after program change to check that nothing was broken.
- Unit testing - testing that a program unit behaves as it supposed to do, according to its specifications.
- Regression testing - running a set of tests after program change to check that nothing was broken.
- Integration testing - testing that program units work together as expected.
- Unit testing - testing that a program unit behaves as it supposed to do, according to its specifications.
- Regression testing - running a set of tests after program change to check that nothing was broken.
- Integration testing - testing that program units work together as expected.
- System testing - testing that complete system behaves according to its specification.
- Unit testing - testing that a program unit behaves as it supposed to do, according to its specifications.
- Regression testing - running a set of tests after program change to check that nothing was broken.
- Integration testing - testing that program units work together as expected.
- System testing - testing that complete system behaves according to its specification.
- Test Driven Development - methodology of development based on writing tests (as a future code specification) before your source code. Helps developer to avoid hyper-complication and ease refactoring.
- Unit testing - testing that a program unit behaves as it supposed to do, according to its specifications.
- Regression testing - running a set of tests after program change to check that nothing was broken.
- Integration testing - testing that program units work together as expected.
- System testing - testing that complete system behaves according to its specification.
- Test Driven Development - methodology of development based on writing tests (as a future code specification) before your source code. Helps developer to avoid hyper-complication and ease refactoring.
- Mock object - fake object that behaves like real (maybe still not implemented or not available) object.
- Unit testing - testing that a program unit behaves as it supposed to do, according to its specifications.
- Regression testing - running a set of tests after program change to check that nothing was broken.
- Integration testing - testing that program units work together as expected.
- System testing - testing that complete system behaves according to its specification.
- Test Driven Development - methodology of development based on writing tests (as a future code specification) before your source code. Helps developer to avoid hyper-complication and ease refactoring.
- Mock object - fake object that behaves like real (maybe still not implemented or not available) object.
- Test case - single test that detects that something atomic works as expected. Test can only pass or fail.
- Unit testing - testing that a program unit behaves as it supposed to do, according to its specifications.
- Regression testing - running a set of tests after program change to check that nothing was broken.
- Integration testing - testing that program units work together as expected.
- System testing - testing that complete system behaves according to its specification.
- Test Driven Development - methodology of development based on writing tests (as a future code specification) before your source code. Helps developer to avoid hyper-complication and ease refactoring.
- Mock object - fake object that behaves like real (maybe still not implemented or not available) object.
- Test case - single test that detects that something atomic works as expected. Test can only pass or fail.
- Test suite - collection of test cases joined together by specific target (e.g. single function or module). May be composed of other test suites.
We are going to write, for example, simple calculator of Fibonacci sequence. Let's start with module fibonacci.erl
(testing/fibonacci_00.erl
in courses source code).
% testing/fibonacci_00.erl
-module(fibonacci_00).
Let's take a look on module info:
1> c(fibonacci_00).
{ok,fibonacci_00}
2> fibonacci_00:module_info().
[{module,fibonacci_00},
{exports,[{module_info,0},{module_info,1}]},
{attributes,[{vsn,[261853951541325737780411885127686387569]}]},
{compile,[{options,[]},
{version,"6.0"},
{time,{2015,8,19,13,39,38}},
{source,"/Users/vessi/edu/erlcourses/testing/fibonacci_00.erl"}]},
{native,false},
{md5,<<196,255,60,147,250,56,166,39,213,231,192,152,192,
83,119,113>>}]
So the first step will be to add EUnit support to our Fibonacci calculator. This can be done by adding EUnit header.
% testing/fibonacci_01.erl
-module(fibonacci_01).
-include_lib("eunit/include/eunit.hrl").
Let's see what has changed.
3> c(fibonacci_01).
{ok,fibonacci_01}
4> fibonacci_01:module_info().
[{module,fibonacci_01},
{exports,[{test,0},{module_info,0},{module_info,1}]},
{attributes,[{vsn,[297479195634998726548445758768610341143]}]},
{compile,[{options,[]},
{version,"6.0"},
{time,{2015,8,19,13,42,38}},
{source,"/Users/vessi/edu/erlcourses/testing/fibonacci_01.erl"}]},
{native,false},
{md5,<<223,204,104,184,217,68,52,0,87,80,38,225,164,88,
241,23>>}]
As you can see, eunit.hrl
added test/0
function to exports
section of our module. Let's run it because we are strong and courageous!
5> fibonacci_01:test()
There were no tests to run.
ok
5> fibonacci_01:test()
There were no tests to run.
ok
5> fibonacci_01:test()
There were no tests to run.
ok
Yes, this is ok, because we haven't wrote any test yet. Thanks, Captain Obvious!
5> fibonacci_01:test()
There were no tests to run.
ok
Yes, this is ok, because we haven't wrote any test yet. Thanks, Captain Obvious!
So it's time to add some. About Fibonacci sequence we know some rules:
- it starts from 0 and equals to 0 for 0
5> fibonacci_01:test()
There were no tests to run.
ok
Yes, this is ok, because we haven't wrote any test yet. Thanks, Captain Obvious!
So it's time to add some. About Fibonacci sequence we know some rules:
- it starts from 0 and equals to 0 for 0
- for 1 it equals to 1
5> fibonacci_01:test()
There were no tests to run.
ok
Yes, this is ok, because we haven't wrote any test yet. Thanks, Captain Obvious!
So it's time to add some. About Fibonacci sequence we know some rules:
- it starts from 0 and equals to 0 for 0
- for 1 it equals to 1
- for N it equals to fib(N-2)+fib(N-1)
So, we are going to write our first test that claims that Fibonacci sequence does not exists for negative numbers. EUnit recognize simple tests by _test()
in name, so let's create fib_negative_test()
function. And don't forget to create fib/1
function because we want to use it for test.
% testing/fibonacci.erl
-module(fibonacci_02).
-include("eunit/include/eunit.hrl").
fib(_) -> ok.
fib_negative_test() -> undefined = fib(-5).
Let's compile it and try to run our tests.
6> c(fibonacci_02).
{ok,fibonacci_02}
7> fibonacci_02:test().
fibonacci_02: fibonacci_negative_test (module 'fibonacci_02')...*failed*
in function fibonacci_02:fibonacci_negative_test/0 (fibonacci_02.erl, line 6)
**error:{badmatch,ok}
output:<<"">>
=======================================================
Failed: 1. Skipped: 0. Passed: 0.
Let's compile it and try to run our tests.
6> c(fibonacci_02).
{ok,fibonacci_02}
7> fibonacci_02:test().
fibonacci_02: fibonacci_negative_test (module 'fibonacci_02')...*failed*
in function fibonacci_02:fibonacci_negative_test/0 (fibonacci_02.erl, line 6)
**error:{badmatch,ok}
output:<<"">>
=======================================================
Failed: 1. Skipped: 0. Passed: 0.
Obviously it's failed because we don't return undefined
atom so matching was failed. We are going to fix it.
% testing/fibonacci_03.erl
-module(fibonacci_03).
-include_lib("eunit/include/eunit.hrl").
fib(_) -> undefined.
fibonacci_negative_test() -> undefined = fib(-5).
% testing/fibonacci_03.erl
-module(fibonacci_03).
-include_lib("eunit/include/eunit.hrl").
fib(_) -> undefined.
fibonacci_negative_test() -> undefined = fib(-5).
8> c(fibonacci_03).
{ok,fibonacci_03}
9> fibonacci_03:test().
Test passed.
ok
Ok, let's complete our tests.
% testing/fibonacci_04.erl
-module(fibonacci_04).
-include_lib("eunit/include/eunit.hrl").
fib(_) -> undefined.
fibonacci_negative_test() -> undefined = fib(-5).
fibonacci_0_test() -> 0 = fib(0).
fibonacci_1_test() -> 1 = fib(1).
fibonacci_2_test() -> 1 = fib(2).
fibonacci_5_test() -> 5 = fib(5).
Now let's try to launch our test suite.
10> c(fibonacci_04).
{ok,fibonacci_04}
11> fibonacci_04:test().
fibonacci_04: fibonacci_0_test...*failed*
in function fibonacci_04:fibonacci_0_test/0 (fibonacci_04.erl, line 7)
% skipped
fibonacci_04: fibonacci_1_test...*failed*
in function fibonacci_04:fibonacci_1_test/0 (fibonacci_04.erl, line 8)
% skipped
fibonacci_04: fibonacci_2_test...*failed*
in function fibonacci_04:fibonacci_2_test/0 (fibonacci_04.erl, line 9)
% skipped
fibonacci_04: fibonacci_5_test...*failed*
in function fibonacci_04:fibonacci_5_test/0 (fibonacci_04.erl, line 10)
% skipped
=======================================================
Failed: 4. Skipped: 0. Passed: 1.
error
% testing/fibonacci_05.erl
-module(fibonacci_05).
-include_lib("eunit/include/eunit.hrl").
fib(0) -> 0;
fib(1) -> 1;
fib(N) when N < 0 -> undefined;
fib(N) -> fib(N-1)+fib(N-2).
fibonacci_negative_test() -> undefined = fib(-5).
fibonacci_0_test() -> 0 = fib(0).
fibonacci_1_test() -> 1 = fib(1).
fibonacci_2_test() -> 1 = fib(2).
fibonacci_5_test() -> 5 = fib(5).
Let's run our tests and check our perfect code:
1> c(fibonacci_05).erl
{ok,fibonacci_05}
2> fibonacci_05:test().
All 5 tests passed.
ok
EUnit introduces a lot of macros to simplify it's usage. Take a look at the list
_test(Expr)
- it turnsExpr
into thing named "test object", which technically is the tuple{?LINE, fun() -> (Expr) end}
EUnit introduces a lot of macros to simplify it's usage. Take a look at the list
_test(Expr)
- it turnsExpr
into thing named "test object", which technically is the tuple{?LINE, fun() -> (Expr) end}
EUNIT
- this macro is always defined totrue
and can be used for-ifdef(EUNIT)...-endif.
instructions. The best way to disable compiling your test for production.
EUnit introduces a lot of macros to simplify it's usage. Take a look at the list
_test(Expr)
- it turnsExpr
into thing named "test object", which technically is the tuple{?LINE, fun() -> (Expr) end}
EUNIT
- this macro is always defined totrue
and can be used for-ifdef(EUNIT)...-endif.
instructions. The best way to disable compiling your test for production.TEST
- the same as before but more general and can be used for another test frameworks.
EUnit introduces a lot of macros to simplify it's usage. Take a look at the list
_test(Expr)
- it turnsExpr
into thing named "test object", which technically is the tuple{?LINE, fun() -> (Expr) end}
EUNIT
- this macro is always defined totrue
and can be used for-ifdef(EUNIT)...-endif.
instructions. The best way to disable compiling your test for production.TEST
- the same as before but more general and can be used for another test frameworks.EUNIT_NOAUTO
- disables autoexport of test functions (checkfibonacci_05:module_info/0
to see something interesting!)
And also we have some utility macros in EUnit framework
LET(Var, Arg, Expr)
- creates local binding forVar
withArg
inExpr
. It's same to(fun(Var)->(Expr)end)(Arg)
.
And also we have some utility macros in EUnit framework
LET(Var, Arg, Expr)
- creates local binding forVar
withArg
inExpr
. It's same to(fun(Var)->(Expr)end)(Arg)
.IF(Cond, TrueCase, FalseCase)
- evaluatesTrueCase
ifCond
evaluates totrue
otherwiseFalseCase
. This is the same as(case (Cond) of true -> (TrueCase); false -> (FalseCase) end.)
And now macros that simplifies testing itself.
assert(Bool)
- evaluates boolean expressionBool
. Can be used anywhere in program
And now macros that simplifies testing itself.
assert(Bool)
- evaluates boolean expressionBool
. Can be used anywhere in programassertNot(Bool)
- equivalent toassert(not(Bool))
And now macros that simplifies testing itself.
assert(Bool)
- evaluates boolean expressionBool
. Can be used anywhere in programassertNot(Bool)
- equivalent toassert(not(Bool))
assertMatch(GuardedPattern, Expr)
- evaluatesExpr
and matches againstGuardedPattern
. This macro has inversed versionassertNotMatch
And now macros that simplifies testing itself.
assert(Bool)
- evaluates boolean expressionBool
. Can be used anywhere in programassertNot(Bool)
- equivalent toassert(not(Bool))
assertMatch(GuardedPattern, Expr)
- evaluatesExpr
and matches againstGuardedPattern
. This macro has inversed versionassertNotMatch
assertEqual(Expect, Expr)
- evaluatesExpect
andExpr
and compares them. More informative than?assert(Expect =:= Expr)
. Has inversed versionassertNotEqual
EUnit has several equal forms:
fun() -> ?assert(1 + 1 =:= 2) end.
is equal to?assert(1 + 1 =:= 2).
because EUnit wraps simple tests as fun expressions?_test(?assert(1 + 1 =:= 2)).
provides more service information:
fib:19: fib_test_...*failed*
in function fib:'-fib_test_/0-fun-14-'/0 (fib.erl, line 19)
**error:{assert,[{module,fib}, % look, here we have extended information!
{line,19},
{expression,"fib ( 31 ) =:= 2178309"},
{expected,true},
{value,false}]}
output:<<"">>
?_assert(1 + 1 =:= 2).
is equal to?_test(?assert(1 + 1 =:= 2)).
class: center,middle
class: center,middle