Common Testの結合テストケース内でcowboy handlerのstateを検証する

Nov 7, 2017 ( Feb 11, 2022 更新 )

概要

  • Common Testでcowboyを使って書いたサーバの結合テストを書きたい
  • 結合テストだけどcowboy handlerのStateを検証したい
  • case application:get_application(ct) of... でCommon Test時のみ有効になるAPIを追加して検証することができる
    • もっと良い方法があるかもしれない…

Common TestでWSサーバの結合テストを書く

cowboyを使ったサーバを書いています。

moduleの単体テストだけではなく、WSサーバならWS handshakeしてからのframeの送受信を検証する結合テストも書くとより安心です。
私は、結合テストをCommon Testで力技で書きました。

WSサーバのクライアントとしてgunを使っています。 また、WS frameのフォーマットとしてJSONを使っており、エンコードとデコードにjsxを使っています。

init_per_testcase(_, Config) ->
  {ok, Started} = application:ensure_all_started(my_server),
  {ok, Gun} = application:ensure_all_started(gun),
  [{gun, Gun}, {applications, Started} | Config].

WS frameを送ったあとの検証はこんな感じでかけます。

%% テストケース
should_get_pong_when_send_ping(Config) ->
  {ConnPid, StreamRef} = connect(Config),
  gun:ws_send(ConnPid, {text, jsx:encode(ping())}),
  receive {gun_ws, ConnPid, {text, Msg} = Frame} ->
    ct:pal("Got msg: ~p", [Frame]),
    true = jsx:encode(pong()) =:= Msg
  after 500 ->
    ct:fail(timeout)
  end,`

  {comment, "PINGを送ったらPONGが返ること"}.
%% helper
connect(Config) ->
  {ok, ConnPid} = gun:open("localhost", 8080),
  StreamRef = gun:ws_upgrade(ConnPid, "/ws", [
    %% additional headers
  ]),
  receive
    {gun_ws_upgrade, ConnPid, ok, Headers} ->
      {ConnPid, StreamRef};
    {gun_response, ConnPid, _, _, Status, Headers} ->
      exit({ws_upgrade_failed, Status, Headers});
    {gun_error, ConnPid, StreamRef, Reason} ->
      exit({ws_upgrade_failed, Reason})
  after 1000 ->
    exit(timeout)
  end.

結合テスト用のAPIを用意する

あるframeを送って、その結果がframeとして受信できるようなAPIであれば、上記のようなケースを書けばいいので簡単です。 ですが、単にhandlerのstateが更新されるようなケースをテストしたい場合がありました。

簡単に検証するために、

  • handlerにCommon Test実行時にのみ有効になるAPIを用意する

という方法をとって、テストケースをつくりました。

cowboyのwebsocket handlerのwebsocket_handle関数内でctが稼動しているか検証し、稼動していたらテスト時のみ有効にする挙動を書きます。

websocket_handle({text, Msg}, Req, State) ->
  Result = case application:get_application(ct) of
    {ok, common_test} ->
        %% テスト用の処理(API)
    _ ->
        %% 通常利用時の処理
  end,
  {reply, {text, jsx:encode(Result)}, Req, NewState}.

テストケースでは、WSを使ってテスト時のみ動くAPIを実行します。

gun:ws_send(ConnPid, {text, jsx:encode(#{ <<"test">> => <<"getState">> })}),
receive {gun_ws, ConnPid, {text, Msg} = Frame} ->
  #{ <<"state">> := #{ <<"user_id">> := Actual } } = jsx:decode(Msg, [return_maps]),
  true = Actual =:= Expected
after 500 ->
  ct:fail(timeout)
end

このようにして半ば無理やりにStateを検証することができます。
ただ、プロダクションコードにテスト時のみ使うAPIが入ってしまうのが、悩ましいところです。もっと良い方法があったら知りたいです。

Return to top