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
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
KazukiHayase
December 01, 2025
Technology
170
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
entのPrivacy機能とgo/astを使って、意図しないDBアクセスを防ぐ
kazukihayase
1
410
要件定義・デザインフェーズでもAIを活用して、コミュニケーションの密度を高める
kazukihayase
0
580
CIでのgolangci-lintの実行を約90%削減した話
kazukihayase
0
560
もし今からGraphQLを採用するなら
kazukihayase
13
6k
Goでテストをしやすくするためにやったこと
kazukihayase
1
930
GraphQLクライアントの技術選定 2023冬
kazukihayase
9
7.9k
Introduction and Insights of the Hasura-based Architecture
kazukihayase
0
1.2k
自分だけが頑張るのをやめて、フルスタックなチームを作る
kazukihayase
2
3.7k
Goでテンプレートからファイルを自動生成するCLIを作る
kazukihayase
0
1.6k
Other Decks in Technology
See All in Technology
RSA暗号を手計算したくなること、ありますよね?? (20260615_orestudy6_rsa)
thousanda
0
430
SONiCのLinuxベースを活かしたZabbix監視
sonic
0
170
【Snowflake Summit 2026 Recap!!】Snowflake Summit Deep Dive: Security & Governance
civitaspo
1
190
2026 TECHFRESH 畢業分享會 - AI-Native 重塑軟體工程與虛擬講師
line_developers_tw
PRO
0
1.1k
Socrates × Looker 〜セマンティックレイヤーで進化するデータ分析エージェント〜
hanon52_
3
2.4k
ACE-Step-1.5で見る 音楽生成AIのしくみと“破綻だけ直す”Retake機能の開発【zennfes spring 2026 登壇資料】
personabb
1
470
AIのReact習熟度を測る
uhyo
2
580
やさしいA2A入門
minorun365
PRO
12
1.9k
AI駆動開発を通して感じた、 AI時代のデザイナーの役割変化
whisaiyo
3
2.1k
新しいUbuntu/GNOMEが使いたいからXからWaylandへ移行頑張ってるの巻 2026-06-20
nobutomurata
0
120
2026TECHFRESH畢業分享會 - 原生還是跨平台? App 開發踩坑實錄
line_developers_tw
PRO
0
1.1k
LayerX コーポレートエンジニアリング室におけるサプライチェーンセキュリティへの取り組み / Supply Chain Security at LayerX Corporate Engineering
yuyatakeyama
2
400
Featured
See All Featured
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
370
Prompt Engineering for Job Search
mfonobong
0
340
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
380
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
201
75k
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
190
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.3k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.5k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
3.4k
Scaling GitHub
holman
464
140k
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.