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-expert
openshift
OpenShift 플랫폼 및 Kubernetes 전문가로, 클러스터 아키텍처, 오퍼레이터, 네트워킹, 스토리지, 문제 해결 및 CI/CD 파이프라인에 대한 깊은 지식을 보유하고 있습니다. 사용…
official
find-token
openshift
숨겨진 인증 토큰을 찾습니다. find-token 스크립트를 실행하여 고유 토큰을 검색하세요.
official
code-review
openshift
풀 리퀘스트의 코드 품질, 정확성, 프로젝트 규칙을 검토합니다. 사용자가 PR 검토, 코드 리뷰, 또는 변경 사항 확인을 요청할 때 사용하세요.
official
css-review
openshift
CSS 코딩 스타일, PatternFly 토큰 사용, 모범 사례를 검토합니다. 사용자가 CSS 검토, 스타일 확인, 또는 CSS 파일 감사를 요청할 때 사용하세요.
official
review-readmes
openshift
리포지토리의 모든 README.md 파일을 검토하여 오타, 오류, 오래된 정보를 찾습니다. 사용자가 README 검토, 문서 정확성 확인 등을 요청할 때 사용합니다.
official
review-skills
openshift
프로젝트 AI 스킬의 중복, 오래된 참조, 오류 및 구조적 문제를 검토합니다. 사용자가 스킬 검토, 스킬 감사, 확인 등을 요청할 때 사용하세요.
official
test
openshift
태그로 필터링된 종단 간 테스트를 실행합니다. 사용자가 테스트 실행, Playwright 실행, 또는 @core나 @attach 같은 특정 기능 태그를 테스트하도록 요청할 때 사용하세요.
official
unused-exports
openshift
다른 파일에서 가져오지 않은 내보낸 심볼을 찾습니다. 사용자가 "내보내기 확인", "사용되지 않는 내보내기"라고 말하거나 내보내기를 정리하도록 요청할 때 사용하세요.
official