Erlang BDD

Posted by yrashk

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}]
Comments

Leave a response