Skip to content

Instantly share code, notes, and snippets.

@voluntas
Last active February 17, 2021 12:40
Show Gist options
  • Save voluntas/4243779 to your computer and use it in GitHub Desktop.
Save voluntas/4243779 to your computer and use it in GitHub Desktop.
meck コトハジメ

meck コトハジメ

更新:2014-04-14
バージョン:0.1.1
作者:@voluntas
URL:http://voluntas.github.io/

Erlang の mock/stub ライブラリである meck の使い方をまとめました

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

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)

sequence

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

loop

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

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

new 時のオプション

meck:new(module, [Opts]) する歳のオプションを紹介します。

no_link

デフォルトでは link するようになっているため、no_link を指定する必要はありません。

meck のプロセスがクラッシュした際、自動で unload します。これが無いとテスト時にエラーが起きて途中でクラッシュした場合、meck 自体をアンロードしないため、その後ずっとエラーになるという罠があります。

その機能を 無効 にするオプションです。

no_passthrough_cover

cover でカバレッジを取得している際で、meck を使ってる時に問題が起きた場合してください。

カバレッジ取得をスルーします。

no_history

meck の history で呼び出せる記録を全て保持しないようにします。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment