[erlang-questions] Mock functions

Arnaud Bailly abailly@REDACTED
Tue Sep 5 10:20:51 CEST 2006


Dominic Williams <xpdoka@REDACTED> writes:

> In functional programming, the "delegation" would happen by 
> passing to the function another function that performs part 
> of the work. In this case, lambda is the ultimate mock! In 
> your test, you can provide the function under test with a 
> dummy fun, a lot more easily than you would write a mock.
>

That's the other option I was thinking of, but I feel this may rapidly 
grows unmanageable if you happen to use several "components" and
interfaces. Or maybe I could use front end functions that delegates
work to back end functions: The former with "data" parameters, the
latter with "behavior" parameter.

However, the message-passing apporch seems easier to implement and
cleaner. 

> I'd be happy to address a specific example if you have a 
> problem at hand...

Yes, I have one. To teach myself erlang and to stuby how FP can be
used in a MDE approach, I am implementing a small web application to
handle billing of customers account. No interesting feature in the
application itself but that's the first example tha sprung to my
mind. 

Clasically (at least in a Java EE setting), one has several layers:
 - a presentation layer that handles communication with client and
 ensures proper flow of operations of the application and input/output
 formatting
 - a business layer that offers functions to manipulate data passing
 back and forth from the presentation layer 
 - a persistence layer that stores and retrieve data from some storage

What I need is a way to abstract the persistence layer so that I can
"plug" different data backend. Nothing really new under the sun. My
post was motivated by the fact that, as I do test-driven development,
I want to write tests for the business layer and so I need a way to
plug a test  backend. 

Here is the code, in bad shape and really sketchy. As I said before, I
am just learning erlang and OTP.

% Description of record structures for billing system
-record(account , { id,               % unique identification for account
		    name,        % string identifier 
		    address, 
		    currency = euro
		   }). 

% accounts relationships: 
% account to projects: one to many
-record(account_project , { account_id ,   % identification of account
			    project_id     % id of related project
			   }).

% Account to contact: many-to-many
-record(account_contact, { account_id ,   
			   contact_id , 
			   role           
			   }).


% note: one-to-one relations are inlined
% structure representing a project
-record(project, {id,
		  name,
		  description, 
		  project_lead,  % contact id for project leader
		  account_id     % related account id. may not be undefined
		 }).


-record(project_contact, { project_id ,   
			   contact_id , 
			   role           % a string
			   }).


% structure representing a contact: a physical individual that is related
% to a project and account (commercial contact, technical,...)
-record(contact, {id,
		  name,
		  phones = [] ,   % a list of phone numbers
		  emails = []   % multiple emails per contact
		 }).


% a billable item
-record(item, {id,
	       description,
	       unit_price = 1,    % price per unit
	       quantity = 1,      % number of units
	       unit = days,       % base unit are days 
	       currency = euro,   
	       vat = 0.196,       % 
	       project_id         % may not be null
	      }).
	       

% a bill
-record(bill, { id,
		date,
		validated = false, 
		account_id
	       }).

 % bills relation to items: one to many
-record(bill_items, { bill_id,
		      item_id
		     }).

% currencies
-record(currency, { id,
		    date,          %  date for valution
		    base = euro,   % base: another currency's id
		    value = 1.0   
		   }).

% list of all records with type of table
model() -> [ {record_info(fields, account),set}, 
	  {record_info(fields, account_contact),bag}, 
	  {record_info(fields, account_project),bag}, 
	  {record_info(fields, project),set}, 
	  {record_info(fields, project_contact),bag}, 
	  {record_info(fields, contact),set}, 
	  {record_info(fields, item),set}, 
	  {record_info(fields, bill),set}, 
	  {record_info(fields, bill_items),bag}, 
	  {record_info(fields, currency),set}
	 ].

%%%-------------------------------------------------------------------
%%% File    : billing_ops.erl
%%% Author  : Arnaud Bailly <abailly@REDACTED>
%%% Description : Contains the business operations 
%%%
%%% Created :  4 Sep 2006 by Arnaud Bailly <abailly@REDACTED>
%%%-------------------------------------------------------------------
-module(billing_ops).
-author('abailly@REDACTED).
%% defines common backend operations
-import(db_ops).

-include("billing_model.hrl").

%% @doc Create a new account and returns its id.
account_create(Name,Address,Currency) ->
    % check currency exists
    case currency_lookup(#currency{id=Currency}) of
	{ok, X} when X > 0 ->
	    % create id and insert record
	    Id = next_id(account),
	    dbcreate(#account{id=Id,name=Name,address=Address,currency=Currency}),
	    {ok,Id};
	{ko,Reason} = Fail -> 
	    Fail 
    end.

%% @doc lookup an account with values matching the given
%% account tuple.
account_lookup_byname(Name) ->
    dblookup(#account{id = '_',name=Name,address='_',currency ='_'}).
    

account_lookup_by_contact_name(Name) ->
   % lookup contacts
    Contacts = dblookup(#contact{id='_',
			      name=Name,
			      phones='_',
			      emails='_' }),
						% lookup account_contact
		    Accts_Cts = lists:flatmap(fun (#contact{id=Id,name=_,phones=_,emails=_}) ->
						      dblookup(#account_contact{account_id = '_',
									     contact_id = Id,
									     role ='_'}) 
						      end,
						      Contacts),
		    lists:flatmap(fun (#account_contact{account_id = Id,
							     contact_id = _,
							     role = _ }) 
				      -> dblookup(#account{id = Id,name='_',address='_',currency='_'}) 
					 end,
					 Accts_Cts).


account_lookup_by_project_name(Name) ->
						% lookup projects
    Projects = dblookup(#project{id='_',
			      name=Name,
			      description='_',
			      project_lead='_',
			      account_id='_'}),
						% lookup account_contact
		    Accts = lists:flatmap(fun (#project{id=_,
							name=_,
							description=_,
							project_lead=_,
							account_id=Acct }) ->
						  dblookup(#account{id = Acct,
								 name='_',
								 address='_',
								 currency='_'}) 
						  end,
						  Projects).


		    
		   
	 
% module for handling database operations for 'billing' application
% provide:
%  - initialisation routines for the database, schema creation
%  - CRUD operations with some checking for each record
-module(billing_data).
-export([init_db/1]).

-include("billing_model.hrl").

%% @doc This function initializes the database schema. It expects 
%% mnesia system to have been started. 
%%  - initialize mnesia system on given nodes
%%  - create schema and tables
%% @param Nodes list of Nodes to initialize
init_db(Nodes) ->
    {ok,Tbls} = make_table(model(),Nodes,[]),
    mnesia:wait_for_tables(Tbls,0)
	.

% create a table for given record type in nodes
make_table([ { {RName, Flds }, Type} | T ], Nodes, Tbls) ->
    case mnesia:create_table(RName,
			[ {type, Type},
			  {ram_copies, Nodes},
			  {attributes, Flds }]) of
	{_,ok}           -> make_table(T, Nodes,[RName|Tbls]);
	{aborted,Reason} -> {error,Reason}
    end;

make_table([],_Nodes, Tbls) ->
    {ok,Tbls}.



Thanks for helping
-- 
OQube < software engineering \ génie logiciel >
Arnaud Bailly, Dr.
\web> http://www.oqube.com





More information about the erlang-questions mailing list