2010-12-02 2 views
7

EUnit을 사용하여 일부 타사 Erlang 코드를 테스트하려고합니다.EUnit 및 io : 형식

코드 기능의 출력은 io:format/2을 사용하여 표준 출력에 표시됩니다. 그 출력을 캡쳐하고 프린트 할 문자열에 대해 ?assert 테스트를 수행하고 싶습니다. 제 3 자 코드를 수정할 수 없습니다.

얼랑 (Erlang)으로 이것을 할 수있는 방법이 있습니까? (예를 들어, Java에서는 단순히 출력 스트림에 System.setOut()을 사용할 수 있습니다.

업데이트 :

group_leader/2는 바른 길에 보인다.

하지만 여전히 내 어설 션을 테스트 할 수 있도록 io:format에 의해 인쇄 된 문자열을 캡처하는 방법을 알 수 없습니다. 코드의 매우 단순화 된 예는 다음

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

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

분명히 함수 result/1의 반환은 원자 ok이지만 실제로 콘솔 (즉 "Result: 5~n")로 출력했다 문자열을 테스트 할.

내가이 검색 방법에 잘못 맞습니까? 검색 결과가 부족하여 다른 사람이하지 않는 것 같습니다.

배경 : 타사 코드는 대화 형 콘솔 응용 프로그램이므로 모든 기능이 io:format을 사용하여 결과를 표시합니다.

답변

2

은 dbg (Erlang 추적자)을 사용합니다. 프로세스에서 io : format/2에 대한 호출을 추적하고 추적에서 추적 메시지를 수신 할 수 있습니다. 이 추적 메시지를 사용하여 io를 호출하는 데 사용중인 것이 무엇인지 주장 할 수 있습니다. format/2,3가 맞습니다. 이것의 이점은 이미 실제 IO 메시지를 캡처하고 있기 때문에 EUnit을 간섭 할 필요가 없다는 것입니다. 당신이 당신의 단위 테스트를 시작하기 전에

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> 

그래서 다른 말로하면 간단하게 추적을 시작 추적 메시지를 테스트 한 후 죽일 :

작은 예는 (당신의 단위 테스트 [S]로 조정) 할 수 추적.전화를 거는 과정 만 추적하도록하십시오! 그렇지 않으면 온 곳에서 메시지를 받게됩니다. 다음과 같이 추적 메시지가 어떻게 표시되는지 확인할 수 있습니다. http://www.erlang.org/doc/man/erlang.html#trace-3

이것을 사용하면 프로세스가 올바른 경로를 취하는 등의 테스트를 수행 할 수 있습니다 (예 : 예상되는 올바른 기능 호출). 또는 다른 프로세스 등으로 올바른 메시지 전송 그것은 종종 단위 테스트에서 간과 되나 아주 강력 할 수 있습니다. 1 점은 그것이 빨리 일이 될 수있다이다 조심하십시오.

이 허용 대답하지 않을 수도 있지만, 때때로 :

행운 테스트에 사용할 수있는 좋은 도구입니다.

+0

매우 자세한 답변을 드리 자면, 귀하의 제안을 이해하는 데 약간의 시간이 필요합니다. 감사. – Max

3

erlang : group_leader/2를 살펴보면이 그룹 ID를 사용하여 보낼 IO를 캡처 할 새 그룹 리더를 설정할 수 있습니다.

eunit이 테스트 코드에서 수행 된 출력을 캡처하여 잘 작동하지 않을 수도 있음을 알고 있습니다. 시험해 봐야 할 일이 무엇인지 확인해야합니다.

+0

감사합니다. 루카스에게 감사드립니다. – Max

3

IO는 일반 메시지 전달 (파일의 원시 모드로 예외가 있음)으로 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 : 형식 (사용자, "결과 : ~ w ~ n", [값])?

+0

제가 설명한 것처럼, io : format() 호출은 수정할 수없는 써드 파티 코드입니다. 그래서, 나는 당신의 제안이 나의 특별한 경우에 어떻게 도움이되는지를 보지 못했다. – Max

+0

오케이, 나는 그 부분을 놓쳤다, 미안. 그러나이 경우에는 매우 이상한 시스템입니다. 콘솔 출력은 항상 기능의 일부가 아닌 부작용으로 간주됩니다. 이 경우 나는 오버로드/모의 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 : 사용 mock_io 최근에

(2017 년 5 월) 내가 얼랑 I를 구현, 테스트에서 입력과 단위의 출력 모두를 조롱하는 mock_io, 아주 간단한 방법을 썼습니다/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 입력 채널에 데이터를 삽입하여 stdin가 또는 다른 채널 일 수있다. 예를 들면 다음과 같습니다.

% 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}).