2010-12-02 2 views
7

EUnitを使用して、サードパーティのErlangコードをテストしたい。EUnitとio:format

コードの機能からの出力は、io:format/2を使用して標準出力に表示されます。私はその出力をキャプチャし、印刷される文字列について?assertテストを実行したいと思います。サードパーティのコードを変更することはできません。

Erlangでこれを行う方法はありますか? (例えば、Javaでは、単に出力ストリームに対してSystem.setOut()を使用できます)。

更新:

​​右のトラックにあると思われます。

しかし、私はio:formatで印刷された文字列をキャプチャできるので、私は自分のアサーションをテストすることができます。コードの非常に簡単な例である:明らかに、機能result/1から復帰原子okある

result(Value) -> 
    io:format("Result: ~w~n", [Value]). 

test_result() -> 
    ?assertMatch("Result: 5~n", result(5)). 

、私は実際にコンソール(すなわち"Result: 5~n")に出力された文字列をテストします。

私はこのアプローチが間違っていますが、他の誰もこれをしないようです(検索結果が不足していると判断)。

背景:サードパーティのコードは対話型のコンソールアプリケーションなので、すべての機能では結果が表示されます。io:format

答えて

2

にはdbg(Erlangトレーサ)を使用してください。プロセスによってio:format/2に行われた呼び出しをトレースし、そこからトレースメッセージを受け取ることができます。このトレースメッセージを使用して、io:format/2,3を呼び出すために使用されているものが正しいことをアサートすることができます。この利点は、すでに実際のIOメッセージをキャプチャしているため、EUnitに干渉する必要がないことです。

小さな例では、(あなたのユニットテストに調整し、[S])のようになります。

1> HandleFun = fun(Trace, Parent) -> Parent ! Trace, Parent end. 
#Fun<erl_eval.12.113037538> 
2> dbg:tracer(process, {HandleFun, self()}). 
{ok,<0.119.0>} 
3> IOCallingFun = fun(F) -> 
3> timer:sleep(5000), 
3> io:format("Random: ~p~n",[random:uniform(1000)]), 
3> F(F) 
3> end. 
#Fun<erl_eval.6.13229925> 
4> PidToTrace = erlang:spawn_link(fun() -> IOCallingFun(IOCallingFun) end). 
<0.123.0> 
Random: 93 
Random: 444 
5> dbg:p(PidToTrace, [c]). 
{ok,[{matched,[email protected],1}]} 
6> dbg:tp(io, format, []). 
{ok,[{matched,[email protected],3}]} 
Random: 724 
Random: 946 
Random: 502 
7> flush(). 
Shell got {trace,<0.123.0>,call,{io,format,["Random: ~p~n",[724]]}} 
Shell got {trace,<0.123.0>,call,{io,format,["Random: ~p~n",[946]]}} 
Shell got {trace,<0.123.0>,call,{io,format,["Random: ~p~n",[502]]}} 
ok 
8> exit(PidToTrace). 
** exception exit: <0.123.0> 
9> dbg:stop_clear(). 
ok 
10> 

だから、他の言葉であなたは単にあなたがあなたのユニットテストを開始する前に、トレースを開始するトレースメッセージをテストしてから殺しますトレース。コールを行うプロセスだけをトレースするようにしてください!そうしないと、どこからでもメッセージが届くでしょう。 http://www.erlang.org/doc/man/erlang.html#trace-3

これを使用すると、プロセスが正しいパスを取るようにテストすることができます(たとえば、あなたが期待する正しい機能を呼び出します)。または、他のプロセスなどに正しいメッセージを送信します。ユニットテストでは見落とされることが多いですが、かなり強力です。しかし、1つの点は、がすぐにになりますので注意してください。

これが受け入れ答えではないかもしれませんが、テストのために、時には:)

幸運を使用するための優れたツールです。

+0

非常に詳細な返答ですが、私はあなたの提案を理解するのに少し時間が必要です。ありがとう。 – Max

3

erlang:group_leader/2を見てください。これを使うと、送信されるIOをキャプチャする新しいグループリーダーを設定できます。

eunitもテストコードで行われた出力をキャプチャして、うまく再生できないことがわかっています。試してみると何が起こるか見る必要があります。

+0

ルーカスに感謝します。 – Max

3

IOは通常のメッセージパッシング(ファイルのrawモードとして例外があります)によってErlangで実行されるため、erlang:group_leader/2コールを使用して標準のioサーバーの代わりに独自のサーバーを置くことができます。グループリーダーは、生成されたプロセスによって継承されるので、このグループリーダーは、キャプチャの出力を望むプロセスの前身のためだけに設定することができます。その後、トラフィックをオリジナルのものにリダイレクトする、偽のioサーバでトリッキーなフィルタリングやキャプチャを行うことができます。

