Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
go testのキャッシュの仕組みにDeep Diveする
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
KazukiHayase
December 01, 2025
Technology
200
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
go testのキャッシュの仕組みにDeep Diveする
KazukiHayase
December 01, 2025
More Decks by KazukiHayase
See All by KazukiHayase
なぜ Temporal の大小比較には compare しかないのか / Why Does Temporal Only Have compare() for Comparisons
kazukihayase
1
210
entのPrivacy機能とgo/astを使って、意図しないDBアクセスを防ぐ
kazukihayase
1
450
要件定義・デザインフェーズでもAIを活用して、コミュニケーションの密度を高める
kazukihayase
0
620
CIでのgolangci-lintの実行を約90%削減した話
kazukihayase
0
570
もし今からGraphQLを採用するなら
kazukihayase
13
6.1k
Goでテストをしやすくするためにやったこと
kazukihayase
1
950
GraphQLクライアントの技術選定 2023冬
kazukihayase
9
8k
Introduction and Insights of the Hasura-based Architecture
kazukihayase
0
1.2k
自分だけが頑張るのをやめて、フルスタックなチームを作る
kazukihayase
2
3.7k
Other Decks in Technology
See All in Technology
リージョンの壁を越える、 ちょっと変わったAWSサービスの話
falken
PRO
1
300
module Synths; end
asonas
1
130
20260912_スクフェス三河
kgnkhkr
0
280
『止めない』を設計する — 制約の中で、事業の根幹を支える判断
hiroyaterui
0
260
Redmine 7.0で私が開発した新機能の狙いと背景
vividtone
1
160
2026_devsumi_ozono.pdf
o3
2
340
AI-DLCって実際どう? 〜聞きたいこと全部聞いてみる〜
news_it_enj
0
260
Snowflakeのコスト最適化を支えるアーキテクチャ設計
ktatsuya
1
1.4k
AIで開発は速くなったのに、なぜ現場は楽にならないのか 〜あなたの組織のボトルネックを突き止めるワークショップ〜
jacopen
1
320
GuardDuty 検知対応を DevOps Agent で効率化しようとしている話 / GuardDuty Investigations with DevOps Agent
masahirokawahara
1
350
synctest時代のhttptest Go 1.27で変わるHTTPサーバテストの裏側 / go conference2026 synctest and httptest
budougumi0617
1
2.6k
家のリアーキテクト・リファクタリング
suguruooki
0
110
Featured
See All Featured
The Spectacular Lies of Maps
axbom
PRO
1
990
Game over? The fight for quality and originality in the time of robots
wayneb77
1
270
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Fashionably flexible responsive web design (full day workshop)
malarkey
408
67k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.5k
The World Runs on Bad Software
bkeepers
PRO
72
12k
Tell your own story through comics
letsgokoyo
1
1.1k
Navigating the Design Leadership Dip - Product Design Week Design Leaders+ Conference 2024
apolaine
2
420
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
290
KATA
mclloyd
PRO
35
15k
AI: The stuff that nobody shows you
jnunemaker
PRO
9
990
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.5k
Transcript
go testのキャッシュの仕組みにDeep Diveする 2025.12.03 明日から使えるgo test実践テクニック集 #go_findy ©2025 Dr.'s Prime
,Inc.
自己紹介 @KazukiHayase 早瀬和輝(Kazuki Hayase) 2025年4月ドクターズプライム入社 リードエンジニア Go / TypeScript /
React(Next.js) / GraphQL 葬送のフリーレン2期がとても楽しみです ©2025 Dr.'s Prime ,Inc.
会社概要 ©2025 Dr.'s Prime ,Inc.
会社概要 ©2025 Dr.'s Prime ,Inc.
01 go testのキャッシュの仕組み 02 ソースコードの解説 03 CIでの活用 04 まとめ ©2025
Dr.'s Prime ,Inc.
1. go testのキャッシュの仕組み ©2025 Dr.'s Prime ,Inc.
go testのキャッシュの仕組み go testのキャッシュとは Go 1.10から導入された機能 テスト結果をパッケージ単位でキャッシュ 変更がない場合、テストをスキップして高速化 $ go
test ./... ok example.com/pkg1 0.123s ok example.com/pkg2 (cached) ok example.com/pkg3 0.456s ©2025 Dr.'s Prime ,Inc.
go testのキャッシュの仕組み キャッシュが利用される条件 ソースコード・テストコードが変更されていない コマンドライン引数が同じ 環境変数が同じ値 コード内で参照するファイルに変更がない ©2025 Dr.'s Prime
,Inc.
go testのキャッシュの仕組み キャッシュの利用判定の流れ 1. 前提条件のチェック 2. テスト引数の検証 キャッシュ可能なフラグのみが利用されているか 3. 2段階のキャッシュ検索
第1段階: testID の計算とテストログの取得 第2段階: testInputsID の計算と結果の取得 ©2025 Dr.'s Prime ,Inc.
2. ソースコードの解説 ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 対象コード ソースコード内の cmd/go/internal/test パッケージ tryCacheWithID というメソッドがキャッシュの判定を担当 Goのバージョンは1.25 ©2025 Dr.'s
Prime ,Inc.
ソースコードの解説 注意点 簡単のためコードを一部省略・変更しています 正確なコードはGoの公式リポジトリで確認してください https://github.com/golang/go/blob/release- branch.go1.25/src/cmd/go/internal/test/test.go ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 処理の流れ 1. 前提条件のチェック 2. テスト引数の検証 3. testID の計算 4.
testInputsID の計算 5. テスト結果の取得 ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 1. 前提条件のチェック go test のように引数なしでは無効 func (c *runCache) tryCacheWithID(b
*work.Builder, a *work.Action, id string) bool { if len(pkgArgs) == 0 { // Caching does not apply to "go test", // only to "go test foo" (including "go test ."). if cache.DebugTest { fmt.Fprintf(os.Stderr, "testcache: caching disabled in local directory mode\n") } c.disableCache = true return false } // ...省略 } ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 1. 前提条件のチェック モジュール、 GOPATH 、 GOROOT の配下にある必要がある if a.Package.Root
== "" { // Caching does not apply to tests outside of any module, GOPATH, or GOROOT. if cache.DebugTest { fmt.Fprintf(os.Stderr, "testcache ...\n", a.Package.ImportPath) } c.disableCache = true return false } ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 2. テスト引数の検証 var cacheArgs []string for _, arg :=
range testArgs { i := strings.Index(arg, "=") switch arg[:i] { case "-test.benchtime", "-test.cpu", "-test.list", "-test.parallel", "-test.run", "-test.short", "-test.skip", "-test.timeout", "-test.failfast", "-test.v", "-test.fullpath": cacheArgs = append(cacheArgs, arg) default: c.disableCache = true return false } } ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 2. テスト引数の検証 キャッシュ不可な引数が1つでもあれば無効化 run 、 timeout などはキャッシュ可能 coverprofile 、
outputdir は値が変わっても無効化しない それ以外の引数はキャッシュ不可 ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 3. testIDの計算 testID とはテストバイナリとテスト引数から作られるハッシュ値 → ソースコードや引数が変更されると、この testID も変わる ©2025
Dr.'s Prime ,Inc.
ソースコードの解説 3. testIDの計算 h := cache.NewHash("testResult") // テストバイナリとテスト引数からハッシュ値を計算 fmt.Fprintf(h, "test
binary %s args %q execcmd %q", id, cacheArgs, work.ExecCmd) testID := h.Sum() if c.id1 == (cache.ActionID{}) { c.id1 = testID } else { c.id2 = testID } ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 3. testIDの計算 計算した testID を元にテストログを取得 // Load list of
referenced environment variables and files // from last run of testID, and compute hash of that content. data, entry, err := cache.GetBytes(cache.Default(), testID) if !bytes.HasPrefix(data, testlogMagic) || data[len(data)-1] != '\n' { if cache.DebugTest { if err != nil { fmt.Fprintf(os.Stderr, "testcache: %s: input list not found: %v\n", a.Package.ImportPath, err) } else { fmt.Fprintf(os.Stderr, "testcache: %s: input list malformed\n", a.Package.ImportPath) } } return false } ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 テストログとは テスト実行時に参照した環境変数とファイルの一覧を記録したもの API_KEY と config.json が参照された場合の例: getenv API_KEY open
/home/user/project/testdata/config.json stat /home/user/project/testdata ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 4. testInputsIDの計算 テストログを元に、現在の環境変数とファイルからハッシュ値を計算 // data = テストログ testInputsID, err
:= computeTestInputsID(a, data) if err != nil { return false } ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 4. testInputsIDの計算 func computeTestInputsID(a *work.Action, testlog []byte) (cache.ActionID, error)
{ testlog = bytes.TrimPrefix(testlog, testlogMagic) h := cache.NewHash("testInputs") // The runtime always looks at GODEBUG, without telling us in the testlog. fmt.Fprintf(h, "env GODEBUG %x\n", hashGetenv("GODEBUG")) pwd := a.Package.Dir for _, line := range bytes.Split(testlog, []byte("\n")) { if len(line) == 0 { continue } s := string(line) op, name, found := strings.Cut(s, " ") if !found { return cache.ActionID{}, errBadTestInputs } // テストログの各行を処理、次のスライドで解説 } sum := h.Sum() return sum, nil } ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 4. testInputsIDの計算 switch op { case "getenv": fmt.Fprintf(h, "env
%s %x\n", name, hashGetenv(name)) case "chdir": pwd = name // always absolute fmt.Fprintf(h, "chdir %s %x\n", name, hashStat(name)) case "stat": if !filepath.IsAbs(name) { name = filepath.Join(pwd, name) } if a.Package.Root == "" || search.InDir(name, a.Package.Root) == "" { // Do not recheck files outside the module, GOPATH, or GOROOT root. break } fmt.Fprintf(h, "stat %s %x\n", name, hashStat(name)) case "open": if !filepath.IsAbs(name) { name = filepath.Join(pwd, name) } if a.Package.Root == "" || search.InDir(name, a.Package.Root) == "" { // Do not recheck files outside the module, GOPATH, or GOROOT root. break } fh, err := hashOpen(name) if err != nil { return cache.ActionID{}, err } fmt.Fprintf(h, "open %s %x\n", name, fh) } ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 4. testInputsIDの計算 環境変数は存在しない場合は 0 、存在する場合は 1 +値をハッシュ化 func hashGetenv(name
string) cache.ActionID { h := cache.NewHash("getenv") v, ok := os.LookupEnv(name) if !ok { h.Write([]byte{0}) } else { h.Write([]byte{1}) h.Write([]byte(v)) } return h.Sum() } ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 4. testInputsIDの計算 ファイルのハッシュ計算 func hashOpen(name string) (cache.ActionID, error) {
h := cache.NewHash("open") info, err := os.Stat(name) if err != nil { fmt.Fprintf(h, "err %v\n", err) return h.Sum(), nil } hashWriteStat(h, info) if info.IsDir() { // ...省略 } else if info.Mode().IsRegular() { if time.Since(info.ModTime()) < modTimeCutoff { return cache.ActionID{}, errFileTooNew } } return h.Sum(), nil } func hashWriteStat(h io.Writer, info fs.FileInfo) { fmt.Fprintf(h, "stat %d %x %v %v\n", info.Size(), uint64(info.Mode()), info.ModTime(), info.IsDir()) } ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 4. testInputsIDの計算 ファイルサイズと更新時刻をハッシュ化 ファイルサイズが大きい場合を考慮し、内容全体のハッシュ化は避ける 2秒以内に更新されたファイルはキャッシュ拒否 ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 5. テスト結果の取得 testID と testInputsID を組み合わせた最終キーで結果を取得 キャッシュの有効期限が切れている場合は無効化 // Parse
cached result in preparation for changing run time to "(cached)". // If we can't parse the cached result, don't use it. data, entry, err = cache.GetBytes(cache.Default(), testAndInputKey(testID, testInputsID)) if entry.Time.Before(testCacheExpire) { return false } ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 5. テスト結果の取得 キャッシュがヒットした場合、実行時間を (cached) に書き換えて出力 // Committed to printing.
c.buf = new(bytes.Buffer) c.buf.Write(data[:j]) c.buf.WriteString("(cached)") for j < len(data) && ('0' <= data[j] && data[j] <= '9' || data[j] == '.' || data[j] == 's') { j++ } c.buf.Write(data[j:]) return true ©2025 Dr.'s Prime ,Inc.
ソースコードの解説 処理の流れ 1. 前提条件のチェック 2. テスト引数の検証 3. testID の計算 4.
testInputsID の計算 5. テスト結果の取得 ©2025 Dr.'s Prime ,Inc.
3. CIでの活用 ©2025 Dr.'s Prime ,Inc.
CIでの活用 キャッシュが利用される条件 ソースコード・テストコードが変更されていない コマンドライン引数が同じ 環境変数が同じ値 コード内で参照するファイルに変更がない → CI環境でも同様にキャッシュが利用可能 ©2025 Dr.'s
Prime ,Inc.
CIでの活用 CIでキャッシュを利用する際の注意点 テスト実行時に実際に参照されたファイルのみを変更を検知 ソースコードと参照ファイル以外の変更は検知されない e.g. 設定ファイル、マイグレーションファイル ©2025 Dr.'s Prime ,Inc.
CIでの活用 CIでキャッシュを利用する際の注意点 検知するべき変更を検知できず、偽陰性が発生する可能性がある テストが通るが、実際には問題がある状態 Goのキャッシュ機構で検知できない変更は、別途対策が必要 ©2025 Dr.'s Prime ,Inc.
CIでの活用 条件付きキャッシュクリア Goファイル以外に変更がある場合、キャッシュをクリアする -count=1 で実行すると、その結果はキャッシュされないため注意 ©2025 Dr.'s Prime ,Inc.
CIでの活用 条件付きキャッシュクリア name: Clean Go Cache Conditionally runs: using: "composite"
steps: - name: Check non go file changes id: changes uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 with: predicate-quantifier: 'every' filters: | has-not-go-file: - '!**/*.go' - '!**/*.mod' - '!**/*.sum' - name: Clean test cache if needed shell: bash run: | if [ "${{ steps.changes.outputs.has-not-go-file }}" == "true" ] || [ "${{ github.ref }}" == "refs/heads/main" ]; then echo "Cleaning test cache due to non-go file changes or main branch" go clean -testcache else echo "Skipping cache clean - only go files changed and not on main branch" fi ©2025 Dr.'s Prime ,Inc.
CIでの活用 条件付きキャッシュクリア jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955
# v4.3.0 - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version-file: 'go.mod' - name: Clean cache conditionally uses: ./.github/actions/clean-go-cache - name: Run tests run: go test -v ./... ©2025 Dr.'s Prime ,Inc.
CIでの活用 キャッシュ利用によるCIの高速化 CIの実行時間が9分→3.5分に短縮 約60%の時間削減に成功 対応自体もシンプルで、簡単に導入可能 ©2025 Dr.'s Prime ,Inc.
4. まとめ ©2025 Dr.'s Prime ,Inc.
まとめ go test のキャッシュは testID と testInputsID で厳密にチェック テストバイナリだけでなく、参照した環境変数・ファイルの変更も検知 CI環境でもキャッシュを利用することで、手軽にCIの高速化を実現
©2025 Dr.'s Prime ,Inc.
We Are Hiring! ぜひドクターズプライムの魅力を覗きにきてください! ©2025 Dr.'s Prime ,Inc.