更新: | 2014-04-14 |
---|---|
バージョン: | 0.1.1 |
作者: | @voluntas |
URL: | http://voluntas.github.io/ |
Erlang の mock/stub ライブラリである meck の使い方をまとめました
meck は Erlang のモックライブラリです。
https://github.com/eproxus/meck
Erlang Solution の中の人が書いています。
meck 自体は単独で使うモノでは無くテストツールと連携するものなので、 アプリケーションを用意する必要があります。
アプリケーションについては別の記事を参考にして貰うとして、meck が使えるまで設定されているアプリケーションを用意しましたのでそちらをお使いください。
準備:
$ git clone git://github.com/voluntas/snowflake.git $ cd snowflake $ git checkout --track origin/feature/meck $ make
起動:
$ erl -pa ebin -pa deps/*/ebin
簡単なモック:
%% モック化するモジュールを指定します 1> meck:new(spam, [non_strict]). ok %% モック化する関数とアリティ、戻り値をセットします 2> meck:expect(spam, eggs, 2, {ok, <<1,2,3,4>>}). ok %% 読んでみるとモックに設定した値が返ってきます 3> spam:eggs(a,b). {ok,<<1,2,3,4>>} %% 引数の値を変えても設定した値通りです 4> spam:eggs(<<1,2,3>>, [1,2,3]). {ok,<<1,2,3,4>>} %% モック化されてると true が返ります %% ちなみにされてないと例外が上がります ... false じゃないんだ ... 5> meck:validate(spam). true %% モックを解除します 6> meck:unload(spam). ok %% そんなモジュールは存在しないのでちゃんと例外が上がります 7> spam:eggs(a,b). ** exception error: undefined function spam:eggs/2 %% ほら例外があがります 8> meck:validate(spam). ** exception error: {not_mocked,spam} in function meck:gen_server/3 (src/meck.erl, line 452)
new して expect するのが基本です。あとは expect の方法がいくつかあるのでそちらを覚えれば良いです。
expect には最初に紹介したアリティを指定するモノと無名関数を使うモノの二つがあります。 アリティを使うモノはシンプルで良いのですが引数による分岐が出来ません。
つまり a だったら b を返し c だったら d を返すという事が出来なくなります。
そこで登場するのが無名関数を使うモノです。
1> meck:new(spam, [non_strict]). ok %% アリティを定義するのでは無く無名関数を定義します 2> meck:expect(spam, eggs, fun(a) -> b; (c) -> d end). ok %% a を渡せば b 3> spam:eggs(a). b %% c を渡せば d 4> spam:eggs(c). d %% 例外が上がるのでやらなくていいです 5> spam:eggs(e). ** Reason for termination == ** {function_clause,[{erl_eval,'-inside-an-interpreted-fun-',[e],[]}, {erl_eval,expr,3,[]}]} ** exception error: no function clause matching erl_eval:'-inside-an-interpreted-fun-'(e)
expect で一通りの事は出来ますが「3回呼ばれた時は今までとは異なり、この値を返して欲しい」という処理を実現する事はめんどうです。そこで登場するのが sequence です。
1> meck:new(spam, [non_strict]). ok 2> meck:sequence(spam, eggs, 2, [a,a,b]). ok %% 一回目は a 3> spam:eggs(a,b). a %% 二回目も a 4> spam:eggs(a,b). a %% 三回目は b 5> spam:eggs(a,b). b
ちなみに四回目を呼ぶと最後の値が返されます
%% 四回目も b 6> spam:eggs(a,b). b
sequence は最後でとまりますが、loop を使えば最初に戻すことが可能です。
1> meck:new(spam, [non_strict]). ok %% 引数は sequence と変わらない 2> meck:loop(spam, eggs, 1, [1,2,3]). ok 3> spam:eggs(a). 1 4> spam:eggs(a). 2 %% ここで一週目終わり 5> spam:eggs(a). 3 %% 最初の 1 に戻る 6> spam:eggs(a). 1
history は expect など mock 化した部分がどれだけ呼ばれたかが確認出来ます
1> meck:new(spam, [non_strict]). ok 2> meck:expect(spam, eggs, 1, [1,2,3]). ok 3> spam:eggs("ham"). [1,2,3] 4> meck:history(spam). [{<0.98.0>,{spam,eggs,["ham"]},[1,2,3]}]
%% unstick は string のすでにあるモジュールを切り離します 1> meck:new(string, [unstick]). ok %% 例外が上がる 2> string:len("abc"). ** exception error: undefined function string:len/1 3> meck:new(string, [unstick]). ok %% meck で string:len/1 が常に 10 を返すように定義する 4> meck:expect(string, len, 1, 10). ok 5> string:len("abc"). 10
さすがにコレだと使いにくいので、passtrhough を指定します
passtrhough を指定すると expect していない関数は今まで通り使えます。
%% passtrhough は expect が定義された関数のみをモック化します 1> meck:new(string, [unstick, passthrough]). ok %% string:concat/2 は常に "abc" を返すようにします 2> meck:expect(string, concat, 2, "abc"). ok %% "abc" が帰って来ました 3> string:concat("abc", "efg"). "abc" %% expect していない len は普通に使えます 4> string:len("abc"). 3
meck:new(module, [Opts]) する歳のオプションを紹介します。
デフォルトでは link するようになっているため、no_link を指定する必要はありません。
meck のプロセスがクラッシュした際、自動で unload します。これが無いとテスト時にエラーが起きて途中でクラッシュした場合、meck 自体をアンロードしないため、その後ずっとエラーになるという罠があります。
その機能を 無効 にするオプションです。
cover でカバレッジを取得している際で、meck を使ってる時に問題が起きた場合してください。
カバレッジ取得をスルーします。
meck の history で呼び出せる記録を全て保持しないようにします。