Monday, 5th January 2009.

Posted on Thursday, 9th October 2008 by charpi

In my previous post, I described a way to mock module by replacing
them. Today, I’ll post a version closer to other mock
library. Basically, I create on mock module on the fly and forward all
calls to a process. You can tell to this process how to answer for
each calls.
Here is the test :

%%% Copyright (c) 2008 Nicolas Charpentier
%%% All rights reserved.
-module(mock_test).

-export([test /0]).

test () ->
    lazy_way () ,
    dynamic_way (),
    ok.

lazy_way () ->
    "i'm the production code" = my_module: who(),
    mock: replace_module (my_module, my_mock_module),
    "I'm the mocked code" = my_module: who (),
    mock: uninstall (my_module),
    "i'm the production code" = my_module: who (),
    ok.

dynamic_way () ->
    "i'm the production code" = my_module: who (),
    {pong,host} = my_module: ping (host),

    mock: start (),
    mock: add_module(my_module),
    mock: set_answer (my_module, who, "I'm the mocked code"),
    "I'm the mocked code" = my_module: who (),
    mock: set_answer (my_module, who, "Oops did it wrong"),
    "Oops did it wrong" = my_module: who (),

    [{my_module, who, []}, {my_module, who, []}] =  mock: calls (),
    [] =  mock: calls (),

    case catch my_module: ping(host) of
        error_no_response -> ok;
        Other -> exit({unexepected_mock_response, Other})
    end,

    mock: set_answer (my_module, ping, {pang, mock_host}),
    {pang, mock_host} = my_module: ping(last_host),

    [{my_module, ping, [host]},
     {my_module, ping, [last_host]}] =  mock: calls (),

    mock: uninstall (my_module),
    "i'm the production code" = my_module: who (),
    {pong,host} = my_module: ping (host),
    ok.

and the implementation

%%% Copyright (c) 2008 Nicolas Charpentier
%%% All rights reserved.
-module(mock).

-export([replace_module /2]).
-export([uninstall /1]).

-export([start /0]).
-export([add_module /1]).
-export([set_answer /3]).
-export([calls /0]).

replace_module (Module, Mock_module) ->
    uninstall (Module),
    {ok, Binary} = file: read_file (code: which (Mock_module)),
    File_name = atom_to_list (Module) ++ ".erl",
    code: load_binary(Module, File_name, Binary),
    ok.

uninstall (Module) ->
    code: purge (Module),
    code: delete (Module).

start () ->
    Pid = spawn_link (fun () -> mocker ([],[]) end),
    register(mocker, Pid),
    ok.

add_module (Module) ->
    Forms = forms (Module),
    uninstall (Module),
    {ok, _, Binary} = compile: forms(Forms, [report]),
    code: load_binary (Module, "foo.erl", Binary),
    ok.

set_answer (Module, Function, Answer) ->
    mocker ! {set_answer, Module, Function, Answer},
    ok.

calls () ->
    mocker ! {self(), calls},
    receive
        {calls, Calls} ->
            Calls
    end.

forms (Module) ->
    Exported_functions = find_exported_functions (Module),
    Fun = fun (F) -> function_to_form (Module, F) end,
    Functions_forms = [Fun(F) || F <- Exported_functions],
    [{attribute,1,module,Module},
     {attribute,3,export,Exported_functions}] ++
        Functions_forms  ++
        [ {function,16,wait_response,0,
           [{clause,16,[],[],
             [{'receive',20,
               [{clause,20,
                 [{tuple,20,[{atom,20,response},{atom, 20, undefined}]}],
                 [],
                 [{call,20,
                   {atom,20,throw},
                   [{atom,20,error_no_response}]}]},
                {clause,20,
                 [{tuple,20,[{atom,20,response},{var,20,'Response'}]}],
                 [],
                 [{var,19,'Response'}]}
                ]}]}]},
          {eof,23}].

function_to_form (Module, {Function, Arity}) ->
    Parameters = parameters (Arity),
    Parameters_cons = parameters_cons (Arity),
    {function,5,Function,Arity,
     [{clause,5,Parameters,[],
       [{op,6,'!',
         {atom,6,mocker},
         {tuple,6,
          [{call,6,{atom,6,self},[]},
           {atom,6,forward},
           {atom,6,Module},
           {atom,6,Function},
           Parameters_cons]}},
        {call,8,{atom,8,wait_response},[]}
       ]}]}.

parameters (0) ->
    [];
parameters (N) ->
    Seq = lists: seq (1,N),
    F = fun (I) ->
                String = lists:flatten (io_lib: format ("Var~p",[I])),
                list_to_atom(String)
        end,
    [{var, 6, F(I)} || I <- Seq].

parameters_cons (N) ->
    parameter_list_form (parameters (N)).

put_parameter_in_call (Parameters) ->
    list_to_tuple (transform_list_to_cons (Parameters)).

transform_list_to_cons ([]) ->
    [nil, 6];
transform_list_to_cons ([H|T]) ->
    [cons, 6, H, put_parameter_in_call (T)].

parameter_list_form ([]) ->
    {nil,6};
parameter_list_form (Variable_forms) ->
    put_parameter_in_call (Variable_forms).

find_exported_functions (Module) ->
    Module_info = Module: module_info (),
    All_exported = proplists: get_value (exports, Module_info),
    lists: filter (fun ({module_info,_}) ->
                           false;
                       (_) ->
                           true
                   end, All_exported).

