Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
go testのキャッシュの仕組みにDeep Diveする
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
KazukiHayase
December 01, 2025
Technology
180
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
120
entのPrivacy機能とgo/astを使って、意図しないDBアクセスを防ぐ
kazukihayase
1
440
要件定義・デザインフェーズでもAIを活用して、コミュニケーションの密度を高める
kazukihayase
0
610
CIでのgolangci-lintの実行を約90%削減した話
kazukihayase
0
560
もし今からGraphQLを採用するなら
kazukihayase
13
6k
Goでテストをしやすくするためにやったこと
kazukihayase
1
940
GraphQLクライアントの技術選定 2023冬
kazukihayase
9
7.9k
Introduction and Insights of the Hasura-based Architecture
kazukihayase
0
1.2k
自分だけが頑張るのをやめて、フルスタックなチームを作る
kazukihayase
2
3.7k
Other Decks in Technology
See All in Technology
FPGAが実現する遠方宇宙の高空間分解能天体撮影 -大型地上望遠鏡の視力を補正する「補償光学」とは?-
komei_mt
0
270
社内の7割が使うデータ基盤を、 データチーム2人で回すためにやったこと
koh_yoshi
4
1.4k
Digitization部 紹介資料
sansan33
PRO
2
7.7k
今こそ聞きたいソフトウェア設計 ドメイン駆動設計再入門
masuda220
PRO
17
7.4k
メルカリのグローバルアプリで挑んだ AlloyDB 運用と課題解決の実践記
hatappi
0
250
【CEDEC2026】『GRANBLUE FANTASY: Relink - Endless Ragnarok』のバトル制作事例 ~最高のキャラゲーを目指して~
cygames
PRO
1
300
名刺メーカーDevグループ 紹介資料
sansan33
PRO
0
1.2k
私がブラウザを自作したくなった理由
supurazako
1
240
Agent 時代の Kaggle 展望 / kaggle-in-the-agentic-era
upura
1
770
AIコーディングの次。コードレビューと理解負荷を解消して組織の開発生産性を高める
moongift
PRO
2
2.6k
Bill One 開発エンジニア 紹介資料
sansan33
PRO
7
19k
Invisible to AI? Making TYPO3 Sites Quotable by AI Search Systems
wolfgangwagner
0
210
Featured
See All Featured
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
380
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.8k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
2
3.8k
A Soul's Torment
seathinner
6
3.4k
Paper Plane (Part 1)
katiecoart
PRO
1
10k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.8k
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
260
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
630
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
66
57k
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
430
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
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.