Slide 1

Slide 1 text

synctest時代のhttptest Go 1.27で変わるHTTPサーバテストの裏側 @budougumi0617 Go Conference 2026 / 2026-09-11

Slide 2

Slide 2 text

自己紹介 清水 陽一郎 @budougumi0617 株式会社LayerX (2023/03~) 法人様向けクレジットカード・ETCカードを開発中 2026年4月からエンジニアリングマネージャ 一般社団法人Gophers Japan代表理事のひとり 2022年にGoの本を出版 24年3月 韓国語版出版 11/2 layerx.go #6 やるよ!! © LayerX Inc.

Slide 3

Slide 3 text

httptestとsynctestのおさらい httptest使ってますか httptest.NewRequest + httptest.NewRecorder Handlerの実装に対してResponseを検証する単体テストコードが書ける httptest.NewServer テスト内でHTTPサーバを起動してクライアントからリクエストするAPIテストが書ける 通常の http.Server と違ってノンブロッキングでサーバが起動するので便利 © LayerX Inc. 3

Slide 4

Slide 4 text

httptestとsynctestのおさらい synctest使ってますか Go 1.25から正式利用が可能 決定的に非同期処理・時間依存のテストを書ける flakyになりがちが非同期処理のテストを安定的に書ける スリープや時間経過にかかるテストを一瞬で終わるように書ける © LayerX Inc. 4

Slide 5

Slide 5 text

なぜsynctestと組み合わせにくかったのか httptestとsynctest: 一緒に使ってみたくなりますよね? でも実際に書くとdeadlockが起きたり思ったように時刻が進まない(Go 1.26までは) © LayerX Inc. 5

Slide 6

Slide 6 text

今日の話の流れ と synctest のおさらい httptest.Server がなぜ synctest と併用できなかったのか Go 1.27の httptest は何が出来るようになったのか httptest.NewTestServer は何をどうやって実現しているのか httptest © LayerX Inc. 6

Slide 7

Slide 7 text

synctestのおさらい

Slide 8

Slide 8 text

synctestのおさらい synctest pacakge func Test(t *testing.T, f func(*testing.T)) それぞれのテストを独立した「バブル」と呼ぶ隔離環境で実行できる バブルの中で生成されたgoroutineの実行状況が管理される バブルの中では独自の時間(fake clock)が流れる 10分後にタイムアウトしたらPASS、のようなテストでも一瞬で完了する func Wait() バブルの中のgoroutineがすべてdurably blockedになるのを待機する func Sleep(d time.Duration) © LayerX Inc. Go1.27で追加。 time.Sleep + synctest.Wait 8

Slide 9

Slide 9 text

synctestのおさらい 独立した時間が流れる「バブル」 synctest bubble fake clock G1 00:10:00 G3 G2 synctest bubble fake clock G4 00:25:00 G6 バブル内で生まれた goroutine を管理する 独自のfake timeを管理する それぞれのバブルは synctest.Test ごとに独立 G5 © LayerX Inc. 9

Slide 10

Slide 10 text

synctestのおさらい durably blocked ゴルーチンが永続的にブロックされている状態 バブル内で作成されたチャネルに対する送信または受信操作がブロックされている すべてのケースがバブル内で作成されたチャネルを対象とした select 文のブロック sync.Cond.Wait sync.WaitGroup.Wait メソッドは、 sync.WaitGroup.Add メソッドがバブル内で呼び出された場合 time.Sleep バブル内のすべてのgoroutineがdurably blockedになると、 バブル内の時刻が進む(n秒のSleepなどが即時に解消される) synctest.Wait を使っていた場合は Wait が解除される © LayerX Inc. 10

Slide 11

Slide 11 text

synctestのおさらい fake timeによって時間が独自に経過する © LayerX Inc. 11

Slide 12

Slide 12 text

synctestのおさらい non-durably blocked バブルの外からブロックが解除される可能性がある 次の操作はdurably blockedにはならない または sync.RWMutex のロック操作 ネットワーク操作のI/O待ち状態 システムコール sync.Mutex © LayerX Inc. 12

Slide 13

Slide 13 text

httptest.Server がなぜ synctest と併用できなかったのか non-durably blocked な処理は synctest と併用できない バブル内のすべてのgoroutineがdurably blockedにならない bubble内の時刻が進まない synctest.Wait を使っていた場合はWaitが解除されない httptest.Server はサーバなのでネットワークI/Oからリクエストを待ち続ける そのためバブルの中でfake clockが進まない synctest.Wait も解除されない © LayerX Inc. 13

Slide 14

Slide 14 text

httptestとsynctest: 一緒に使ってみる(再掲) © LayerX Inc. 14

Slide 15

Slide 15 text

httptestとsynctestは併用できない

Slide 16

Slide 16 text

Go 1.27のhttptestの新機能

Slide 17

Slide 17 text

Go1.27のhttptest Go 1.27からのsynctestとhttptest リリースノートを読むと httptest.NewTestServer で synctest に対応したらしい! 正確に言うと @mazrean さんのGo Proposal Weekly Digestで見かけて目をつけていた net/http/httptest The new NewTestServer function creates a Server configured to use an in-memory fake network suitable for use with the testing/synctest package. © LayerX Inc. ref: Go 1.27 Release Notes 17

Slide 18

Slide 18 text