mocker (Modules, Calls) ->
    receive
        {From, calls} ->
            From ! {calls,lists: reverse (Calls)},
            mocker (Modules, []);
        {set_answer, Module, Function, Answer} ->
            New_modules = proplists: delete ({Module, Function}, Modules),
            mocker ([{{Module, Function}, Answer}|New_modules], Calls);
        {From, forward, Module, Function, Args} ->
            Response = proplists: get_value ({Module,Function}, Modules),
            From ! {response, Response},
            mocker (Modules, [{Module, Function, Args}|Calls])
    end.

Tags: ,
Posted in Uncategorized | Comments (4)

Posted on Wednesday, 8th October 2008 by charpi

In my previous post, I wrote a test to specify a lazy
mock. In my mind, a lazy mock consists to replace the implementation
of one module by another one at runtime.

Here is the implementation of the lazy mock test

-module(mock).

-export([replace_module /2]).

replace_module (Module, Mock_module) ->
    uninstall (Module),
    {ok, Binary} = file: read_file (code: which (Mock_module)),
    File_name = atom_to_list (Module) ++ ".erl",
    code: load_binary(Module, File_name, Binary),
    ok.

uninstall (Module) ->
    code: purge (Module),
    code: delete (Module).

The tricky thing is that your mock module must not be a valid
erlang beam file. The code_loader use the beam file name to locate the
code load but the VM expected that the module declaration in the file
got the same name than the file.
In order to use my implementation of the lazy mock your mock
module must have the same module declaration than the real
implementation.
Here is the content of the source file my_mock_module.erl:

-module(my_module).

-export([who /0]).

who () ->
    "I'm the mocked code".

Tags: ,
Posted in Uncategorized | Comments (0)

Posted on Friday, 3rd October 2008 by charpi

People coming from object oriented programming often use mock
object
in their unittest. The main question is do we need mock
when programming with erlang ?
.

The answer could be we don’t need any mock library because it’s
already part of the language
.

Let me explain a little more my vision of mock in Erlang.

The main purpose of the mock is to replace something
complicated by something very simple that you know the
behavior and that you can control. Using this mock, your tests will
only focus on one class module and forget all about the
environment of the complicated thing.
Using good unittest practices in OOP, you’ll pass all needed object in
the constructor of your classes, so you’ll be able to use mock in your
unittests.
In Erlang, we can’t use this technique because we don’t have
constructors however we can write good unittest.

  • Erlang is a concurrent programming language so we might have to
    test relationships between several processes. In order to test
    processes one by one, we can just spawn a small process in our tests
    and then pass its pid to the process under test. After that the
    only thing to do it to implement the message passing between
    processes. This technique is very close to the one used in OOP and
    their constructors.
  • Erlang is a functional programming language. One of the most
    interesting feature of FP is high order functions. It allows
    you to pass functions to your functions. Usually, high order functions
    are used to build generic algorithms but they can also be used to
    modify function behavior.
    Using fun to mock some behavior, oblige you to write some
    more tests:

    • Main function test with mock fun.
    • Production fun.
    • Real integration of the production fun in your application

Working on legacy code, it might be difficult to write tests. Of
course you can refactor to be able to use one of the previous case but
how to refactor without any tests ??. This observation make me realize
that sometimes we need something to intercept function calls.
The first technique I used was dbg. Adding traces on function
call, you can ensure that an expected side effect happened.
Anyway, if you need to setup a complete environment dbg
isn’t usable.
For those case a mock library could be useful. I have in mind
something that allow you to replace a module by another one.

There are the basic unit tests for this module

-module(mock_test).

-export([test /0]).

test () ->
    lazy_way () ,
    dynamic_way(),
    ok.

lazy_way () ->
    "i'm the production code" = my_module: who(),
    mock: replace_module (my_module, my_mock_module),
    "I'm the mocked code" = my_module: who (),
    mock: uninstall (my_module),
    "i'm the production code" = my_module: who(),
    ok.

dynamic_way () ->
    "i'm the production code" = my_module: who(),
    mock: start (),
    mock: add_module(my_module),
    mock: set_answer (my_module, who, "I'm the mocked code"),
    "I'm the mocked code" = my_module: who (),
    mock: set_answer (my_module, who, "Oops did it wrong"),
    "Oops did it wrong" = my_module: who (),

    [{my_module, who, []}, {my_module, who, []}] =  mock: calls (),
    mock: uninstall (my_module),
    "i'm the production code" = my_module: who(),
    ok.

Tags: ,
Posted in Uncategorized | Comments (1)

Posted on Sunday, 14th September 2008 by charpi

The summer wasn’t as productive as I would have liked. Anyway I took some time to think about my current professional life. Those thoughts gave me some conclusions:

  • I really need some method to organize myself. I bought Getting things done some time ago and my mission now is to read it and find a way to implement it.
  • My extra work need more visibility. It’s the reason why I started a Trac site and a GitHub repository. My expectations with this are double: job opportunities and increase the erlang footprint in France
  • My vision of agility need some clarifications

Tags: , ,
Posted in Uncategorized | Comments (0)

Posted on Sunday, 14th September 2008 by charpi

Since the beginning I use pyblosxom as my blogging system.One day I heard that my site was drab. As I’m not an expert in web design I searched a tools which can handle more things for me (especially nice themes). I decided to give a chance to WordPress due to its good reputation.
We’ll see if I made a good choice.

Tags: ,
Posted in Uncategorized | Comments (1)

-->
About me Downloads