ioサーバープロトコルについては、Is there a specification of the group leader protocol that handles IO?を参照し、そこに記載されているリンクを参照してください。

+0

ありがとう、Hynek。私はいくつかの読み上げを行います。 – Max

0

io:format(ユーザー、 "結果:〜w〜n"、[値])?

+0

私が説明したように、io:format()呼び出しは、私が変更できないサードパーティ製のコードです。だから、あなたの提案が私の特定のケースでどのように役立つのかわかりません。 – Max

+0

ok、私はその部分を見逃しました、申し訳ありません。しかし、それは非常に奇妙なシステムです。コンソール出力は常に機能の一部ではない副作用とみなされていました。その場合、私はioモジュールをオーバーロードしてコンソール出力をキャプチャします。 – user425720

4

アプローチ1:メック

このコードを使用して、テストし、あなたが求めている正確に何をすべき。 (特にそれがmeck:passthrough/0と呼ばれるときに)いくつかの非常に高度なメックトリックをしますが、それはまだ非常に明確だと思います。

% UUT 
foo() -> 
    io:format("Look ma no newlines"), 
    io:format("more ~w~n", [difficult]), 
    io:format("~p dudes enter a bar~n", [3]), 
    ok. 

% Helper: return true if mock Mod:Fun returned Result at least once. 
meck_returned(Mod, Fun, Result) -> 
    meck_returned2(Mod, Fun, Result, meck:history(Mod)). 

meck_returned2(_Mod, _Fun, _Result, _History = []) -> 
    false; 
meck_returned2(Mod, Fun, Result, _History = [H|T]) -> 
    case H of 
     {_CallerPid, {Mod, Fun, _Args}, MaybeResult} -> 
      case lists:flatten(MaybeResult) of 
       Result -> true; 
       _  -> meck_returned2(Mod, Fun, Result, T) 
      end; 
     _ -> meck_returned2(Mod, Fun, Result, T) 
    end. 

simple_test() -> 
    % Two concepts to understand: 
    % 1. we cannot mock io, we have to mock io_lib 
    % 2. in the expect, we use passthrough/0 to actually get the output 
    % we will be looking for in the history! :-) 
    ok = meck:new(io_lib, [unstick, passthrough]), 
    meck:expect(io_lib, format, 2, meck:passthrough()), 
    ?assertMatch(ok, foo()), 
    %?debugFmt("history: ~p", [meck:history(io_lib)]), 
    ?assert(meck_returned(io_lib, format, "Look ma no newlines")), 
    ?assert(meck_returned(io_lib, format, "more difficult\n")), 
    ?assert(meck_returned(io_lib, format, "3 dudes enter a bar\n")), 
    ?assertNot(meck_returned(io_lib, format, "I didn't say this!")), 
    ?assert(meck:validate(io_lib)). 

アプローチ2:ごく最近(2017年5月)mock_io

を使用して、私はアーランIを実装することで、テスト対象ユニットの入力と出力の両方を模擬するために非常に単純な方法を​​を書きました/ Oプロトコル。 mock_ioで

、同等のコードは次のようになる。

% UUT 
foo() -> 
    io:format("Look ma no newlines"), 
    io:format("more ~w~n", [difficult]), 
    io:format("~p dudes enter a bar~n", [3]), 
    ok. 

simple_test() -> 
    Expected = <<"Look ma no newlines" 
       "more difficult\n", 
       "3 dudes enter a bar\n">>, 
    {Pid, GL} = mock_io:setup(), 
    ?assertMatch(ok, foo()), 
    ?assertEqual(Expected, mock_io:extract(Pid)), 
    mock_io:teardown({Pid, GL}). 

注意また、そのmock_ioは、UUT入力チャンネルにデータを注入することができ、それは標準入力され、または任意の他のチャネル。例:

% UUT 
read_from_stdin() -> 
    io:get_line("prompt"). 

% Test 
inject_to_stdin_test() -> 
    {IO, GL} = mock_io:setup(), 
    mock_io:inject(IO, <<"pizza pazza puzza\n">>), 
    ?assertEqual("pizza pazza puzza\n", uut:read_from_stdin()), 
    ?assertEqual(<<>>, mock_io:remaining_input(IO)), 
    mock_io:teardown({IO, GL}). 
関連する問題