Go 1.27のhttptestは何が出来るようになったのか httptest.NewTestServer() Go 1.27で追加された httptest.Server の新しい初期化関数 インメモリな擬似通信でクライアント・サーバー間をやりとりするテストを書ける © LayerX Inc. 18

Slide 19

Slide 19 text

Go 1.27のhttptestは何が出来るようになったのか synctestと併用しなくても便利 サーバにある Client() メソッドから作成したClientからサーバへ疑似通信できる このクライアントを使うとどんなURLでもテスト用サーバにリクエストされる 逆に他のクライアントからは繋がらない インメモリなので実行環境のポートを占有しない © LayerX Inc. 19

Slide 20

Slide 20 text

httptest.NewTestServerは 何をどうやって実現しているのか

Slide 21

Slide 21 text

Go 1.27のhttptestは何が出来るようになったのか どうやって実現しているのか Go1.26以前からあった httptest.NewServer で作成した(ほぼ通常の)HTTPサーバの処理の流れ 側 側 client server http.Client http.Server http.RoundTripper Serve(net.Listener) *http.Transport TCPListener.Accept DialContext TCPConn © LayerX Inc. OS socket (loopback) TCPConn 21

Slide 22

Slide 22 text

Go 1.27のhttptestは何が出来るようになったのか httptest.NewServer と httptest.NewTestServer はどう違うの? 実はそこまで変わっていない internal/nettest パッケージの nettest.Listener と nettest.Conn を使っている 側 側 client server http.Client http.Server http.RoundTripper Serve(net.Listener) *http.Transport DialContext client nettest.Conn © LayerX Inc. nettest.Listener.Accept connection pair in-memory server nettest.Conn 22

Slide 23

Slide 23 text

internal/nettest

Slide 24

Slide 24 text

internal/nettest deep dive internal/nettest durably blockedと判定される処理だけを使って net.Listener と net.Conn の実装を提供している バブル内で作成されたチャネルに対する送信または受信操作がブロックされている すべてのケースがバブル内で作成されたチャネルを対象とした select 文のブロック sync.Cond.Wait sync.WaitGroup.Wait メソッドは、 sync.WaitGroup.Add メソッドがバブル内で呼び出された場合 time.Sleep そのため internal/nettest を使っていても synctest のバブルが期待通りの挙動をしてくれる © LayerX Inc. 24

Slide 25

Slide 25 text

internal/nettest deep dive httptestはどう利用しているか サーバが保持するlistenerの NewConn メソッドでクライアントもコネクションを作成することで 同じ net.Conn の実体を参照する © LayerX Inc. ref: server.go#L582-L588 25

Slide 26

Slide 26 text

internal/nettest deep dive internal/nettest.Conn 内部に2つのバッファとチャネルによるロック構造を持つ net.Conn の実装 クライアントが書き込み、サーバー側が読むバッファ サーバが書き込み、クライアントが読むバッファ サーバとクライアントが同じ Conn を見ていればバッファ経由で送受信できる © LayerX Inc. ref: internal/nettest/conn.go 26

Slide 27

Slide 27 text

internal/nettest deep dive internal/nettest.NewListener *nettest.Listener は net.Listener インターフェースを満たす実装 状態管理しながらチャネルを使った Gate というロック機能 作成したインメモリな Conn をプールする インメモリな net.Conn を作成する NewConn メソッドを持つ © LayerX Inc. 27

Slide 28

Slide 28 text

internal/nettest deep dive httptestはどう利用しているか クライアントが nettest.Listener のキューに nettest.Con をpushする サーバは nettest.Listener のキューから nettest.Con をpopする 側 側 client server http.Client http.Server http.RoundTripper Serve(net.Listener) *http.Transport DialContext client nettest.Conn © LayerX Inc. nettest.Listener.Accept connection pair in-memory server nettest.Conn 28

Slide 29

Slide 29 text

internal/nettest deep dive httptestはどう利用しているか © LayerX Inc. 29

Slide 30

Slide 30 text

internal/nettest deep dive internal/nettest 公開pkgとして実装するプロポーザルも出てはいる https://github.com/golang/go/issues/77362 © LayerX Inc. ref: golang/go #77362 30

Slide 31

Slide 31 text

まとめ synctest は決定的に非同期処理・時間依存のテストを書ける バブルとdurably blockedで成り立っている Go1.26までの httptest.Server はno-durably blockedなネットワーク通信をするので synctest と併用できなかった Go1.27の httptest.NewTestServer を使えば synctest と併用できるようになった 実現している internal/nettest は思ったよりシンプル GoはGoで実装されているので読みやすい! © LayerX Inc. 31

Slide 32

Slide 32 text

参考資料 (1/3) Go 1.27 is released!(2026-08-19リリース) Go 1.27 Release Notes net/http/httptest package documentation net/http/httptest: synctest support #76608 proposal: testing/nettest: in-memory implementations of net package interfaces #77362 © LayerX Inc. 32

Slide 33

Slide 33 text

参考資料 (2/3) CL 740100: testing/nettestのドラフト実装 net/http: use fake network for HTTP/3 tests #80480 CL 801940: http3: rework registration to allow using a fake network CL 803380: net/http: rework HTTP/3 registration to allow using a fake network net/http のHTTP/3テストにおける PacketNet の利用 © LayerX Inc. 33

Slide 34

Slide 34 text

参考資料 (3/3) source internal/nettest.Listener source internal/nettest.Conn source internal/gate source Go Blog: Testing concurrent code with testing/synctest net/http/httptest.Server © LayerX Inc. 34