go-testing-code-review

作成者: openshift

Goのテストコードをレビューし、適切なテーブル駆動テスト、アサーション、カバレッジパターンを確認します。*_test.goファイルのレビュー時に使用してください。

npx skills add https://github.com/openshift/lightspeed-operator --skill go-testing-code-review

Go Testing Code Review

Quick Reference

Issue TypeReference
Test structure, namingreferences/structure.md
Mocking, interfacesreferences/mocking.md

Review Checklist

File Organization

  • Test file naming: All test files follow *_test.go convention (e.g., reconciler_test.go for reconciler.go)
  • Co-location: Unit tests are in the same package as the code they test

Standard Go Testing

  • Tests are table-driven with clear case names
  • Subtests use t.Run for parallel execution
  • Test names describe behavior, not implementation
  • Errors include got/want with descriptive message
  • Cleanup registered with t.Cleanup
  • Parallel tests don't share mutable state
  • Mocks use interfaces defined in test file
  • Coverage includes edge cases and error paths

Ginkgo/Gomega (BDD Framework)

  • Use Describe for test suites, Context for scenarios, It for test cases
  • Setup/teardown in BeforeEach/AfterEach for proper isolation
  • Assertions use Expect() with matchers (To(), NotTo())
  • Complex nested contexts organized logically (not too flat, not too deep)
  • Use By() for multi-step test documentation

Kubernetes/Controller Testing

  • Controller tests use envtest for realistic API server testing
  • Test fixtures cleaned up properly (remove finalizers before deletion)
  • Owner references validated on created resources
  • Reconciliation loops tested with eventual consistency (Eventually())
  • Both success and error paths tested for each reconcile function

Critical Patterns

Table-Driven Tests

// BAD - repetitive
func TestAdd(t *testing.T) {
    if Add(1, 2) != 3 {
        t.Error("wrong")
    }
    if Add(0, 0) != 0 {
        t.Error("wrong")
    }
}

// GOOD
func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        want     int
    }{
        {"positive numbers", 1, 2, 3},
        {"zeros", 0, 0, 0},
        {"negative", -1, 1, 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)
            }
        })
    }
}

Error Messages

// BAD
if got != want {
    t.Error("wrong result")
}

// GOOD
if got != want {
    t.Errorf("GetUser(%d) = %v, want %v", id, got, want)
}

// For complex types
if diff := cmp.Diff(want, got); diff != "" {
    t.Errorf("GetUser() mismatch (-want +got):\n%s", diff)
}

Parallel Tests

func TestFoo(t *testing.T) {
    tests := []struct{...}

    for _, tt := range tests {
        tt := tt  // capture (not needed Go 1.22+)
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel()
            // test code
        })
    }
}

Cleanup

// BAD - manual cleanup, skipped on failure
func TestWithTempFile(t *testing.T) {
    f, _ := os.CreateTemp("", "test")
    defer os.Remove(f.Name())  // skipped if test panics
}

// GOOD
func TestWithTempFile(t *testing.T) {
    f, _ := os.CreateTemp("", "test")
    t.Cleanup(func() {
        os.Remove(f.Name())
    })
}

Anti-Patterns

1. Testing Internal Implementation

// BAD - tests private state
func TestUser(t *testing.T) {
    u := NewUser("alice")
    if u.id != 1 {  // testing internal field
        t.Error("wrong id")
    }
}

// GOOD - tests behavior
func TestUser(t *testing.T) {
    u := NewUser("alice")
    if u.ID() != 1 {
        t.Error("wrong ID")
    }
}

2. Shared Mutable State

// BAD - tests interfere with each other
var testDB = setupDB()

func TestA(t *testing.T) {
    t.Parallel()
    testDB.Insert(...)  // race!
}

// GOOD - isolated per test
func TestA(t *testing.T) {
    db := setupTestDB(t)
    t.Cleanup(func() { db.Close() })
    db.Insert(...)
}

3. Assertions Without Context

// BAD
assert.Equal(t, want, got)  // "expected X got Y" - which test?

// GOOD
assert.Equal(t, want, got, "user name after update")

When to Load References

  • Reviewing test file structure → structure.md
  • Reviewing mock implementations → mocking.md

Review Questions

  1. Are tests table-driven with named cases?
  2. Do error messages include input, got, and want?
  3. Are parallel tests isolated (no shared state)?
  4. Is cleanup done via t.Cleanup?
  5. Do tests verify behavior, not implementation?

openshiftのその他のスキル

openshift-docs
openshift
OpenShift Container Platformのドキュメントをマークダウン形式で検索および閲覧します。ユーザーがOpenShiftの機能、設定、インストールなどについて質問する場合に使用します。
triage-leaked-infra
openshift
AWS VPCまたはHyperShift CIからのインフラセットが削除しても安全かどうかを評価します。ユーザーがcleanleakedの出力を貼り付け、「これは削除できますか?」「これは…」と尋ねたときに使用します。
openshift-expert
openshift
OpenShiftプラットフォームとKubernetesのエキスパートであり、クラスターアーキテクチャ、オペレーター、ネットワーキング、ストレージ、トラブルシューティング、CI/CDパイプラインに関する深い知識を持つ。使用…
Konflux Archived PipelineRuns
openshift
アーカイブされたKonflux PipelineRun、TaskRun、およびポッドログにKubeArchive経由でアクセスします。Konflux PipelineRunの結果を確認する際や調査時に自動適用されます。
backport
openshift
メインからリリースブランチへのコミットやPRをバックポートします。ユーザーがバックポート、チェリーピック、ブランチ間の変更の移植を依頼した場合、または解決中に使用します。
rebase
openshift
現在のブランチをベースブランチにリベースし、すべてのコンフリクトを解決して、lint、i18n、ビルドが通ることを確認します。ユーザーがリベース、更新、同期を依頼した場合に使用します…
Build CPO Image
openshift
コントロールプレーンオペレーターのコンテナイメージをビルドしてプッシュします。CPOの変更をライブクラスターにデプロイしてテストする際に自動適用されます。
find-complexity
openshift
サイクロマティック複雑度が高い、長すぎる、またはパラメータが多すぎる関数やメソッドを見つけます。ユーザーが複雑なコードや複雑性を探すよう依頼した場合に使用します。