Skip to main content

go-testing

Go testing patterns for Gentleman.Dots, including Bubbletea TUI testing. Trigger: When writing Go tests, using teatest, or adding test coverage.

소스 정보

저장소
Gentleman-Programming/Gentleman.Dots
최근 소스 활동
2026년 1월 10일 12:27
감지된 SKILL.md 언어
영어
스타
2,058
포크
293

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
go-testing
description
Go testing patterns for Gentleman.Dots, including Bubbletea TUI testing. Trigger: When writing Go tests, using teatest, or adding test coverage.
license
Apache-2.0
metadata
{"author":"gentleman-programming","version":"1.0"}
## When to Use Use this skill when: - Writing Go unit tests - Testing Bubbletea TUI components - Creating table-driven tests - Adding integration tests - Using golden file testing --- ## Critical Patterns ### Pattern 1: Table-Driven Tests Standard Go pattern for multiple test cases: ```go func TestSomething(t *testing.T) { tests := []struct { name string input string expected string wantErr bool }{ { name: "valid input", input: "hello", expected: "HELLO", wantErr: false, }, { name: "empty input", input: "", expected: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := ProcessInput(tt.input) if (err != nil) != tt.wantErr { t.Errorf("error = %v, wantErr %v", err, tt.wantErr) return } if result != tt.expected { t.Errorf("got %q, want %q", result, tt.expected) } }) } } ``` ### Pattern 2: Bubbletea Model Testing Test Model state transitions directly: ```go func TestModelUpdate(t *testing.T) { m := NewModel() // Simulate key press newModel, _ := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) m = newModel.(Model) if m.Screen != ScreenMainMenu { t.Errorf("expected ScreenMainMenu, got %v", m.Screen) } } ``` ### Pattern 3: Teatest Integration Tests Use Charmbracelet's teatest for TUI testing: ```go func TestInteractiveFlow(t *testing.T) { m := NewModel() tm := teatest.NewTestModel(t, m) // Send keys tm.Send(tea.KeyMsg{Type: tea.KeyEnter}) tm.Send(tea.KeyMsg{Type: tea.KeyDown}) tm.Send(tea.KeyMsg{Type: tea.KeyEnter}) // Wait for model to update tm.WaitFinished(t, teatest.WithDuration(time.Second)) // Get final model finalModel := tm.FinalModel(t).(Model) if finalModel.Screen != ExpectedScreen { t.Errorf("wrong screen: got %v", finalModel.Screen) } } ``` ### Pattern 4: Golden File Testing Compare output against saved "golden" files: ```go func TestOSSelectGolden(t *testing.T) { m := NewModel() m.Screen = ScreenOSSelect m.Width = 80 m.Height = 24 output := m.View() golden := filepath.Join("testdata", "TestOSSelectGolden.golden") if *update { os.WriteFile(golden, []byte(output), 0644) } expected, _ := os.ReadFile(golden) if output != string(expected) { t.Errorf("output doesn't match golden file") } } ``` --- ## Decision Tree ``` Testing a function? ├── Pure function? → Table-driven test ├── Has side effects? → Mock dependencies ├── Returns error? → Test both success and error cases └── Complex logic? → Break into smaller testable units Testing TUI component? ├── State change? → Test Model.Update() directly ├── Full flow? → Use teatest.NewTestModel() ├── Visual output? → Use golden file testing └── Key handling? → Send tea.KeyMsg Testing system/exec? ├── Mock os/exec? → Use interface + mock ├── Real commands? → Integration test with --short skip └── File operations? → Use t.TempDir() ``` --- ## Code Examples ### Example 1: Testing Key Navigation ```go func TestCursorNavigation(t *testing.T) { tests := []struct { name string startPos int key string endPos int numOptions int }{ {"down from 0", 0, "j", 1, 5}, {"up from 1", 1, "k", 0, 5}, {"down at bottom", 4, "j", 4, 5}, // stays at bottom {"up at top", 0, "k", 0, 5}, // stays at top } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := NewModel() m.Cursor = tt.startPos // Set up options... newModel, _ := m.Update(tea.KeyMsg{ Type: tea.KeyRunes, Runes: []rune(tt.key), }) m = newModel.(Model) if m.Cursor != tt.endPos { t.Errorf("cursor = %d, want %d", m.Cursor, tt.endPos) } }) } } ``` ### Example 2: Testing Screen Transitions ```go func TestScreenTransitions(t *testing.T) { tests := []struct { name string startScreen Screen action tea.Msg expectScreen Screen }{ { name: "welcome to main menu", startScreen: ScreenWelcome, action: tea.KeyMsg{Type: tea.KeyEnter}, expectScreen: ScreenMainMenu, }, { name: "escape from OS select", startScreen: ScreenOSSelect, action: tea.KeyMsg{Type: tea.KeyEsc}, expectScreen: ScreenMainMenu, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := NewModel() m.Screen = tt.startScreen newModel, _ := m.Update(tt.action) m = newModel.(Model) if m.Screen != tt.expectScreen { t.Errorf("screen = %v, want %v", m.Screen, tt.expectScreen) } }) } } ``` ### Example 3: Testing Trainer Exercises ```go func TestExerciseValidation(t *testing.T) { exercise := &Exercise{ Solutions: []string{"w", "W", "e"}, Optimal: "w", } tests := []struct { input string valid bool optimal bool }{ {"w", true, true}, {"W", true, false}, {"e", true, false}, {"x", false, false}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { valid := ValidateAnswer(exercise, tt.input) optimal := IsOptimalAnswer(exercise, tt.input) if valid != tt.valid { t.Errorf("valid = %v, want %v", valid, tt.valid) } if optimal != tt.optimal { t.Errorf("optimal = %v, want %v", optimal, tt.optimal) } }) } } ``` ### Example 4: Mocking System Info ```go func TestWithMockedSystem(t *testing.T) { m := NewModel() // Mock system info for testing m.SystemInfo = &system.SystemInfo{ OS: system.OSMac, IsARM: true, HasBrew: true, HomeDir: t.TempDir(), } // Now test with controlled environment m.SetupInstallSteps() // Verify expected steps hasHomebrew := false for _, step := range m.Steps { if step.ID == "homebrew" { hasHomebrew = true } } if hasHomebrew { t.Error("should not have homebrew step when HasBrew=true") } } ``` --- ## Test File Organization ``` installer/internal/tui/ ├── model.go ├── model_test.go # Model tests ├── update.go ├── update_test.go # Update handler tests ├── view.go ├── view_test.go # View rendering tests ├── teatest_test.go # Teatest integration tests ├── comprehensive_test.go # Full flow tests ├── testdata/ │ ├── TestOSSelectGolden.golden │ └── TestViewGolden.golden └── trainer/ ├── types.go ├── types_test.go ├── exercises.go ├── exercises_test.go └── simulator_test.go ``` --- ## Commands ```bash go test ./... # Run all tests go test -v ./internal/tui/... # Verbose TUI tests go test -run TestNavigation # Run specific test go test -cover ./... # With coverage go test -update ./... # Update golden files go test -short ./... # Skip integration tests ``` --- ## Resources - **TUI Tests**: See `installer/internal/tui/*_test.go` - **Trainer Tests**: See `installer/internal/tui/trainer/*_test.go` - **System Tests**: See `installer/internal/system/*_test.go` - **Golden Files**: See `installer/internal/tui/testdata/` - **Teatest Docs**: https://github.com/charmbracelet/bubbletea/tree/master/teatest
GitHub에서 보기