Erlang学习: EUnit Testing for gen_fsm
背景: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 testset
main_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 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) ->
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.
ok
2>
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 testset
main_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 tests
identify_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 <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,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}). %%% API
start_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/*.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...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 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
]}. % 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(tradepost,Pid,"Started Properly Test",
[{state,is,pending},
{loopdata,is,[undefined,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,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.erl 4 directories, 4 files
来编译后Run一下:
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
[done in 0.014 s]
[done in 0.014 s]
=======================================================
All 4 tests passed. 1>
全Pass。
Erlang学习: EUnit Testing for gen_fsm的更多相关文章
- Erlang 学习笔记
http://wenku.baidu.com/link?url=AUQR8Hn-e-fEB_lqjXsd8XfapWj1qAK7J05JoBXFib_LlSk5qSOTia8HIxNV1XkeZi-k ...
- erlang四大behaviour之二-gen_fsm
来源:http://www.cnblogs.com/puputu/articles/1701012.html 今天介绍erlang的一个非常重要的behaviour,就是gen_fsm-有限状态机,有 ...
- erlang学习笔记(shell命令)
erlang shell 命令: help(). 可以查看erlang shell内置命令. 比如:m(Mod),可以查看模块Mod. 待续..
- Programming Erlang 学习笔记(一)
入门 启动Shell 在cmd中输入命令”erl”,百分号(%)表示一个注释的开始,从百分号开始到这行结束的所有文本都被看做是注释. 一个完整的命令需要以一个句点和一个回车结束. 退出erlang的命 ...
- erlang学习笔记之基础语法
字符串是双引号,单引号的是atom元组: 下标从1开始 X = {'test1',2,3,4}. element(1,X). 配合模式匹配,可以给元素项命名,直接不用下标标记元素项 列表增删改查 增加 ...
- Erlang学习记录(三)——表达式大集合
Erlang中的表达式必须以.结束才会去执行.如果不加.你在编译环境下按多少次Enter,表达式都不会执行,表达式之间可以用,分隔,以.结尾后所有的表达式都会执行,但是只有最后一个以.结尾的表达式会在 ...
- Erlang学习记录(二)——基本数据类型
Erlang可以说和我以前接触过的语言都大不相同,这个从它的类型定义就可以看出来...反正学起来觉得既不熟悉,也不亲切,我估计在用Erlang写应用的时候,整个编程思路都要变一下了.不过存在即是合理的 ...
- Erlang学习记录(一)——Windows下的环境搭建
一.安装编译器 在http://www.erlang.org/download.html下载R16B01 Windows Binary File并安装. 二.运行编译器 安装完编译器后,打开安装目录下 ...
- Erlang学习笔记2
http://wgcode.iteye.com/blog/1007623 第二章 入门 1.所有的变量都必须以大写字母开头,如果要查看某个变量,只要输入变量的名字即可,如果一个变量被赋予值,就称为绑定 ...
随机推荐
- 7个基于Linux命令行的文件下载和网站浏览工具
7个基于Linux命令行的文件下载和网站浏览工具 时间:2015-06-01 09:36来源:linux.cn 编辑:linux.cn 点击: 2282 次 Linux命令行是GNU/Linux中最神 ...
- 利用Xtrabackup备份集合恢复一台从库的过程
1 time tar -xvf Open..tarx.gz real 35m22.502s user 10m16.499s sys 1m28.578s You have new m ...
- Codeforces 39E What Has Dirichlet Got to Do with That? 游戏+内存搜索
主题链接:点击打开链接 意甲冠军: 特定 a一箱 b球 不变n (球和箱子都不尽相同,样的物品) 设 way = 把b个球放到a个箱子中的方法数, 若way >= n则游戏结束 有2个人玩游戏. ...
- iphone关于单倍图和二倍图(导航 背景 变高)
同学们and朋友们大家好!今天我想说一下关于@2x二倍图的知识,以我所知所见所闻来讲述我的理解! 别看关于这么点小知识,有很多初学者在这个上面常会犯错误,以下是我的理解: 用二倍图是为了适配iphon ...
- Find命令, find用法,
Find命令 用法示例:查找HOME目录下大于1M小于10M的文件$ find ~ -size +1M -size -10M 15个小时这一时刻修改的文件:find . -mmin 900 | xar ...
- 【web开发学习笔记】Structs2 Action学习笔记(一个)
1.org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter准备和运行 2. <filter-mapping&g ...
- Swift - 给表格添加编辑功能(删除,插入)
1,下面的样例是给表格UITableView添加编辑功能: (1)给表格添加长按功能,长按后表格进入编辑状态 (2)在编辑状态下,第一个分组处于删除状态,第二个分组处于插入状态 (3)点击删除图标,删 ...
- css js 优化工具
我知道国内很多网页制作人员都还在制作table式网页,这样的网页打开速度很慢.如果要想网站打开速度快,就要学会使用DIV+CSS,将图片写进CSS,这样如果网站内容很多的时候,也不会影响网页的浏览.它 ...
- js 常用正则表达式分析详解
1.整数或者小数:/^((0{1}|[1-9]{1}[0-9]+)\.{1}[0-9]+|[1-9]{1}[0-9]*|0)$/ 分析:分类讨论,如果是小数,则有两种形式 0.111对应的是 0{ ...
- SDUT 1304-取数字问题(DFS)
取数字问题 Time Limit: 1000ms Memory limit: 65536K 有疑问?点这里^_^ 题目描写叙述 给定M×N的矩阵,当中的每一个元素都是-10到10之间的整数.你的 ...