I’m definitely a BDD maniac. Recently I’ve created a quick-n-dirty Common Lisp BDD primitives and today I’ve hacked BDD primitives for Erlang. Just like Lisp version, it still lacks lots of BDD stuff, but only expectations. Here you are bdd.erl:
-module(bdd).
%%
%% Exported Functions
%%
-export([should/5,should_not/5,equal/2,be/2,raise/2,match/2,run/1]).
%%
%% Expectations
%%
should(Matcher,Args,Expr,File,Line) ->
case apply(?MODULE,Matcher,[Args,Expr]) of
false -> exit({expectation_not_met,{File,Line,Expr,should,Matcher,Args}});
_ -> ok
end.
should_not(Matcher,Args,Expr,File,Line) ->
case apply(?MODULE,Matcher,[Args,Expr]) of
true -> exit({expectation_not_met,{File,Line,Expr,should_not,Matcher,Args}});
_ -> ok
end.
%%
%% Matchers
%%
equal(Args,Expr) ->
case Expr of
Args -> true;
_ -> false
end.
be([H|T],Expr) ->
be(H,Expr,T);
be(Predicate, Expr) ->
be(Predicate,Expr, []).
be(Predicate,Expr,Args) ->
Pred = list_to_atom("is_" ++ atom_to_list(Predicate)),
case (catch apply(Pred,[Expr] ++ Args)) of
{'EXIT',_} ->
% is it a bif?
D = (catch apply(erlang,Pred,[Expr] ++ Args)),
case (catch apply(erlang,Pred,[Expr] ++ Args)) of
{'EXIT',_} ->
% last idea
apply(erlang,Predicate,[Expr] ++ Args);
Result -> Result
end;
Result -> Result
end.
raise(Args, Expr) ->
case Expr of
{'EXIT',{Args,_}} -> true;
{'EXIT',Args} -> true;
_ -> false
end.
match(Args, Expr) ->
not raise({badmatch,Expr},(catch Args=Expr)).
run([M|Modules]) ->
run(M) ++ run(Modules);
run([]) ->
[];
run(M) ->
Context = get_from_module(attributes,M,context),
io:format("~s~n",[Context]),
Fun = fun(Spec) ->
case Spec of
module_info -> skipped;
setup -> skipped;
teardown -> skipped;
context_setup -> skipped;
context_teardown -> skipped;
_ ->
(catch M:setup()),
io:format(" - ~s",[atom_to_list(Spec)]),
case (catch apply(M,Spec,[])) of
{'EXIT',{expectation_not_met,Value}} -> io:format(" (FAILED)~n",[]), {Spec,failed,Value};
{'EXIT',Value} -> io:format(" (ERROR)~n",[]), {Spec,error,Value};
_ -> (catch M:teardown()), io:format("~n",[]),{Spec,ok}
end
end
end,
(catch M:context_setup()),
R =lists:filter(fun(TestResult) -> not (TestResult =:= skipped) end, lists:map(fun(Item) -> hd(tuple_to_list(Item)) end,lists:keymap(Fun,1,M:module_info(exports)))),
(catch M:context_teardown()),
R.
get_from_module(Kind,Module,Name) ->
{value, {Name, Val}} = lists:keysearch(Name,1,Module:module_info(Kind)),
Val.
and bdd.hrl:
-define(bdd(Expr,Expectation,Matcher,Args),
apply(bdd,Expectation,[Matcher,Args,(catch Expr),?FILE,?LINE])).
Now you can check various expectations:
test() ->
?bdd(1,should,equal,1),
?bdd(1,should_not,equal,2),
?bdd(1,should,be,integer),
?bdd(1,should_not,be,list),
?bdd(1,should,be,['>',0]),
?bdd(1,should,be,['>=',1]),
?bdd(1=2,should,raise,{badmatch,2}),
?bdd(1=1,should_not,raise,{badmatch,2}),
?bdd({ok,1+2},should,match,{ok,3}),
?bdd({error,-1},should_not,match,{ok,3}),
?bdd(#state{},should,be,[record,state]).
and create contexts with specifications as modules:
-module(empty_list_spec).
-context("Empty list").
%%
%% Include files
%%
-include("bdd.hrl").
%%
%% Exported Functions
%%
-export([setup/0,'should have zero length'/0]).
setup() ->
put(empty_list,[]).
%%
%% Specifications
%%
'should have zero length'() ->
?bdd(length(get(empty_list)),should,equal,0).
%%
%% Local Functions
%%
Now just run it:
> bdd:run(empty_list_spec)
% or
> bdd:run([empty_list_spec])
Empty list
- should have zero length
[{'should have zero length',ok}]