feature image

2023年3月13日 | ブログ記事

GoでWebSocketのテスト書く

この記事は、2023年traP新歓ブログリレー5日目の記事です。

こんにちは。20Bの@Rasです。traPでは主にSysAd班で活動しています。
先日traQでのメッセージ数が1位になりました。

pasted-image-20230313220701

東工大に合格された方、おめでとうございます。過去に自炊や一人暮らしのブログを書いているので是非よかったら見てください。
過去のブログ一覧

1年半ほど前にVSCodeで効率的にGoのテストを書く記事を書きました。
VSCodeで手を抜いてGoのテストを手を抜かずに書く

ごくたまに反響をもらえるのでありがたい限りです。

今回はGoでWebsocketのテストを書く手法を紹介します。

手元では再現できるんですよね

サンプルを書きました。
https://github.com/ras0q/go-wstest

このレポジトリには2つの実行コマンドがあります。

$ git clone git@github.com:ras0q/go-wstest.git

$ make
go build -o ./bin/app .
go build -o ./bin/wscat ./wscat

$ tree ./bin
./bin
├── app
└── wscat

0 directories, 2 files

appを実行し localhost:8080 にWebSocketサーバーを立ち上げた後、別のシェルでwscatを実行すると無事やまびこが返ってくるのが確認できます。

$ ./bin/wscat ws://localhost:8080/ws
Connected to ws://localhost:8080/ws
> hello
< You said: hello
> yo
< You said: yo
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
< You said: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

手元でやまびこが返ってくるのは確認できましたが、これをテストするためには少し特殊な方法を使う必要があります。

WebSocketのテスト...?

結論を先に貼っておきます。
https://github.com/ras0q/go-wstest/blob/main/ws/ws_test.go

GoでWebアプリケーションを実装した時、HTTPハンドラーのテストを書くときには、標準パッケージの net/http/httptest を使うのが一般的でしょう。httptest.NewServer(handler http.Handler) *Serverを使うとgoroutineを使っていい感じに別スレッドでHTTPサーバーを立ててくれます。

少し荒業ですが、WebSocketのテストでもこれを使います。

httptest.NewServerの引数にはhttp.Handler インターフェイスを渡す必要があるので、*wsHandlerhttp.Handlerを満たすように実装します。

type wsHandler struct{}

// *wsHandlerはhttp.Handlerインターフェイスを満たす
func (h *wsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ws.Serve(w, r)
}

この*wsHandlerを使ってhttptest.Serverを立ち上げます。ついでに自動で作られるURLのスキーマもhttp://からws://に変えてしまいます。

server := httptest.NewServer(&wsHandler{})
server.URL = strings.Replace(server.URL, "http", "ws", 1)

あとはこのURLに向けてWebSocketを接続すると1つのソースコード上でWebSocketのサーバーとクライアントを実装することができます。

c, _, err := websocket.DefaultDialer.Dial(server.URL, nil)
if err != nil {
	...
}

実際にテストを書いてみると以下のようになります。(ws_test.go と内容は同じです)

func TestServe(t *testing.T) {
	t.Run("n回並行にWebsocketの相互通信を行う", func(t *testing.T) {
		t.Parallel()

		var (
			n  = 10
			wg = sync.WaitGroup{}
		)

		wg.Add(n)
		defer wg.Wait()

		for i := 0; i < n; i++ {
			go func(i int) {
				defer wg.Done()

				// httptestを無理やりws用に使う
				server := httptest.NewServer(&wsHandler{})
				server.URL = strings.Replace(server.URL, "http", "ws", 1)

				// クライアントを立ち上げる
				c, _, err := websocket.DefaultDialer.Dial(server.URL, nil)
				require.NoError(t, err)

				// "hello {i}"と送信すると、、、
				err = c.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("hello %d", i)))
				require.NoError(t, err)

				// "You said: hello {i}"と返ってくる
				_, msg, err := c.ReadMessage()
				require.NoError(t, err)
				require.Equal(t, []byte(fmt.Sprintf("You said: hello %d", i)), msg)
			}(i)
		}
	})
}

複数同時接続を意識してgoroutineを回していますが、問題なくテストは成功します。

$ go test ./ws/... -v -race
=== RUN   TestServe
=== RUN   TestServe/n回並行にWebsocketの相互通信を行う
=== PAUSE TestServe/n回並行にWebsocketの相互通信を行う
=== CONT  TestServe/n回並行にWebsocketの相互通信を行う
client1: 2023/03/13 21:21:15 connected
client2: 2023/03/13 21:21:15 connected
client3: 2023/03/13 21:21:15 connected
client4: 2023/03/13 21:21:15 connected
client5: 2023/03/13 21:21:15 connected
client6: 2023/03/13 21:21:15 connected
client7: 2023/03/13 21:21:15 connected
client8: 2023/03/13 21:21:15 connected
client10: 2023/03/13 21:21:15 connected
client9: 2023/03/13 21:21:15 connected
--- PASS: TestServe (0.00s)
    --- PASS: TestServe/n回並行にWebsocketの相互通信を行う (0.01s)
PASS
ok      github.com/ras0q/go-wstest/ws

終わりに

GoでWebSocketのテストを書いてる記事が見つからなかったので書いたんですが、この記事を書いている途中で見つけてしまいました。共有だけしておきます。

新入生の中には大学からプログラミングを始めようと思っている方も多いと思います。筆者もその一人でした。最初からこの記事を完全に理解する必要はないです。traPでは初心者のサポートがめちゃめちゃ手厚いので是非体験会だけでもお越しください!

明日は、@kashiwadeさんの記事です。楽しみ〜

Ras icon
この記事を書いた人
Ras

20B。アライグマです。

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2021年8月12日
CPCTFを支えたWebshell
mazrean icon mazrean
2021年5月19日
CPCTF2021を実現させたスコアサーバー
xxpoxx icon xxpoxx
2023年3月20日
traPグラフィック班の活動紹介(Ver.2023)
NABE icon NABE
2022年9月26日
競プロしかシラン人間が web アプリ QK Judge を作った話
tqk icon tqk
2022年4月5日
アーキテクチャとディレクトリ構造
mazrean icon mazrean
2017年11月17日
そばやのワク☆ワク流体シミュレーション~MPS編~
sobaya007 icon sobaya007
記事一覧 タグ一覧 Google アナリティクスについて