博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Erlang学习: EUnit Testing for gen_fsm
阅读量:7098 次
发布时间:2019-06-28

本文共 13832 字,大约阅读时间需要 46 分钟。

背景:gen_fsm 是Erlang的有限状态机behavior,很实用。爱立信的一位TDD大神写了一篇怎样測试gen_fsm,这个fsm是一个交易系统,负责简单的交易员登陆,插入item,删除item等等,翻译例如以下:

1. Start and Stop

先看下最初版本号的tradepost_tests:

-module(tradepost_tests).-include_lib("eunit/include/eunit.hrl").% This is the main point of "entry" for my EUnit testing.% A generator which forces setup and cleanup for each test in the testsetmain_test_() ->    {foreach,     fun setup/0,     fun cleanup/1,     % Note that this must be a List of TestSet or Instantiator     % (I have instantiators == functions generating tests)     [      % First Iteration      fun started_properly/1,     ]}.% Setup and Cleanupsetup()      -> {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 thisstarted_properly(Pid) ->    fun() ->            ?

assertEqual(pending,tradepost:introspection_statename(Pid)), ?assertEqual([undefined,undefined,undefined,undefined,undefined], tradepost:introspection_loopdata(Pid)) end.

译者注:在eunit中。 setup返回的值作为全部函数包含cleanup的输入,这里是Pid。

started_properly函数是assert 初始为pending, State的值全为空。

如今Test 还不能run。由于tradepost:introspection_statename(Pid) 和 tradepost:introspection_loopdata(Pid)这两个函数还没有。

于是在tradepost.erl里增加:

introspection_statename(TradePost) ->    gen_fsm:sync_send_all_state_event(TradePost,which_statename).introspection_loopdata(TradePost) ->    gen_fsm:sync_send_all_state_event(TradePost,which_loopdata).stop(Pid) -> gen_fsm:sync_send_all_state_event(Pid,stop).handle_sync_event(which_statename, _From, StateName, LoopData) ->    {reply, StateName, StateName, LoopData};handle_sync_event(which_loopdata, _From, StateName, LoopData) ->    {reply,tl(tuple_to_list(LoopData)),StateName,LoopData};handle_sync_event(stop,_From,_StateName,LoopData) ->    {stop,normal,ok,LoopData}.
这样就能够run test 了

zen:EUnitFSM zenon$ erl -pa ebin/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:test(tradepost,[verbose]).======================== EUnit ========================module 'tradepost'  module 'tradepost_tests'    tradepost_tests: started_properly...ok    [done in 0.004 s]  [done in 0.005 s]=======================================================  Test passed.ok2>
2. 增加測试用例(identify_seller。 insert_item。 withdraw_item)

identify_seller seller是登陆函数。 insert_item。 withdraw_item是添加。删除item的函数

% This is the main point of "entry" for my EUnit testing.% A generator which forces setup and cleanup for each test in the testsetmain_test_() ->    {foreach,     fun setup/0,     fun cleanup/1,     % Note that this must be a List of TestSet or Instantiator     % (I have instantiators)     [      % First Iteration      fun started_properly/1,      % Second Iteration      fun identify_seller/1,      fun insert_item/1,      fun withdraw_item/1     ]}.% Now, we are adding the Seller API testsidentify_seller(Pid) ->    fun() ->            % From Pending, identify seller, then state should be pending            % loopdata should now contain seller_password            ?assertEqual(pending,tradepost:introspection_statename(Pid)),            ?assertEqual(ok,tradepost:seller_identify(Pid,seller_password)),            ?

assertEqual(pending,tradepost:introspection_statename(Pid)), ?

assertEqual([undefined,undefined,seller_password,undefined, undefined],tradepost:introspection_loopdata(Pid)) end. insert_item(Pid) -> fun() -> % From pending and identified seller, insert item % state should now be item_received, loopdata should now contain itm tradepost:introspection_statename(Pid), tradepost:seller_identify(Pid,seller_password), ?assertEqual(ok,tradepost:seller_insertitem(Pid,playstation, seller_password)), ?assertEqual(item_received,tradepost:introspection_statename(Pid)), ?assertEqual([playstation,undefined,seller_password,undefined, undefined],tradepost:introspection_loopdata(Pid)) end. withdraw_item(Pid) -> fun() -> % identified seller and inserted item, withdraw item % state should now be pending, loopdata should now contain only password tradepost:seller_identify(Pid,seller_password), tradepost:seller_insertitem(Pid,playstation,seller_password), ?assertEqual(ok,tradepost:withdraw_item(Pid,seller_password)), ?assertEqual(pending,tradepost:introspection_statename(Pid)), ?assertEqual([undefined,undefined,seller_password,undefined, undefined],tradepost:introspection_loopdata(Pid)) end.

在tradepost.erl添加对应的函数:

%%-------------------------------------------------------------------%%% @author Gianfranco 
%%% @copyright (C) 2010, Gianfranco%%% Created : 2 Sep 2010 by Gianfranco
%%%--------------------------------------------------------------------module(tradepost).-behaviour(gen_fsm).%% API-export([start_link/0,introspection_statename/1,introspection_loopdata/1, stop/1,seller_identify/2,seller_insertitem/3,withdraw_item/2]).%% States-export([pending/2,pending/3,item_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,time}).%%% APIstart_link() -> gen_fsm:start_link(?MODULE, [], []).introspection_statename(TradePost) -> gen_fsm:sync_send_all_state_event(TradePost,which_statename).introspection_loopdata(TradePost) -> gen_fsm:sync_send_all_state_event(TradePost,which_loopdata).stop(Pid) -> gen_fsm:sync_send_all_state_event(Pid,stop).seller_identify(TradePost,Password) -> gen_fsm:sync_send_event(TradePost,{identify_seller,Password}).seller_insertitem(TradePost,Item,Password) -> gen_fsm:sync_send_event(TradePost,{insert,Item,Password}).withdraw_item(TradePost,Password) -> gen_fsm:sync_send_event(TradePost,{withdraw,Password}).%%--------------------------------------------------------------------pending(_Event,LoopData) -> {next_state,pending,LoopData}.pending({identify_seller,Password},_Frm,LoopD = #state{seller=Password}) -> {reply,ok,pending,LoopD};pending({identify_seller,Password},_Frm,LoopD = #state{seller=undefined}) -> {reply,ok,pending,LoopD#state{seller=Password}};pending({identify_seller,_},_,LoopD) -> {reply,error,pending,LoopD};pending({insert,Item,Password},_Frm,LoopD = #state{seller=Password}) -> {reply,ok,item_received,LoopD#state{object=Item}};pending({insert,_,_},_Frm,LoopD) -> {reply,error,pending,LoopD}.item_received({withdraw,Password},_Frm,LoopD = #state{seller=Password}) -> {reply,ok,pending,LoopD#state{object=undefined}};item_received({withdraw,_},_Frm,LoopD) -> {reply,error,item_received,LoopD}.%%--------------------------------------------------------------------handle_sync_event(which_statename, _From, StateName, LoopData) -> {reply, StateName, StateName, LoopData};handle_sync_event(which_loopdata, _From, StateName, LoopData) -> {reply,tl(tuple_to_list(LoopData)),StateName,LoopData};handle_sync_event(stop,_From,_StateName,LoopData) -> {stop,normal,ok,LoopData};handle_sync_event(_E,_From,StateName,LoopData) -> {reply,ok,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}.
再run tests:

zen:EUnitFSM zenon$ erlc -o ebin/ src/*.erl test/*.erlzen: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...ok    tradepost_tests: identify_seller...ok    tradepost_tests: insert_item...ok    tradepost_tests: withdraw_item...ok    [done in 0.015 s]  [done in 0.015 s]=======================================================  All 4 tests passed.1>
3. 使用eunit_fsm

eunit_fsm是作者写的一个module,使gen_fsm的測试看起来更美观:

原来版本号:

started_properly(Pid) ->    fun() ->            ?

assertEqual(pending,tradepost:introspection_statename(Pid)), ?

assertEqual([undefined,undefined,undefined,undefined,undefined], tradepost:introspection_loopdata(Pid)) end.

新版本号:

started_properly(Pid) ->    {"Proper startup test",     [{statename,is,pending},      {loopdata,is,[undefined,undefined,undefined,undefined,undefined]}      ]}.
再看insert_item, 原来版本号:

insert_item(Pid) ->    fun() ->        % From pending and identified seller, insert item        % state should now be item_received, loopdata should now contain itm        tradepost:introspection_statename(Pid),        tradepost:seller_identify(Pid,seller_password),        ?assertEqual(ok,tradepost:seller_insertitem(Pid,playstation,                                              seller_password)),        ?assertEqual(item_received,tradepost:introspection_statename(Pid)),        ?

assertEqual([playstation,undefined,seller_password,undefined, undefined],tradepost:introspection_loopdata(Pid)) end.

新版本号:

insert_item(Pid) ->    {"Insert Item Test",      [{state,is,pending},       {call,tradepost,seller_identify,[Pid,seller_password],ok},       {call,tradepost,seller_insertitem,[Pid,playstation,seller_password]},       {state,is,item_received},       {loopdata,is,[playstation,undefined,seller_password,undefined,undefined]}      ]}.
看起来更易读了吧!

来看下整个的tradepost_test.erl 

-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 testsetmain_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     ]}.% Setup and Cleanupsetup()      -> {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 thisstarted_properly(Pid) ->    ?fsm_test(tradepost,Pid,"Started Properly Test",      [{state,is,pending},       {loopdata,is,[undefined,undefined,undefined,undefined,undefined]}     ]).% Now, we are adding the Seller API testsidentify_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,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,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,undefined]} ]).

在这里我们看下作者自己写的 eunit_fsm.hrl 和  eunit_fsm.erl

 eunit_fsm.hrl :

-define(fsm_test(Id,Title,CmdList),  {Title,fun() -> [ eunit_fsm:translateCmd(Id,Cmd) || Cmd <- CmdList] end}).
eunit_fsm.erl:

-module(eunit_fsm).-export([translateCmd/2,get/2]).-define(Expr(X),??X).translateCmd(Id,{state,is,X}) ->    case get(Id,"StateName") of        X -> true;        _V ->  .erlang:error({statename_match_failed,                              [{module, ?MODULE},                               {line, ?

LINE}, {expected, X}, {value, _V}]}) end; translateCmd(_Id,{call,M,F,A,X}) -> case apply(M,F,A) of X -> ok; _V -> .erlang:error({function_call_match_failed, [{module, ?MODULE}, {line, ?LINE}, {expression, ?Expr(apply(M,F,A))}, {expected, X}, {value, _V}]}) end; translateCmd(Id,{loopdata,is,X}) -> case tl(tuple_to_list(get(Id,"StateData"))) of X -> true; _V -> .erlang:error({loopdata_match_failed, [{module, ?MODULE}, {line, ?LINE}, {expected, X}, {value, _V}]}) end. % StateName or StateData get(Id,Which) -> {status,_Pid,_ModTpl, List} = sys:get_status(Id), AllData = lists:flatten([ X || {data,X} <- lists:last(List) ]), proplists:get_value(Which,AllData).

看下如今的文件夹结构:

zen:EUnitFSM zenon$ tree ..├── ebin├── include│   └── eunit_fsm.hrl├── src│   └── tradepost.erl└── test    ├── eunit_fsm.erl    └── tradepost_tests.erl4 directories, 4 files
来编译后Run一下:

zen:EUnitFSM zenon$ erlc -o ebin/ src/*.erl test/*.erlzen: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    [done in 0.014 s]  [done in 0.014 s]=======================================================  All 4 tests passed.1>
全Pass。

转载地址:http://cjhql.baihongyu.com/

你可能感兴趣的文章
(转载)showModalDialog关闭子窗口刷新主窗口
查看>>
ACM HDU 1219 AC me(简单题,但是花了很长时间才AC)
查看>>
Ethernet LEDs
查看>>
row_number()over函数的使用(转)
查看>>
怎样在Delphi中屏蔽Flash控件的右键弹出菜单
查看>>
[BuildRelease]Mozilla Build Tools - Autoconf + GNU Make
查看>>
DRM-内容数据版权加密保护技术学习(上):视频文件打包实现(转)
查看>>
Html.ActionLink 几种重载方式说明及例子
查看>>
[转]spinlock 理解
查看>>
今天看腾讯在北航的演讲《1亿在线背后的技术挑战》想到的关于MD5算法。
查看>>
SQL高级---SQL Alias(别名)
查看>>
Android开发历程_5(Activity相对布局)
查看>>
OSI
查看>>
方法论、方法论——程序员的阿喀琉斯之踵 (转载)
查看>>
对架构师而言,什么最重要?
查看>>
ecshop foreach控制文章显示条数
查看>>
WPF Button添加图片
查看>>
TabHost的两种实现形式
查看>>
Sharepoint学习笔记—Site Definition系列-- 2、创建Content Type
查看>>
半DDD架构 1
查看>>