[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