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
190
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
200
entのPrivacy機能とgo/astを使って、意図しないDBアクセスを防ぐ
kazukihayase
1
450
要件定義・デザインフェーズでもAIを活用して、コミュニケーションの密度を高める
kazukihayase
0
610
CIでのgolangci-lintの実行を約90%削減した話
kazukihayase
0
570
もし今からGraphQLを採用するなら
kazukihayase
13
6k
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
ハッカソンで入賞した話 @ Findy LT
asari194617
0
1.5k
型落ちシンクライアント端末のPoEモジュールを自作したかった話
logica0419
0
390
AIに持続⼒を与える 判断の⻑期記憶設計
eiei114
1
680
現場回帰したデータエンジニアが考える AI 時代のキャリア開発 / Career Development in the Age of AI Perspectives from a Hands-on Data Engineer
medley
0
320
あなたの知らないバージョン命名規則
sat
PRO
2
370
AIペネトレーションテスト・ セキュリティ検証「AgenticSec」紹介資料
laysakura
2
9.4k
案件に一番詳しいAIを Amazon Bedrock AgentCore で作る ― 知見が知見を生むチームへ / Compounding Knowledge with AgentCore
yusukeshimizu
1
170
属人化を叩き割れ!「制作加速」と「安定化」の矛盾を打破する:8年目プロダクトが辿り着いた「ミスが起こり得ない」アセット制作フローの全貌
gree_tech
PRO
0
370
巨大気象データと戦う ― サロゲートモデル学習を高速化する圧縮技術
gpuunite_official
0
250
LanceDB入門
mocobeta
9
670
VLMで2.3万枚のPyCon JP写真を検索!
terapyon
1
420
Go 1.27 の標準パッケージに uuid が入った!のでいろいろ喋る / go_127_std_go_uuid
convto
3
660
Featured
See All Featured
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
400
4 Signs Your Business is Dying
shpigford
187
23k
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.1k
Scaling GitHub
holman
464
140k
Rails Girls Zürich Keynote
gr2m
96
14k
How to make the Groovebox
asonas
2
2.3k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
270
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.3k
Exploring anti-patterns in Rails
aemeredith
3
470
Fireside Chat
paigeccino
42
4k
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
420
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.