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
آخر نشاط في المصدر
١٠ يناير ٢٠٢٦ في ١٢:٢٧
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٢٬٠٥٨
التفرعات
٢٩٣

خيارات التثبيت

يُحدَّد 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