mit einem Klick
golang-testing
テスト駆動開発とGoコードの高品質を保証するための包括的なテスト戦略。
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Menü
テスト駆動開発とGoコードの高品質を保証するための包括的なテスト戦略。
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Basierend auf der SOC-Berufsklassifikation
| name | golang-testing |
| description | テスト駆動開発とGoコードの高品質を保証するための包括的なテスト戦略。 |
テスト駆動開発(TDD)とGoコードの高品質を保証するための包括的なテスト戦略。
失敗するテストを書き、実装し、リファクタリングするサイクルに従います。
// 1. テストを書く(失敗)
func TestCalculateTotal(t *testing.T) {
total := CalculateTotal([]float64{10.0, 20.0, 30.0})
want := 60.0
if total != want {
t.Errorf("got %f, want %f", total, want)
}
}
// 2. 実装する(テストを通す)
func CalculateTotal(prices []float64) float64 {
var total float64
for _, price := range prices {
total += price
}
return total
}
// 3. リファクタリング
// テストを壊さずにコードを改善
複数のケースを体系的にテストします。
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -2, -3, -5},
{"mixed signs", -2, 3, 1},
{"zeros", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.want {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, got, tt.want)
}
})
}
}
サブテストを使用した論理的なテストの構成。
func TestUser(t *testing.T) {
t.Run("validation", func(t *testing.T) {
t.Run("empty email", func(t *testing.T) {
user := User{Email: ""}
if err := user.Validate(); err == nil {
t.Error("expected validation error")
}
})
t.Run("valid email", func(t *testing.T) {
user := User{Email: "test@example.com"}
if err := user.Validate(); err != nil {
t.Errorf("unexpected error: %v", err)
}
})
})
t.Run("serialization", func(t *testing.T) {
// 別のテストグループ
})
}
mypackage/
├── user.go
├── user_test.go # ユニットテスト
├── integration_test.go # 統合テスト
├── testdata/ # テストフィクスチャ
│ ├── valid_user.json
│ └── invalid_user.json
└── export_test.go # 内部テスト用の非公開エクスポート
// user_test.go - 同じパッケージ(ホワイトボックステスト)
package user
func TestInternalFunction(t *testing.T) {
// 内部をテストできる
}
// user_external_test.go - 外部パッケージ(ブラックボックステスト)
package user_test
import "myapp/user"
func TestPublicAPI(t *testing.T) {
// 公開APIのみをテスト
}
func TestBasicAssertions(t *testing.T) {
// 等価性
got := Calculate()
want := 42
if got != want {
t.Errorf("got %d, want %d", got, want)
}
// エラーチェック
_, err := Process()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// nil チェック
result := GetResult()
if result == nil {
t.Fatal("expected non-nil result")
}
}
// ヘルパーとしてマーク(スタックトレースに表示されない)
func assertEqual(t *testing.T, got, want interface{}) {
t.Helper()
if got != want {
t.Errorf("got %v, want %v", got, want)
}
}
func assertNoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
// 使用例
func TestWithHelpers(t *testing.T) {
result, err := Process()
assertNoError(t, err)
assertEqual(t, result.Status, "success")
}
import "reflect"
func assertDeepEqual(t *testing.T, got, want interface{}) {
t.Helper()
if !reflect.DeepEqual(got, want) {
t.Errorf("got %+v, want %+v", got, want)
}
}
func TestStructEquality(t *testing.T) {
got := User{Name: "Alice", Age: 30}
want := User{Name: "Alice", Age: 30}
assertDeepEqual(t, got, want)
}
// 本番コード
type UserStore interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
type UserService struct {
store UserStore
}
// テストコード
type MockUserStore struct {
users map[string]*User
err error
}
func (m *MockUserStore) GetUser(id string) (*User, error) {
if m.err != nil {
return nil, m.err
}
return m.users[id], nil
}
func (m *MockUserStore) SaveUser(user *User) error {
if m.err != nil {
return m.err
}
m.users[user.ID] = user
return nil
}
// テスト
func TestUserService(t *testing.T) {
mock := &MockUserStore{
users: make(map[string]*User),
}
service := &UserService{store: mock}
// サービスをテスト...
}
// プロダクションコード - 時間を注入可能にする
type TimeProvider interface {
Now() time.Time
}
type RealTime struct{}
func (RealTime) Now() time.Time {
return time.Now()
}
type Service struct {
time TimeProvider
}
// テストコード
type MockTime struct {
current time.Time
}
func (m MockTime) Now() time.Time {
return m.current
}
func TestTimeDependent(t *testing.T) {
mockTime := MockTime{
current: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
}
service := &Service{time: mockTime}
// 固定時間でテスト...
}
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
type MockHTTPClient struct {
response *http.Response
err error
}
func (m *MockHTTPClient) Do(req *http.Request) (*http.Response, error) {
return m.response, m.err
}
func TestAPICall(t *testing.T) {
mockClient := &MockHTTPClient{
response: &http.Response{
StatusCode: 200,
Body: io.NopCloser(strings.NewReader(`{"status":"ok"}`)),
},
}
api := &APIClient{client: mockClient}
// APIクライアントをテスト...
}
func TestHandler(t *testing.T) {
handler := http.HandlerFunc(MyHandler)
req := httptest.NewRequest("GET", "/users/123", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
// ステータスコードをチェック
if rec.Code != http.StatusOK {
t.Errorf("got status %d, want %d", rec.Code, http.StatusOK)
}
// レスポンスボディをチェック
var response map[string]interface{}
if err := json.NewDecoder(rec.Body).Decode(&response); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if response["id"] != "123" {
t.Errorf("got id %v, want 123", response["id"])
}
}
func TestAuthMiddleware(t *testing.T) {
// ダミーハンドラー
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
// ミドルウェアでラップ
handler := AuthMiddleware(nextHandler)
tests := []struct {
name string
token string
wantStatus int
}{
{"valid token", "valid-token", http.StatusOK},
{"invalid token", "invalid", http.StatusUnauthorized},
{"no token", "", http.StatusUnauthorized},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
if tt.token != "" {
req.Header.Set("Authorization", "Bearer "+tt.token)
}
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != tt.wantStatus {
t.Errorf("got status %d, want %d", rec.Code, tt.wantStatus)
}
})
}
}
func TestAPIIntegration(t *testing.T) {
// テストサーバーを作成
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]string{
"message": "hello",
})
}))
defer server.Close()
// 実際のHTTPリクエストを行う
resp, err := http.Get(server.URL)
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
// レスポンスを検証
var result map[string]string
json.NewDecoder(resp.Body).Decode(&result)
if result["message"] != "hello" {
t.Errorf("got %s, want hello", result["message"])
}
}
func TestUserRepository(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
tests := []struct {
name string
fn func(*testing.T, *sql.DB)
}{
{"create user", testCreateUser},
{"find user", testFindUser},
{"update user", testUpdateUser},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tx, err := db.Begin()
if err != nil {
t.Fatal(err)
}
defer tx.Rollback() // テスト後にロールバック
tt.fn(t, tx)
})
}
}
func setupTestDB(t *testing.T) *sql.DB {
t.Helper()
db, err := sql.Open("postgres", "postgres://localhost/test")
if err != nil {
t.Fatalf("failed to connect: %v", err)
}
// スキーマを移行
if err := runMigrations(db); err != nil {
t.Fatalf("migrations failed: %v", err)
}
return db
}
func seedTestData(t *testing.T, db *sql.DB) {
t.Helper()
fixtures := []string{
`INSERT INTO users (id, email) VALUES ('1', 'test@example.com')`,
`INSERT INTO posts (id, user_id, title) VALUES ('1', '1', 'Test Post')`,
}
for _, query := range fixtures {
if _, err := db.Exec(query); err != nil {
t.Fatalf("failed to seed data: %v", err)
}
}
}
func BenchmarkCalculation(b *testing.B) {
for i := 0; i < b.N; i++ {
Calculate(100)
}
}
// メモリ割り当てを報告
func BenchmarkWithAllocs(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
ProcessData([]byte("test data"))
}
}
func BenchmarkEncoding(b *testing.B) {
data := generateTestData()
b.Run("json", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
json.Marshal(data)
}
})
b.Run("gob", func(b *testing.B) {
b.ReportAllocs()
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
b.ResetTimer()
for i := 0; i < b.N; i++ {
enc.Encode(data)
buf.Reset()
}
})
}
// 実行: go test -bench=. -benchmem
func BenchmarkStringConcat(b *testing.B) {
b.Run("operator", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = "hello" + " " + "world"
}
})
b.Run("fmt.Sprintf", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = fmt.Sprintf("%s %s", "hello", "world")
}
})
b.Run("strings.Builder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
sb.WriteString("hello")
sb.WriteString(" ")
sb.WriteString("world")
_ = sb.String()
}
})
}
func FuzzParseInput(f *testing.F) {
// シードコーパス
f.Add("hello")
f.Add("world")
f.Add("123")
f.Fuzz(func(t *testing.T, input string) {
// パースがパニックしないことを確認
result, err := ParseInput(input)
// エラーがあっても、nilでないか一貫性があることを確認
if err == nil && result == nil {
t.Error("got nil result with no error")
}
})
}
func FuzzJSONParsing(f *testing.F) {
f.Add([]byte(`{"name":"test","age":30}`))
f.Add([]byte(`{"name":"","age":0}`))
f.Fuzz(func(t *testing.T, data []byte) {
var user User
err := json.Unmarshal(data, &user)
// JSONがデコードされる場合、再度エンコードできるべき
if err == nil {
_, err := json.Marshal(user)
if err != nil {
t.Errorf("marshal failed after successful unmarshal: %v", err)
}
}
})
}
# カバレッジを実行してHTMLレポートを生成
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
# パッケージごとのカバレッジを表示
go test -cover ./...
# 詳細なカバレッジ
go test -coverprofile=coverage.out -covermode=atomic ./...
// Good: テスタブルなコード
func ProcessData(data []byte) (Result, error) {
if len(data) == 0 {
return Result{}, ErrEmptyData
}
// 各分岐をテスト可能
if isValid(data) {
return parseValid(data)
}
return parseInvalid(data)
}
// 対応するテストが全分岐をカバー
func TestProcessData(t *testing.T) {
tests := []struct {
name string
data []byte
wantErr bool
}{
{"empty data", []byte{}, true},
{"valid data", []byte("valid"), false},
{"invalid data", []byte("invalid"), false},
}
// ...
}
//go:build integration
// +build integration
package myapp_test
import "testing"
func TestDatabaseIntegration(t *testing.T) {
// 実際のDBを必要とするテスト
}
# 統合テストを実行
go test -tags=integration ./...
# 統合テストを除外
go test ./...
import "github.com/testcontainers/testcontainers-go"
func setupPostgres(t *testing.T) *sql.DB {
ctx := context.Background()
req := testcontainers.ContainerRequest{
Image: "postgres:15",
ExposedPorts: []string{"5432/tcp"},
Env: map[string]string{
"POSTGRES_PASSWORD": "test",
"POSTGRES_DB": "testdb",
},
}
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
container.Terminate(ctx)
})
// コンテナに接続
// ...
return db
}
func TestParallel(t *testing.T) {
tests := []struct {
name string
fn func(*testing.T)
}{
{"test1", testCase1},
{"test2", testCase2},
{"test3", testCase3},
}
for _, tt := range tests {
tt := tt // ループ変数をキャプチャ
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // このテストを並列実行
tt.fn(t)
})
}
}
func TestWithResourceLimit(t *testing.T) {
// 同時に5つのテストのみ
sem := make(chan struct{}, 5)
tests := generateManyTests()
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
sem <- struct{}{} // 獲得
defer func() { <-sem }() // 解放
tt.fn(t)
})
}
}
# 基本テスト
go test ./...
go test -v ./... # 詳細出力
go test -run TestSpecific ./... # 特定のテストを実行
# カバレッジ
go test -cover ./...
go test -coverprofile=coverage.out ./...
# レースコンディション
go test -race ./...
# ベンチマーク
go test -bench=. ./...
go test -bench=. -benchmem ./...
go test -bench=. -cpuprofile=cpu.prof ./...
# ファジング
go test -fuzz=FuzzTest
# 統合テスト
go test -tags=integration ./...
# JSONフォーマット(CI統合用)
go test -json ./...
# テストタイムアウト
go test -timeout 30s ./...
# 短時間テスト(長時間テストをスキップ)
go test -short ./...
# ビルドキャッシュのクリア
go clean -testcache
go test ./...
// Good: テーブル駆動テストで繰り返しを削減
func TestValidation(t *testing.T) {
tests := []struct {
input string
valid bool
}{
{"valid@email.com", true},
{"invalid-email", false},
{"", false},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
err := Validate(tt.input)
if (err == nil) != tt.valid {
t.Errorf("Validate(%q) error = %v, want valid = %v",
tt.input, err, tt.valid)
}
})
}
}
// Good: テストデータを testdata/ ディレクトリに配置
func TestLoadConfig(t *testing.T) {
data, err := os.ReadFile("testdata/config.json")
if err != nil {
t.Fatal(err)
}
config, err := ParseConfig(data)
// ...
}
func TestWithCleanup(t *testing.T) {
// リソースを設定
file, err := os.CreateTemp("", "test")
if err != nil {
t.Fatal(err)
}
// クリーンアップを登録(deferに似ているが、サブテストで動作)
t.Cleanup(func() {
os.Remove(file.Name())
})
// テストを続ける...
}
// Bad: 不明確なエラー
if result != expected {
t.Error("wrong result")
}
// Good: コンテキスト付きエラー
if result != expected {
t.Errorf("Calculate(%d) = %d; want %d", input, result, expected)
}
// Better: ヘルパー関数の使用
assertEqual(t, result, expected, "Calculate(%d)", input)
// Bad: 外部状態に依存
func TestBadDependency(t *testing.T) {
result := GetUserFromDatabase("123") // 実際のDBを使用
// テストが壊れやすく遅い
}
// Good: 依存を注入
func TestGoodDependency(t *testing.T) {
mockDB := &MockDatabase{
users: map[string]User{"123": {ID: "123"}},
}
result := GetUser(mockDB, "123")
}
// Bad: テスト間で状態を共有
var sharedCounter int
func TestShared1(t *testing.T) {
sharedCounter++
// テストの順序に依存
}
// Good: 各テストを独立させる
func TestIndependent(t *testing.T) {
counter := 0
counter++
// 他のテストに影響しない
}
// Bad: エラーを無視
func TestIgnoreError(t *testing.T) {
result, _ := Process()
if result != expected {
t.Error("wrong result")
}
}
// Good: エラーをチェック
func TestCheckError(t *testing.T) {
result, err := Process()
if err != nil {
t.Fatalf("Process() error = %v", err)
}
if result != expected {
t.Errorf("got %v, want %v", result, expected)
}
}
| コマンド/パターン | 目的 |
|---|---|
go test ./... | すべてのテストを実行 |
go test -v | 詳細出力 |
go test -cover | カバレッジレポート |
go test -race | レースコンディション検出 |
go test -bench=. | ベンチマークを実行 |
t.Run() | サブテスト |
t.Helper() | テストヘルパー関数 |
t.Parallel() | テストを並列実行 |
t.Cleanup() | クリーンアップを登録 |
testdata/ | テストフィクスチャ用ディレクトリ |
-short | 長時間テストをスキップ |
-tags=integration | ビルドタグでテストを実行 |
覚えておいてください: 良いテストは高速で、信頼性があり、保守可能で、明確です。複雑さより明確さを目指してください。
Use after a complex task, failure, or when reviewing what was learned. Teaches how to write growth logs that extract reusable patterns — not diary entries.
Design a goal-oriented agent loop, and review it for the ways loops go wrong — spinning and burning tokens, Goodhart-gaming the verifier, or running a wrong answer to completion. Two actions: (1) WRITE a loop — gate whether to build it, define a machine-decidable goal, pick the loop type, pick a skeleton; (2) REVIEW a loop — run it past five failure modes plus decidability, boundaries, fallback, judge independence, and keep-judgment-with-the-human red lines. Use when designing an autonomous agent loop, or when you already have one and worry it will spin, cheat, or run a wrong answer to the end. Complements the mechanism-layer loop skills (autonomous-loops, continuous-agent-loop) by covering the judgment layer they don't. 中文触发:写 loop、设计 loop、做一个 loop、检查 loop 对不对、loop 体检、loop 会不会跑飞、可判定目标、五个崩法、plan build judge。English triggers: design an agent loop, write a loop, check a loop, loop review, prevent a runaway loop, goal-oriented loop, decidable goal, plan/build/judge.
Stop hook that blocks Claude from finishing until quality checks pass. Detects rationalization patterns (surface text heuristics), stale learning logs (filesystem mtime), and low disk space. Complements self-audit by mechanically enforcing learning capture habits.
React Native and Expo app patterns — Expo Router navigation, state separation (server/client/route/form), TanStack Query data fetching with Zod, performant lists, NativeWind/StyleSheet styling, native APIs, and secure storage. Use when building or editing React Native / Expo screens, components, navigation, or data layers.
Instinct-based learning system that observes sessions via hooks, creates atomic instincts with confidence scoring, and evolves them into skills/commands/agents. v2.1 adds project-scoped instincts to prevent cross-project contamination.
Use this skill when writing new features, fixing bugs, or refactoring code. Enforces test-driven development with 80%+ coverage including unit, integration, and E2E tests.