EUnit Testing gen_fsm – Part 2


Last post we saw the symbolic (somewhat DSL) for the gen_fsm testing, using that as help we shall continue with the Buyer API. Technically, we are now doing the fourth iteration.

Fourth Iteration – Buyer API

The buyer wishes to be identified like the seller, to deposit cash and to withdraw cash. Thus his/her usage is similar. By good TDD, we will write the tests first, adding them to tradepost_tests.erl

The new added instantiators

% This is the main point of "entry" for my EUnit testing.
% A generator which forces setup and cleanup for each test in the testset
main_test_() ->
    {foreach,
     fun setup/0,
     fun cleanup/1,
     % Note that this must be a List of TestSet or Instantiator
     [
      % First Iteration
      fun started_properly/1,
      % Second Iteration
      fun identify_seller/1,
      fun insert_item/1,
      fun withdraw_item/1,
      % Fourth iteration
      fun identify_buyer/1,
      fun insert_cash/1,
      fun withdraw_cash/1
     ]}.

And their implementations

identify_buyer(Pid) ->
    ?fsm_test(Pid,"Identify Buyer Test",
       [{state,is,pending},
        {call,tradepost,buyer_identify,[Pid,buyer_password],ok},
        {state,is,pending},
        {loopdata,is,[undefined,undefined,undefined,buyer_password,undefined]}
      ]).

insert_cash(Pid) ->
    ?fsm_test(Pid,"Insert Cash Test",
       [{state,is,pending},
        {call,tradepost,buyer_identify,[Pid,buyer_password],ok},
        {call,tradepost,buyer_insertcash,[Pid,100,buyer_password],ok},
        {state,is,cash_received},
        {loopdata,is,[undefined,100,undefined,buyer_password,undefined]}
       ]).

withdraw_cash(Pid) ->
    ?fsm_test(Pid,"Withdraw Cash Test",
       [{state,is,pending},
        {call,tradepost,buyer_identify,[Pid,buyer_password],ok},
        {call,tradepost,buyer_insertcash,[Pid,100,buyer_password],ok},
        {call,tradepost,buyer_withdrawcash,[Pid,buyer_password],ok},
        {loopdata,is,[undefined,undefined,undefined,buyer_password,undefined]}
       ]).

The changes in tradepost.erl

%% API
-export([start_link/0,stop/1,seller_identify/2,seller_insertitem/3,
         seller_withdraw_item/2,buyer_identify/2,buyer_insertcash/3,
         buyer_withdrawcash/2]).
%% States
-export([pending/2,pending/3,item_received/3,cash_received/3]).

buyer_identify(TradePost,Password) ->
    gen_fsm:sync_send_event(TradePost,{identify_buyer,Password}).
buyer_insertcash(TradePost,Amount,Password) ->
    gen_fsm:sync_send_event(TradePost,{insert_cash,Amount,Password}).
buyer_withdrawcash(TradePost,Password) ->
    gen_fsm:sync_send_event(TradePost,{withdraw_cash,Password}).    

