Testing Guidelines
This document outlines testing conventions and best practices for the Pumbaa project.
Project Structure
Tests are placed alongside the code they test, following Go conventions:
internal/
├── domain/workflow/
│ ├── entity.go
│ ├── entity_test.go
│ ├── preemption.go
│ ├── preemption_test.go
│ ├── monitoring.go
│ └── monitoring_test.go
├── application/workflow/
│ ├── metadata.go
│ ├── metadata_test.go
│ ├── query.go
│ └── query_test.go
└── infrastructure/cromwell/
├── mapper.go
└── mapper_test.go
Test Categories by Layer
| Layer | Test Type | Dependencies | Purpose |
|---|---|---|---|
| Domain | Unit | None | Pure business logic |
| Application | Unit/Integration | Mocked infrastructure | Use case orchestration |
| Infrastructure | Integration | External systems or mocks | API clients, parsers |
| Interfaces | E2E (optional) | Full stack | CLI, TUI validation |
Conventions
Naming
- Test files:
*_test.goin the same package - Test functions:
TestFunctionNameorTestTypeName_MethodName - Subtests: Use descriptive names with
t.Run("description", ...)
Table-Driven Tests
Prefer table-driven tests for multiple scenarios:
func TestCalculatePreemptionSummary(t *testing.T) {
tests := []struct {
name string
workflow *Workflow
want *PreemptionSummary
}{
{
name: "empty workflow",
workflow: &Workflow{},
want: &PreemptionSummary{CostEfficiency: 1.0},
},
{
name: "workflow with preemptions",
workflow: createTestWorkflow(3, 2), // 3 tasks, 2 preempted
want: &PreemptionSummary{TotalPreemptions: 2},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.workflow.CalculatePreemptionSummary()
// assertions
})
}
}
Test Data
Shared fixtures live in the top-level test_data/ directory:
test_data/
├── metadata.json # Cromwell workflow metadata
└── wdl/ # Sample WDL files for indexer/parser tests
├── hello.wdl
├── subworkflow/
└── tasks/
Fixtures used by a single package are kept next to that package (package-local test data), not in test_data/.
Load test data with relative paths:
Assertions
Use standard library comparisons. For complex structs, consider:
if got.TotalTasks != want.TotalTasks {
t.Errorf("TotalTasks = %d, want %d", got.TotalTasks, want.TotalTasks)
}
For deep equality with large structs:
Test Helpers
Create helper functions for common setup:
// createTestCall creates a Call with the given parameters for testing.
func createTestCall(name string, attempt int, preemptible bool) Call {
preemptStr := "false"
if preemptible {
preemptStr = "3" // 3 preemptible attempts
}
return Call{
Name: name,
Attempt: attempt,
Preemptible: preemptStr,
Status: StatusSucceeded,
}
}
Running Tests
# Run all tests
go test ./...
# Run tests with verbose output
go test -v ./...
# Run specific package tests
go test ./internal/domain/workflow/...
# Run with coverage
go test -cover ./...
# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
Test Coverage Goals
| Layer | Target Coverage |
|---|---|
| Domain | 80%+ (critical business logic) |
| Application | 70%+ |
| Infrastructure | 60%+ |
Mocking
For infrastructure dependencies, create interfaces and mock implementations:
// In internal/application/ports/workflow.go
type WorkflowRepository interface {
GetMetadata(ctx context.Context, id string) (*workflow.Workflow, error)
}
// In tests
type mockRepository struct {
metadata *workflow.Workflow
err error
}
func (m *mockRepository) GetMetadata(ctx context.Context, id string) (*workflow.Workflow, error) {
return m.metadata, m.err
}
Agent Tools E2E Test
internal/infrastructure/agents/tools/e2e_test.go contains TestToolsE2E, which exercises every built-in agent action against a real Cromwell server (and, optionally, a real WDL directory). It is the "are the tools actually functional" check.
The test is opt-in: it is skipped unless PUMBAA_TOOLS_E2E=1 is set, because it needs live infrastructure.
PUMBAA_TOOLS_E2E=1 CROMWELL_HOST=http://localhost:8000 \
PUMBAA_WDL_DIR=/path/to/workflows \
go test ./internal/infrastructure/agents/tools/ -run TestToolsE2E -v
Note
CROMWELL_HOST defaults to http://localhost:8000 when unset. PUMBAA_WDL_DIR is optional; without it, the WDL tools are not exercised.
Edge Cases to Test
- Empty inputs: Empty slices, nil pointers, empty strings
- Boundary conditions: Zero, one, many items
- Error conditions: Invalid input, parsing failures
- Unicode/special characters: In workflow names, labels
- Time zones: Timestamp parsing with different formats