[erlang-questions] Module mock-up's
Ladislav Lenart
lenartlad@REDACTED
Wed Mar 21 19:32:35 CET 2007
Peter Mechlenborg wrote:
> Hi,
>
> When writing tests I often find that I want to test the module that I
> working on, in isolation from the rest of the system.
> In the past I have sometimes created mock-up processes, for the parts of
> my system that interacts with the module I'm testing. This works well
> for some cases, but normally processes are encapsulated by a module, and
> what I really want to do is to create a mock-up of the module instead of
> the process.
>
> I haven't found anything with Google, and before I start trying to
> implementing a module-mock-up library, I would like to know if anyone
> have done anything similar, creating mock-up modules?
>
> I would also like to know if I'm missing something simple - how do you
> guys normally test Erlang code that depends on other modules, without
> creating a complete integration test?. In other words I guess my
> question is, how to do unit tests at the level of modules en Erlang?
Hello.
First, I'd like to appologize for a rather long answer, I just don't
know when to stop... :-)
I will start by answering your question about "mock-up" modules, though
I am affraid that I will disappoint you a little bit.
We implemented a simple 'sniffer' module. This is a process (not even
a 'gen' thing) that stores all incoming messages in a list and responds
to two messages: 'stop' and 'get_message'. The second returns the oldest
buffered message to the caller process (and removes it from the buffer).
The caller in our case is the 'shell_eval' process running the tests.
The example usage is like:
test_function(Pid, Sniffer) ->
%% No message arrives to the sniffer in 10 milliseconds.
?assert({error, timeout}, sniffer:wait(Sniffer, 10)),
some:action(Pid),
%% This effectively means that some:action(Pid) sends
%% a message 'message' to the sniffer within 10 milliseconds.
?assert({ok, message}, sniffer:wait(Sniffer, 10)),
ok.
Function sniffer:wait/2 returns one of:
* {ok, Message}
* {error, timeout}
We implemented sniffer in such a way that it "understands" a gen_XXX
messages (cast, call, event, ...) and returns us only "our" part of
them.
As you can see we actually didn't implement your mock-up module to be
replaced for the real one, we just check that the tested process sends
the "right" messages in proper order so later when it is connected to
the real process, all works as expected.
Though odd it might seem, it actually works for us pretty well.
And to answer your first question...
I started to learn Erlang (not a long time ago) by writing my own testing
framework with colleagues of mine. We wrote it in TD (test-driven) style
just as Kent Beck did in his famous "Test-Driven Development By Example".
We literally went step by step through the book. The only difference was
the language. This petty project has grown over past few months and we
happily use it ever since in our daily Erlang work.
Because we were all used to OO idiom, we wanted it to be as similar as
possible to the style we were already familiar with. An example testing
module now looks like:
----------------------------------------------------------------
%%% Module name is not important but as a convention we append "_tests".
-module(some_tests).
-export([setup_test/1, teardown_test/1, test_f/1]).
-export([setup__other__test/1, teardown__other__test/1, test__other__f/1]).
-export([test__no_setup__f/1]).
%%% This attribute annotates a test module.
%%% By this we can easily filter which tests to run:
%%% * run all tests: eunit:run()
%%% * run only "some" tests: eunit:run(some).
-test_categories([all, some]).
%% Called before each test function with signature test_XXX/1
%% (test_f/1 in this example).
setup_test(_) ->
"test_config".
%% Called after each test function with signature test_XXX/1.
%% The test framework guarantees that the teardown is called
%% even if there is an error in a test.
teardown_test("test_config") ->
%% clean up
ok.
%% A test.
test_f("test_config) ->
?assert(true, true).
%% Called before each test function with signature test__other__XXX/1.
setup__other__test(_) ->
"other_test_config".
%% Called after each test function with signature test__other__XXX/1.
teardown__other__test("other_test_config") ->
%% clean up
ok.
%% A test.
test__other__f("other_test_config") ->
?fail.
%% A test without a setup or teardown function is also possible.
test__no_setup__f(_) ->
Fun = fun () -> throw("ex") end,
?should_raise(Fun, throw, "ex"),
ok.
----------------------------------------------------------------
We invoke tests from the erlang shell:
eunit:report(complete, eunit:run()).
Function eunit:report/2 only pretty prints the results.
I hope this inspires you to write YOUR BEST eunit... :-)
Ladislav Lenart
More information about the erlang-questions
mailing list