the use of eunit is very easy and intuitive. in principle every function which ends with _test of arity 0 (i.e. no input arguments) will be automaticly exported by eunit. eunit will also create a function called test() which will call all tests functions which have been defined. this mechanism leaves the programmer with only the actual test writing, and saves him/her the tedious wrappers and procedures. writing a test on a new module become something which take few seconds.

for example, the next function has its own testing function:

flip_sides(Side) ->
	case Side of
		x -> o;
		o -> x
	end.
 
flip_sides_test() ->
	?assert(flip_sides(x) == o),
	?assert(flip_sides(o) == x).

the testing function is invoked automatically by calling the test function of this module, e.g. mymodule:test(). the test function is supplied by the eunit framework, which takes every function that ends with _test() and add it to the testing list of that module.

a more advanced testing method is needed for testing the always loops that keep the state on the erlang module. few problems need to be solved in order to properly test those kind of loops:

  • initial state loading.
  • external function calls.
  • check of the new state, after each event.
  • the first problem can be solved by adding a new start function for debug use, which takes all the state variables of the always loop as parameters. this allows you to start the always loop in any state you want.

    % original start of arity/0
    start() -> start([], start).
     
    % new function for debug of arity/2
    start(State, Status) ->  register(?MODULE, spawn(fun() -> always(State, Status) end)).
     
    % the state loop
    always(State, Status) ->
    	receive
    		{From, stop} -> From ! stopped;
    		{From, reset} -> always([], start);
    		{From, {add, N}} -> always(module2:add(N, State), continue)
    	end.

    the next problem is the call for external function, shown here as call for module2:add/2, i solved this by adding another parameter to the loop’s state in order to keep a reference to the called function. i have also added it to the start function.

    % original start of arity/0
    start() -> start([], start, fun module2:add/2).
     
    % new function for debug of arity/2
    start(State, Status, Func) ->
    	register(?MODULE, spawn(fun() -> always(State, Status, Func) end)).
     
    % the state loop
    always(State, Status, Func) ->
    	receive
    		{From, stop} -> From ! stopped;
    		{From, reset} -> always([], start, Func);
    		{From, {add, N}} -> always(Func(N, State), continue, Func)
    	end.

    adding a reference to the external function allows me to switch to a mocking function as desired. for the new state checking i have added a dump command to the always loop which return me the current state, so i can test it on my testing functions.

    % the state loop
    always(State, Status, Func) ->
    	receive
    		{From, stop} -> From ! stopped;
    		{From, reset} -> From ! reset, always([], start, Func);
    		{From, {add, N}} -> always(Func(N, State), continue, Func);
    		{From, dump} -> {State, dump, Func}
    	end.

    last i’ll show the test function:

    always_reset_test() ->
    	Fun = fun callback_check_reset_mockup/2,
    	start([x, o], continue, Fun),
    	rpc(reset),
    	?assert(rpc(dump) == {[], start, Fun}),
    	stop().

    callback_check_reset_mockup(N, State) -> void. % you can check match if applicable

    that’s it!