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.

Informations de source

Dépôt
Gentleman-Programming/Gentleman.Dots
Dernière activité de la source
10 janvier 2026 à 12:27
Langue détectée de SKILL.md
anglais
Étoiles
2 047
Forks
292

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
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
Voir sur GitHub