pending({identify_buyer,Password},_Frm,LoopD = #state{buyer=Password}) ->
    {reply,ok,pending,LoopD};
pending({identify_buyer,Password},_Frm,LoopD = #state{buyer=undefined}) ->
    {reply,ok,pending,LoopD#state{buyer=Password}};
pending({identify_buyer,_},_,LoopD) ->
    {reply,error,pending,LoopD};

pending({insert_cash,Amount,Password},_Frm,LoopD = #state{buyer=Password}) ->
    {reply,ok,cash_received,LoopD#state{cash=Amount}};
pending({insert_cash,_,_},_Frm,LoopD) ->
    {reply,error,pending,LoopD}.

cash_received({withdraw_cash,Password},_From,LoopD = #state{buyer=Password}) ->
    {reply,ok,pending,LoopD#state{cash=undefined}};
cash_received({withdraw_cash,_},_From,LoopD) ->
    {reply,error,cas_received,LoopD}.

Compiling and running

zen:EUnitFSM zenon$ erlc -o ebin/ src/*.erl test/*.erl
zen:EUnitFSM zenon$ erl -pa ebin/ -eval 'eunit:test(tradepost,[verbose]).'
Erlang R13B04 (erts-5.7.5) [source] [64-bit] [smp:4:4] [rq:4]
[async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.5  (abort with ^G)
1> ======================== EUnit ========================
module 'tradepost'
  module 'tradepost_tests'
    tradepost_tests: started_properly (Started Properly Test)...ok
    tradepost_tests: identify_seller (Identify Seller Test)...ok
    tradepost_tests: insert_item (Insert Item Test)...[0.001 s] ok
    tradepost_tests: withdraw_item (Withdraw Item Test)...[0.001 s] ok
    tradepost_tests: identify_buyer (Identify Buyer Test)...ok
    tradepost_tests: insert_cash (Insert Cash Test)...[0.001 s] ok
    tradepost_tests: withdraw_cash (Withdraw Cash Test)...ok
    [done in 0.024 s]
  [done in 0.024 s]
=======================================================
  All 7 tests passed.

1>

Wow. Great. So the buyer and the seller can now deposit and retract their respective parts. Awesome. However, there are some intentionally left out parts (and yes, I assume more than one of you has been thinking and cringing about it) - the interleaving of the actions. That is left for the fifth iteration.

Fifth Iteration - Interleaving of Actions

Much straight forward  - what if the buyer identifies and inserts the item, and the buyer wishes to identify after this?

Why, let's write a test scenario for it (and of course it will fail).

interleaving1(Pid) ->
    ?fsm_test(Pid,"Seller Identifies, Inserts Item, then Buyer Identiefies",
              [{state,is,pending},
               {call,tradepost,seller_identify,[Pid,a],ok},
               {call,tradepost,seller_insertitem,[Pid,ring,a],ok},
               {call,tradepost,buyer_identify,[Pid,b],ok}
              ]).

And just as we knew, it would blow

zen:EUnitFSM zenon$ erlc -o ebin/ src/*.erl test/*.erl
zen:EUnitFSM zenon$ erl -pa ebin/ -eval 'eunit:test(tradepost,[verbose]).'
Erlang R13B04 (erts-5.7.5) [source] [64-bit] [smp:4:4] [rq:4]
[async-threads:0] [hipe] [kernel-poll:false]Eshell V5.7.5  (abort with ^G)
1> ======================== EUnit ========================
module 'tradepost'
  module 'tradepost_tests'
    tradepost_tests: started_properly (Started Properly Test)...[0.001 s] ok
    tradepost_tests: identify_seller (Identify Seller Test)...ok
    tradepost_tests: insert_item (Insert Item Test)...ok
    tradepost_tests: withdraw_item (Withdraw Item Test)...ok
    tradepost_tests: identify_buyer (Identify Buyer Test)...ok
    tradepost_tests: insert_cash (Insert Cash Test)...ok
    tradepost_tests: withdraw_cash (Withdraw Cash Test)...ok
    tradepost_tests: interleaving1 (Seller Identifies, Inserts Item, then Buyer Identiefies)...*skipped*
undefined
*unexpected termination of test process*
::{function_clause,[{tradepost,item_received,
                               [{identify_buyer,b},
                                {<0.69.0>,#Ref<0.0.0.113>},
                                {state,ring,undefined,a,undefined,...}]},
                    {gen_fsm,handle_msg,7},
                    {proc_lib,init_p_do_apply,3}]}

The issue is of course that it is not possible to identify oneself in any other state than the pending one. Do we consider this a flaw or as part of the system design?  For this example, we shall regard it to be a flaw. And the true design should be that either buyer or seller must be able to identify themselves once before inserting their part and closing the deal.

Let's add some more tests that we know should pass, like reversing the roles, and adding more interleaving of actions. As will be seen, this causes a lot of code, and we discover the need for some Automated Test Generation.

interleaving1(Pid) ->
    ?fsm_test(Pid,"Seller Identifies, Inserts Item, then Buyer Identiefies",
              [{state,is,pending},
               {call,tradepost,seller_identify,[Pid,s],ok},
               {call,tradepost,seller_insertitem,[Pid,ring,s],ok},
               {call,tradepost,buyer_identify,[Pid,b],ok},
               {loopdata,is,[ring,undefined,s,b]}
              ]).

interleaving2(Pid) ->
    ?fsm_test(Pid,"Buyer Identifies, Inserts Item, then Seller Identiefies",
              [{state,is,pending},
               {call,tradepost,buyer_identify,[Pid,b],ok},
               {call,tradepost,buyer_insertcash,[Pid,100,b],ok},
               {call,tradepost,seller_identify,[Pid,s],ok},
               {loopdata,is,[undefined,100,s,b]}
              ]).

interleaving3(Pid) ->
    ?fsm_test(Pid,"Buyer Identifies, Seller Identifies, buyer inserts cash",
              [{state,is,pending},
               {call,tradepost,buyer_identify,[Pid,b],ok},
               {call,tradepost,seller_identify,[Pid,s],ok},
               {call,tradepost,buyer_insertcash,[Pid,100,b],ok},
               {loopdata,is,[undefined,100,s,b]}
              ]).

interleaving4(Pid) ->
    ?fsm_test(Pid,"Seller Identifies, Buyer Identifies, Seller inserts item",
              [{state,is,pending},
               {call,tradepost,seller_identify,[Pid,s],ok},
               {call,tradepost,buyer_identify,[Pid,b],ok},
               {call,tradepost,seller_insertitem,[Pid,ring,s],ok},
               {loopdata,is,[ring,undefined,s,b]}
              ]).

interleaving5(Pid) ->
    ?fsm_test(Pid,"Seller Identifies, Seller inserts item, Buyer Identifies,"
              "Buyer Inserts Cash",
              [{state,is,pending},
               {call,tradepost,seller_identify,[Pid,s],ok},
               {call,tradepost,seller_insertitem,[Pid,ring,s],ok},
               {call,tradepost,buyer_identify,[Pid,b],ok},
               {call,tradepost,buyer_insertcash,[Pid,100,b],ok},
               {loopdata,is,[ring,100,s,b]}
              ]).

These are not all the tests (some 6 more are hidden). For the future (for some future iteration), we would like to specify which state transitions are legal, which functions cause these transitions, and ultimately let the machine generate them for us, run the sequences and test whether all is good.

Also, while failing, it would be tremenduously nice if the automatic test generation would show us a trace of the failing run.

Fixing up the problem with identification, a lot of the tests run through, however a new problem is evident.

zen:EUnitFSM zenon$ erlc -o ebin/ src/*.erl test/*.erl
zen:EUnitFSM zenon$ erl -pa ebin/ -eval 'eunit:test(tradepost,[verbose]).'
Erlang R13B04 (erts-5.7.5) [source] [64-bit] [smp:4:4] [rq:4]
[async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.5  (abort with ^G)
1> ======================== EUnit ========================
module 'tradepost'
  module 'tradepost_tests'
    tradepost_tests: started_properly (Started Properly Test)...ok
    tradepost_tests: identify_seller (Identify Seller Test)...ok
    tradepost_tests: insert_item (Insert Item Test)...ok
    tradepost_tests: withdraw_item (Withdraw Item Test)...ok
    tradepost_tests: identify_buyer (Identify Buyer Test)...ok
    tradepost_tests: insert_cash (Insert Cash Test)...ok
    tradepost_tests: withdraw_cash (Withdraw Cash Test)...ok
    tradepost_tests: interleaving1 (Seller Identifies, Inserts Item, then Buyer Identiefies)...ok
    tradepost_tests: interleaving2 (Buyer Identifies, Inserts Item, then Seller Identiefies)...ok
    tradepost_tests: interleaving3 (Buyer Identifies, Seller Identifies, buyer inserts cash)...ok
    tradepost_tests: interleaving4 (Seller Identifies, Buyer Identifies, Seller inserts item)...ok
    tradepost_tests: interleaving5 (Seller Identifies, Seller inserts item, Buyer Identifies,Buyer Inserts Cash)...*skipped*
undefined
*unexpected termination of test process*
::{function_clause,[{tradepost,item_received,
                               [{insert_cash,100,a},
                                {<0.85.0>,#Ref<0.0.0.153>},
                                {state,ring,undefined,a,a,...}]},
                    {gen_fsm,handle_msg,7},
                    {proc_lib,init_p_do_apply,3}]}

=ERROR REPORT==== 5-Sep-2010::22:33:23 ===
** State machine <0.83.0> terminating
** Last message in was {'$gen_sync_event',
                           {<0.85.0>,#Ref<0.0.0.153>},
                           {insert_cash,100,a}}
** When State == item_received
**      Data  == {state,ring,undefined,a,a,undefined}
** Reason for termination =
** {function_clause,[{tradepost,item_received,
                                [{insert_cash,100,a},
                                 {<0.85.0>,#Ref<0.0.0.153>},
                                 {state,ring,undefined,a,a,undefined}]},
                     {gen_fsm,handle_msg,7},
                     {proc_lib,init_p_do_apply,3}]}
=======================================================
  Failed: 0.  Skipped: 0.  Passed: 11.
One or more tests were cancelled.

1>

It is not possible to insert the cash after the item has been inserted!(?) Clearly, there is an interleaving problem between item insertion and cash insertion. The true design  should be that either buyer or seller must be able to insert xor withdraw their item / cash before closing the deal, irrespective of the other parts item / cash.

An interesting thing to note is that we got this failing tests because this test was longer. It triggered more transitions, and was in a sense, more complex. This is another thing we wish to get for free from an automated test generating engine.

Wishing to test what we just discussed, we add a longer test that should serve as a green light once it goes through.

interleaving6(Pid) ->
    ?fsm_test(Pid,"Seller Identifies, Seller inserts item, Buyer Identifies,"
              "Seller Withdraws Item, Buyer Inserts Cash, Seller Inserts Item"
              "Buyer Withdraws Cash, Seller Withdraws Item"
              [{state,is,pending},
               {call,tradepost,seller_identify,[Pid,s],ok},
               {call,tradepost,seller_insertitem,[Pid,ring,s],ok},
               {call,tradepost,buyer_identify,[Pid,b],ok},
               {call,tradepost,seller_withdraw_item,[Pid,s],ok},
               {call,tradepost,buyer_insertcash,[Pid,100,b],ok},
               {call,tradepost,seller_insertitem[Pid,ring,s],ok},
               {call,tradepost,buyer_withdrawcash,[Pid,b],ok},
               {call,tradepost,seller_withdraw_item,[Pid,s],ok},
               {loopdata,is,[undefined,undefined,s,b]}
              ]).

A little later ...

zen:EUnitFSM zenon$ erlc -o ebin/ src/*.erl test/*.erl
zen:EUnitFSM zenon$ erl -pa ebin/ -eval 'eunit:test(tradepost,[verbose]).'
Erlang R13B04 (erts-5.7.5) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.5  (abort with ^G)
1> ======================== EUnit ========================
module 'tradepost'
  module 'tradepost_tests'
    tradepost_tests: started_properly (Started Properly Test)...ok
    tradepost_tests: identify_seller (Identify Seller Test)...ok
    tradepost_tests: insert_item (Insert Item Test)...[0.001 s] ok
    tradepost_tests: withdraw_item (Withdraw Item Test)...[0.001 s] ok
    tradepost_tests: identify_buyer (Identify Buyer Test)...[0.001 s] ok
    tradepost_tests: insert_cash (Insert Cash Test)...[0.001 s] ok
    tradepost_tests: withdraw_cash (Withdraw Cash Test)...[0.001 s] ok
    tradepost_tests: interleaving1 (Seller Identifies, Inserts Item, then Buyer Identiefies)...ok
    tradepost_tests: interleaving2 (Buyer Identifies, Inserts Item, then Seller Identiefies)...ok
    tradepost_tests: interleaving3 (Buyer Identifies, Seller Identifies, buyer inserts cash)...[0.001 s] ok
    tradepost_tests: interleaving4 (Seller Identifies, Buyer Identifies, Seller inserts item)...ok
    tradepost_tests: interleaving5 (Seller Identifies, Seller inserts item, Buyer Identifies,Buyer Inserts Cash)...[0.001 s] ok
    tradepost_tests: interleaving6 (Seller Identifies, Seller inserts item, Buyer Identifies,Seller Withdraws Item, Buyer Inserts Cash, Seller Inserts ItemBuyer Withdraws Cash, Seller Withdraws Item)...[0.001 s] ok
    [done in 0.041 s]
  [done in 0.041 s]
=======================================================
  All 13 tests passed.

1>

That done, the code of tradepost.erl is now

%%%-------------------------------------------------------------------
%%% @author Gianfranco <zenon@zen.home>
%%% @copyright (C) 2010, Gianfranco
%%% Created :  2 Sep 2010 by Gianfranco <zenon@zen.home>
%%%-------------------------------------------------------------------
-module(tradepost).
-behaviour(gen_fsm).

%% API
-export([start_link/0,stop/1,seller_identify/2,seller_insertitem/3,
         seller_withdraw_item/2,buyer_identify/2,buyer_insertcash/3,
         buyer_withdrawcash/2]).

%% States
-export([pending/3,item_received/3,cash_received/3]).

%% gen_fsm callbacks
-export([init/1, handle_event/3, handle_sync_event/4, handle_info/3,
         terminate/3, code_change/4]).
-record(state, {object,cash,seller,buyer}).

%%%===================================================================
%%% API
start_link() -> gen_fsm:start_link(?MODULE, [], []).

stop(Pid) -> gen_fsm:sync_send_all_state_event(Pid,stop).

seller_identify(TradePost,Password) ->
    gen_fsm:sync_send_all_state_event(TradePost,{identify_seller,Password}).
seller_insertitem(TradePost,Item,Password) ->
    gen_fsm:sync_send_event(TradePost,{insert,Item,Password}).
seller_withdraw_item(TradePost,Password) ->
    gen_fsm:sync_send_event(TradePost,{withdraw,Password}).

buyer_identify(TradePost,Password) ->
    gen_fsm:sync_send_all_state_event(TradePost,{identify_buyer,Password}).
buyer_insertcash(TradePost,Amount,Password) ->
    gen_fsm:sync_send_event(TradePost,{insert_cash,Amount,Password}).
buyer_withdrawcash(TradePost,Password) ->
    gen_fsm:sync_send_event(TradePost,{withdraw_cash,Password}).    

%%--------------------------------------------------------------------
pending({insert,Item,Password},_Frm,LoopD = #state{seller=Password}) ->
    {reply,ok,item_received,LoopD#state{object=Item}};
pending({insert_cash,Amount,Password},_Frm,LoopD = #state{buyer=Password}) ->
    {reply,ok,cash_received,LoopD#state{cash=Amount}};
pending(_E,_From,LoopD) ->
    {reply,error,pending,LoopD}.

item_received({withdraw,Password},_Frm,LoopD = #state{seller=Password}) ->
    NewState = case LoopD#state.cash of
                   undefined -> pending;
                   _ -> cash_received
               end,
    {reply,ok,NewState,LoopD#state{object=undefined}};
item_received({withdraw_cash,Password},_Frm,LoopD = #state{buyer=Password}) ->
    NewState = case LoopD#state.object of
                   undefined -> pending;
                   _ -> item_received
               end,
    {reply,ok,NewState,LoopD#state{cash=undefined}};
item_received({insert_cash,Amount,Password},_Frm,LoopD = #state{buyer=Password})->
    {reply,ok,cash_received,LoopD#state{cash=Amount}};
item_received({insert,Item,Password},_Frm,LoopD = #state{seller=Password})->
    {reply,ok,item_received,LoopD#state{object=Item}};
item_received(_E,_From,LoopD) ->
    {reply,error,item_received,LoopD}.

cash_received({withdraw_cash,Password},_From,LoopD = #state{buyer=Password}) ->
    NewState = case LoopD#state.object of
                   undefined -> pending;
                   _ -> item_received
               end,
    {reply,ok,NewState,LoopD#state{cash=undefined}};
cash_received({withdraw,Password},_Frm,LoopD = #state{seller=Password}) ->
    NewState = case LoopD#state.cash of
                   undefined -> pending;
                   _ -> cash_received
               end,
    {reply,ok,NewState,LoopD#state{object=undefined}};
cash_received({insert_cash,Amount,Password},_Frm,LoopD = #state{buyer=Password})->
    {reply,ok,cash_received,LoopD#state{cash=Amount}};
cash_received({insert,Item,Password},_Frm,LoopD = #state{seller=Password})->
    {reply,ok,item_received,LoopD#state{object=Item}};
cash_received(_E,_From,LoopD) ->
    {reply,error,cash_received,LoopD}.

%%--------------------------------------------------------------------
handle_sync_event({identify_seller,Pass},_From,StateName,
                  LoopData=#state{seller=undefined}) ->
    {reply,ok,StateName,LoopData#state{seller=Pass}};
handle_sync_event({identify_buyer,Pass},_From,StateName,
                  LoopData=#state{buyer=undefined}) ->
    {reply,ok,StateName,LoopData#state{buyer=Pass}};
handle_sync_event(stop,_From,_StateName,LoopData) ->
    {stop,normal,ok,LoopData};
handle_sync_event(_E,_From,StateName,LoopData) ->
    {reply,error,StateName,LoopData}.

%%--------------------------------------------------------------------
init([]) -> {ok, pending, #state{}}.
handle_event(_Event, StateName, State) ->{next_state, StateName, State}.
handle_info(_Info, StateName, State) -> {next_state, StateName, State}.
terminate(_Reason, _StateName, _State) -> ok.
code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}.

And the time has inevitably come for the deal closure API (next up - in the third part), and for your convenience, the full tradepost_tests.erl

%%% @author Gianfranco <zenon@zen.home>
%%% @copyright (C) 2010, Gianfranco
%%% Created :  6 Sep 2010 by Gianfranco <zenon@zen.home>
-module(tradepost_tests).
-include_lib("eunit/include/eunit.hrl").
-include("include/eunit_fsm.hrl").

% This is the main point of "entry" for my EUnit testing.
% A generator which forces setup and cleanup for each test in the testset
main_test_() ->
    {foreach,
     fun setup/0,
     fun cleanup/1,
     % Note that this must be a List of TestSet or Instantiator
     [
      % First Iteration
      fun started_properly/1,
      % Second Iteration
      fun identify_seller/1,
      fun insert_item/1,
      fun withdraw_item/1,
      % Fourth iteration
      fun identify_buyer/1,
      fun insert_cash/1,
      fun withdraw_cash/1,
      % Fifth iteration
      fun interleaving1/1,
      fun interleaving2/1,
      fun interleaving3/1,
      fun interleaving4/1,
      fun interleaving5/1,
      fun interleaving6/1
     ]}.

% Setup and Cleanup
setup()      -> {ok,Pid} = tradepost:start_link(), Pid.
cleanup(Pid) -> tradepost:stop(Pid).

% Pure tests below
% ------------------------------------------------------------------------------
% Let's start simple, I want it to start and check that it is okay.
% I will use the introspective function for this
started_properly(Pid) ->
    ?fsm_test(Pid,"Started Properly Test",
              [{state,is,pending},
               {loopdata,is,[undefined,undefined,undefined,undefined]}
              ]).

% Now, we are adding the Seller API tests
identify_seller(Pid) ->
    ?fsm_test(Pid,"Identify Seller Test",
              [{state,is,pending},
               {call,tradepost,seller_identify,[Pid,seller_password],ok},
               {state,is,pending},
               {loopdata,is,[undefined,undefined,seller_password,undefined]}
              ]).

insert_item(Pid) ->
    ?fsm_test(Pid,"Insert Item Test",
              [{state,is,pending},
               {call,tradepost,seller_identify,[Pid,seller_password],ok},
               {call,tradepost,seller_insertitem,[Pid,playstation,seller_password],ok},
               {state,is,item_received},
               {loopdata,is,[playstation,undefined,seller_password,undefined]}
              ]).

withdraw_item(Pid) ->
    ?fsm_test(Pid,"Withdraw Item Test",
              [{state,is,pending},
               {call,tradepost,seller_identify,[Pid,seller_password],ok},
               {call,tradepost,seller_insertitem,[Pid,button,seller_password],ok},
               {state,is,item_received},
               {call,tradepost,seller_withdraw_item,[Pid,seller_password],ok},
               {state,is,pending},
               {loopdata,is,[undefined,undefined,seller_password,undefined]}
              ]).

identify_buyer(Pid) ->
    ?fsm_test(Pid,"Identify Buyer Test",
              [{state,is,pending},
               {call,tradepost,buyer_identify,[Pid,buyer_password],ok},
               {state,is,pending},
               {loopdata,is,[undefined,undefined,undefined,buyer_password]}
              ]).

insert_cash(Pid) ->
    ?fsm_test(Pid,"Insert Cash Test",
              [{state,is,pending},
               {call,tradepost,buyer_identify,[Pid,buyer_password],ok},
               {call,tradepost,buyer_insertcash,[Pid,100,buyer_password],ok},
               {state,is,cash_received},
               {loopdata,is,[undefined,100,undefined,buyer_password]}
              ]).

withdraw_cash(Pid) ->
    ?fsm_test(Pid,"Withdraw Cash Test",
              [{state,is,pending},
               {call,tradepost,buyer_identify,[Pid,buyer_password],ok},
               {call,tradepost,buyer_insertcash,[Pid,100,buyer_password],ok},
               {call,tradepost,buyer_withdrawcash,[Pid,buyer_password],ok},
               {loopdata,is,[undefined,undefined,undefined,buyer_password]}
              ]).

interleaving1(Pid) ->
    ?fsm_test(Pid,"Seller Identifies, Inserts Item, then Buyer Identiefies",
              [{state,is,pending},
               {call,tradepost,seller_identify,[Pid,s],ok},
               {call,tradepost,seller_insertitem,[Pid,ring,s],ok},
               {call,tradepost,buyer_identify,[Pid,b],ok},
               {loopdata,is,[ring,undefined,s,b]}
              ]).

interleaving2(Pid) ->
    ?fsm_test(Pid,"Buyer Identifies, Inserts Item, then Seller Identiefies",
              [{state,is,pending},
               {call,tradepost,buyer_identify,[Pid,b],ok},
               {call,tradepost,buyer_insertcash,[Pid,100,b],ok},
               {call,tradepost,seller_identify,[Pid,s],ok},
               {loopdata,is,[undefined,100,s,b]}
              ]).

interleaving3(Pid) ->
    ?fsm_test(Pid,"Buyer Identifies, Seller Identifies, buyer inserts cash",
              [{state,is,pending},
               {call,tradepost,buyer_identify,[Pid,b],ok},
               {call,tradepost,seller_identify,[Pid,s],ok},
               {call,tradepost,buyer_insertcash,[Pid,100,b],ok},
               {loopdata,is,[undefined,100,s,b]}
              ]).

interleaving4(Pid) ->
    ?fsm_test(Pid,"Seller Identifies, Buyer Identifies, Seller inserts item",
              [{state,is,pending},
               {call,tradepost,seller_identify,[Pid,s],ok},
               {call,tradepost,buyer_identify,[Pid,b],ok},
               {call,tradepost,seller_insertitem,[Pid,ring,s],ok},
               {loopdata,is,[ring,undefined,s,b]}
              ]).

interleaving5(Pid) ->
    ?fsm_test(Pid,"Seller Identifies, Seller inserts item, Buyer Identifies,"
              "Buyer Inserts Cash",
              [{state,is,pending},
               {call,tradepost,seller_identify,[Pid,s],ok},
               {call,tradepost,seller_insertitem,[Pid,ring,s],ok},
               {call,tradepost,buyer_identify,[Pid,b],ok},
               {call,tradepost,buyer_insertcash,[Pid,100,b],ok},
               {loopdata,is,[ring,100,s,b]}
              ]).

interleaving6(Pid) ->
    ?fsm_test(Pid,"Seller Identifies, Seller inserts item, Buyer Identifies,"
              "Seller Withdraws Item, Buyer Inserts Cash, Seller Inserts Item"
              "Buyer Withdraws Cash, Seller Withdraws Item",
              [{state,is,pending},
               {call,tradepost,seller_identify,[Pid,s],ok},
               {call,tradepost,seller_insertitem,[Pid,ring,s],ok},
               {call,tradepost,buyer_identify,[Pid,b],ok},
               {call,tradepost,seller_withdraw_item,[Pid,s],ok},
               {call,tradepost,buyer_insertcash,[Pid,100,b],ok},
               {call,tradepost,seller_insertitem,[Pid,ring,s],ok},
               {call,tradepost,buyer_withdrawcash,[Pid,b],ok},
               {call,tradepost,seller_withdraw_item,[Pid,s],ok},
               {loopdata,is,[undefined,undefined,s,b]}
              ]).

About these ads
Leave a comment

2 Comments

  1. Given only the steps that are stated in the blog, the Fourth Iterations tests do not pass as shown… You get this for the output:

    ======================== EUnit ========================
    module ‘tradepost’
    module ‘tradepost_tests’
    tradepost_tests: started_properly (Started Properly Test)…[0.001 s] ok
    tradepost_tests: identify_seller (Identify Seller Test)…[0.001 s] ok
    tradepost_tests: insert_item (Insert Item Test)…*skipped*
    undefined
    *unexpected termination of test process*
    ::{function_clause,
    [{tradepost,pending,
    [{insert,playstation,seller_password},
    {,#Ref},
    {state,undefined,undefined,seller_password,undefined,...}],
    [{file,"src/tradepost.erl"},{line,70}]},
    {gen_fsm,handle_msg,7,[{file,"gen_fsm.erl"},{line,494}]},
    {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,227}]}]}

    =ERROR REPORT==== 20-Jun-2013::13:54:14 ===
    ** State machine terminating
    ** Last message in was {‘$gen_sync_event’,
    {,#Ref},
    {insert,playstation,seller_password}}
    ** When State == pending
    ** Data == {state,undefined,undefined,seller_password,undefined,
    undefined}
    ** Reason for termination =
    ** {function_clause,
    [{tradepost,pending,
    [{insert,playstation,seller_password},
    {,#Ref},
    {state,undefined,undefined,seller_password,undefined,undefined}],
    [{file,"src/tradepost.erl"},{line,70}]},
    {gen_fsm,handle_msg,7,[{file,"gen_fsm.erl"},{line,494}]},
    {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,227}]}]}
    =======================================================
    Failed: 0. Skipped: 0. Passed: 2.
    One or more tests were cancelled.

    You’re testing for a function with a pattern match clause in the Fourth Iteration that you don’t add until the Fifth Iteration…

    pending({insert,Item,Password},_Frm,LoopD = #state{seller=Password}) ->
    {reply,ok,item_received,LoopD#state{object=Item}};

    So if you add this, you get past this failure… but then you get this test failing:

    tradepost_tests: withdraw_item (Withdraw Item Test)…*failed*
    in function tradepost:withdraw_item/2
    called as withdraw_item(,seller_password)
    in call from eunit_fsm:translateCmd/2 (test/eunit_fsm.erl, line 14)
    in call from tradepost_tests:’-withdraw_item/1-lc$^0/1-0-’/2 (test/tradepost_tests.erl, line 58)
    **error:undef

    Which is better in Iteration 1, 2, 3 – we called `seller_withdraw_item/2` just `withdraw_item/2`. Luckily, if you’re sharp – you saw this in the -export() declarative.

    Now everything passed. But – there is a typo in one of the atoms from the Fourth Iteration that we didn’t catch since we didn’t test for it, it’s right here:

    cash_received({withdraw_cash,Password},_From,LoopD = #state{buyer=Password}) ->
    {reply,ok,pending,LoopD#state{cash=undefined}};
    cash_received({withdraw_cash,_},_From,LoopD) ->
    {reply,error,cas_received,LoopD}.

    in the second clause for cash_received/3, the state atom is ‘cas_received’, but not the correct StateName, ‘cash_received’.

    Let’s ROCK the TDD here… so add this test:

    withdraw_cash_fail(Pid) ->
    ?fsm_test(Pid,”Withdraw Cash Failure Test”,
    [{state,is,pending},
    {call,tradepost,buyer_identify,[Pid,buyer_password],ok},
    {call,tradepost,buyer_insertcash,[Pid,100,buyer_password],ok},
    {call,tradepost,buyer_withdrawcash,[Pid,space_monkey],error},
    {state,is,cash_received},
    {loopdata,is,[undefined,100,undefined,buyer_password,undefined]}
    ]).

    (don’t forget to add it in the test set generator – and don’t forget the comma after the last func in the generator that was there…)

    We should be back to the cash_received according to the implementation – I’d think we might really want to go to pending… but whatever – we missed a typo in the atom for the state transition and I’d reading this blog to help avoid that sort of bug…

    Now, that’s run the tests and “see it fail” …

    tradepost_tests: withdraw_cash_fail (Withdraw Cash Failure Test)…*failed*
    in function eunit_fsm:translateCmd/2 (test/eunit_fsm.erl, line 7)
    in call from tradepost_tests:’-withdraw_cash_fail/1-lc$^0/1-0-’/2 (test/tradepost_tests.erl, line 97)
    **error:{statename_match_failed,[{module,eunit_fsm},
    {line,9},
    {expected,cash_received},
    {value,cas_received}]}

    so we see it catch our typo… Awesome – the expected state is ‘cash_received’

    So we fix that typo and we’re solid.

    Fourth Iteration complete.

    Reply
  2. Sorry – I probably should have stuck those console output blocks and code in gist on GitHub…

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: