Go を触り始めたあなたが次にぶつかる壁――それは 依存関係の管理 と 品質保証 です。Go Modules を使いこなし、テスト駆動開発 (TDD) を取り入れることで、チーム開発でもスケールする堅牢なコードベースを構築できます。本記事では、Modules の基本コマンドからモック生成、CI 自動化まで、実践的なノウハウを詰め込みました。この記事を読み終える頃には、あなたの Go プロジェクトが 再現性・信頼性・拡張性 を兼ね備えたものに生まれ変わります。
この記事で得られること
- Modules の導入・運用フローが理解できる
- Go らしいテスト設計パターンを習得できる
- GitHub Actions によるテスト自動化パイプラインを構築できる
- 実務で使えるディレクトリ構成・CI ファイル・.gitignore をコピー&ペーストできる
はじめに
前回の記事では "Hello, World!" から軽量 Web サーバーまでを体験し、Go の開発速度と並行処理の楽しさを味わいました。実際の業務で Go を使うには、再現性のある依存管理 と テスト自動化 が不可欠です。本稿ではその中核である Go Modules と TDD を丁寧に解説します。
Go Modules とは?
歴史と採用背景
Go 1.11 で実験的に導入、1.13 でデフォルト化された 依存管理システム。従来の GOPATH
ベースから脱却し、プロジェクト単位で依存ライブラリのバージョンを固定 できます。
旧 (GOPATH) | 新 (Modules) |
---|---|
$GOPATH/src 配下に強制配置 | 任意ディレクトリで OK |
dep など外部ツールが必要 | 標準 go CLI で完結 |
バージョン固定が煩雑 | go.mod に宣言的に記述 |
基本コマンド早見表
シチュエーション | コマンド | 補足 |
---|---|---|
新規プロジェクト開始 | go mod init example.com/app | go.mod 作成 |
依存追加 (コード書くだけで可) | go get github.com/gin-gonic/gin@v1.9.0 | @latest 省略可 |
バージョン整合&不要パッケージ削除 | go mod tidy | CI で実行推奨 |
依存グラフ確認 | go mod graph | 重複確認に便利 |
キャッシュクリア | go clean -modcache | 破損時の最終手段 |
既存プロジェクトを Modules 対応にする
- ルートで
go mod init <module path>
を実行 go build ./...
で依存解析 →go.mod
とgo.sum
が生成replace
ディレクティブでローカルパッケージを一時的に参照する例:replace example.com/old => ../local/old
- CI に
go vet
,go test
,go mod tidy
を追加
Tips: GOPATH 時代のコードはインポートパスが
github.com/組織/リポジトリ/...
になっていることが多い。go fix
で自動修正できる場合も。
ディレクトリ構成例とサンプルテスト
Go Modules 初期化直後の最小構成
myapp/
├── go.mod
├── main.go
└── main_test.go
実務で推奨されるレイヤード構成
myapp/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── go.mod
├── go.sum
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── handler/
│ │ ├── hello.go
│ │ └── hello_test.go
│ └── domain/
│ └── user.go
├── mock/
│ └── mock_user.go
└── Makefile
.gitignore
# Go binaries & test artifacts
*.exe
*.dll
*.so
*.dylib
*.test
*.out
# Build output
bin/
coverage.out
# Dependency directories (if vendored)
vendor/
# Editor / IDE settings
.vscode/
.idea/
.github/workflows/ci.yml
name: Go CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "^1.22"
- run: go test ./... -v
- run: go vet ./...
- run: go mod tidy && git diff --exit-code
cmd/server/main.go
package main
import (
"log"
"net/http"
"example.com/myapp/internal/handler"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", handler.HelloHandler)
log.Println("start :8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal(err)
}
}
internal/handler/hello.go
package handler
import (
"encoding/json"
"fmt"
"net/http"
)
func HelloHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
resp := map[string]string{"message": fmt.Sprintf("Hello, %s", name)}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
internal/handler/hello_test.go
package handler
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestHelloHandler(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/hello?name=Go", nil)
rr := httptest.NewRecorder()
HelloHandler(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("unexpected status: %d", rr.Code)
}
want := `{"message":"Hello, Go"}`
if strings.TrimSpace(rr.Body.String()) != want {
t.Fatalf("body = %s, want %s", rr.Body.String(), want)
}
}
サンプルコードの GitHub リンク
- リポジトリ全体: lancelot89/go-blog-examples
- 本記事用ブランチ:
article/02-modules-tdd
- ブランチをチェックアウトして
go mod tidy && go test ./...
で即動作確認できます。
- ブランチをチェックアウトして
- Pull Request(差分レビュー用): #1 – Modules/TDD サンプル
- 完成形コミット(Permalink):
3ca530e
依存関係バージョン管理戦略
SemVer と Minimal Version Selection
Go Modules は SemVer に準拠しつつ、ビルド時に 最小互換バージョン を選択する MVS 方式を採用します。結果として「ビルドは通るがテストが落ちる」トラブルを減らせます。
A@v1.2.0 requires B@v1.1.0
C@v1.3.0 requires B@v1.0.0
→ B の最小バージョン v1.1.0 が選ばれる
go work
ワークスペース (Go 1.18+)
複数モジュールを横断した開発に便利。モノレポ構成で go.work
を置き、配下のモジュールをまとめてビルド/テストできます。
# Uber-maintained fork (golang/mock レポジトリは 2023-06 にアーカイブ済)
go install go.uber.org/mock/mockgen@latest
mockgen -source=user.go -destination=mock/mock_user.go -package=mock
ポイント: 実装に依存せずテストを高速・安定化できる。
CI/CD でテスト自動化
GitHub Actions サンプル
name: Go CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "^1.22"
- run: go test ./... -v
- run: go vet ./...
- run: go mod tidy && git diff --exit-code
Go vet と go mod tidy を自動チェックに含めることで、レビュー工数を大幅削減。
ベストプラクティスまとめ
go mod tidy
は 保存時フック or CI で強制- 1 パッケージ 1 目的:
internal/
を活用し依存方向を制御 - テストは テーブル駆動 + サブテスト で可読性と網羅性を両立
- モック生成は Makefile / Taskfile に統一コマンドを用意
- ベンチとカバレッジを日次 CI でトレンド監視
まとめ
- Go Modules により依存管理の再現性が向上し、チーム開発の摩擦が激減する。
- TDD を採用すると、実装より先に仕様が固まり、リファクタリング耐性が高まる。
- CI/CD でテストとモジュール整合性を自動チェックすることで、安心してデプロイできるパイプライン を構築可能。
次回は Go での並行処理パターンとパフォーマンスチューニング にフォーカスし、context
, sync
パッケージを使いこなすテクニックを解説します。お楽しみに!
さらなる学習リソース
- Official Docs: https://go.dev/doc/modules
- "Testify" Library: https://github.com/stretchr/testify
- Book: Go言語によるテスト駆動開発
- Video: GopherCon - Dependency Management in Go
行動呼びかけ: 手元で
go mod init
からgo test
まで一連のコマンドを実行し、記事で紹介した CI 設定も GitHub へ push してみましょう。習うより慣れる、それが Go の醍醐味です!