Best Practices: Testing Strategies
For: QA engineers and developers
Level: Advanced
Time to read: 35 minutes
Coverage: 80%+ testing target
This guide covers comprehensive testing strategies from unit tests to production monitoring.
Testing Pyramid & Strategy
▲ Cost/Time
╱ ╲
╱ E2E╲ 5-10 tests
╱─────╲ Complete workflows
╱ ╲ Production-like
╱ Integration╲ 30-50 tests
╱─────────────╲ Component integration
╱ ╲ Staging environment
╱ Unit Tests ╲ 100-200 tests
───────────────── Isolated componentsUnit Testing
Activity Unit Tests
func TestValidateOrder(t *testing.T) {
tests := []struct {
name string
input *ValidateOrderInput
wantValid bool
wantErr bool
}{
{
name: "valid order",
input: &ValidateOrderInput{
OrderID: "ORD-123",
Amount: 100.00,
},
wantValid: true,
wantErr: false,
},
{
name: "missing order ID",
input: &ValidateOrderInput{
Amount: 100.00,
},
wantValid: false,
wantErr: true,
},
{
name: "negative amount",
input: &ValidateOrderInput{
OrderID: "ORD-123",
Amount: -50.00,
},
wantValid: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := activities.ValidateOrder(context.Background(), tt.input)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.wantValid, result.Valid)
})
}
}Test Coverage Target
# Run tests with coverage
go test -cover ./...
# Detailed coverage
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# Target: 80%+ coverage
# Priority: Core activities, error paths, edge casesIntegration Testing
Activity + Database Integration
func TestLoadCustomerWithDB(t *testing.T) {
// Setup test database
db := setupTestDB(t)
defer db.Close()
// Insert test data
_, err := db.Exec(`
INSERT INTO customers (id, name, email, tier) VALUES
('cust-1', 'Alice', 'alice@example.com', 'gold')
`)
assert.NoError(t, err)
// Create activity context
ctx := database.WithContext(context.Background(), db)
// Call activity
result, err := activities.LoadCustomer(ctx, &LoadCustomerInput{
CustomerID: "cust-1",
})
// Assertions
assert.NoError(t, err)
assert.Equal(t, "Alice", result.Name)
assert.Equal(t, "gold", result.Tier)
}Workflow Integration Test
func TestOrderWorkflow(t *testing.T) {
s := &testsuite.WorkflowTestSuite{}
env := s.NewTestWorkflowEnvironment()
// Mock activities
env.OnActivity(activities.ValidateOrder, mock.Anything).
Return(&ValidateOrderOutput{Valid: true}, nil)
env.OnActivity(activities.ProcessPayment, mock.Anything).
Return(&ProcessPaymentOutput{
PaymentID: "pay-123",
Status: "success",
}, nil)
// Execute workflow
env.ExecuteWorkflow(workflows.OrderProcessing, OrderInput{
OrderID: "ord-123",
Amount: 100.00,
})
// Assertions
assert.True(t, env.IsWorkflowCompleted())
assert.NoError(t, env.GetWorkflowError())
// Verify activity calls
env.AssertExpectations(t)
}End-to-End Testing
E2E Test Script
#!/bin/bash
set -e
# Deploy application
echo "Deploying application..."
cascade app apply -f test-app.yaml --namespace test-e2e
# Wait for readiness
kubectl wait --for=condition=ready pod -l app=cascade --namespace test-e2e --timeout=300s
# Start workflow
echo "Starting workflow..."
EXECUTION_ID=$(cascade process start \
--app test-app \
--workflow TestOrderFlow \
--input '{"order_id":"e2e-123","amount":100.00}' \
--namespace test-e2e \
--quiet)
echo "Execution ID: $EXECUTION_ID"
# Wait for completion
echo "Waiting for completion..."
cascade process wait $EXECUTION_ID --timeout=5m --namespace test-e2e
# Verify results
echo "Verifying results..."
STATUS=$(cascade process inspect $EXECUTION_ID --json --namespace test-e2e | jq -r '.status')
if [ "$STATUS" != "completed" ]; then
echo "FAIL: Workflow did not complete. Status: $STATUS"
cascade logs $EXECUTION_ID --namespace test-e2e
exit 1
fi
# Check database state
ORDER=$(psql -h postgres.test-e2e -c "SELECT * FROM orders WHERE id='e2e-123'")
if [ -z "$ORDER" ]; then
echo "FAIL: Order not found in database"
exit 1
fi
echo "SUCCESS: E2E test passed"Performance Testing
Load Test
#!/bin/bash
# Test with increasing concurrency
for CONCURRENCY in 10 50 100 500 1000; do
echo "Testing with concurrency: $CONCURRENCY"
# Start N workflows in parallel
for i in $(seq 1 $CONCURRENCY); do
cascade process start \
--app test-app \
--workflow LoadTest \
--input "{\"test_id\":$i}" \
--namespace load-test &
done
wait
# Collect metrics
sleep 10 # Wait for completion
COMPLETED=$(cascade process list \
--namespace load-test \
--status completed \
--since 1m | wc -l)
AVG_LATENCY=$(cascade metrics workflow \
--namespace load-test \
--period 1m \
--percentile p50 \
--json | jq '.duration_ms')
echo " Completed: $COMPLETED, P50: ${AVG_LATENCY}ms"
# Check for errors
ERRORS=$(cascade process list \
--namespace load-test \
--status failed \
--since 1m | wc -l)
if [ $ERRORS -gt 0 ]; then
echo " WARNING: $ERRORS failures detected"
fi
doneBenchmark Activity
func BenchmarkValidateOrder(b *testing.B) {
input := &ValidateOrderInput{
OrderID: "ORD-123",
Amount: 100.00,
}
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
activities.ValidateOrder(ctx, input)
}
// Run: go test -bench=. -benchmem
// Output: BenchmarkValidateOrder-8 100000 11234 ns/op 1024 B/op 15 allocs/op
}Chaos Testing
Workflow Under Failure
# chaos-test.yaml
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
name: cascade-pod-chaos
spec:
action: kill
mode: fixed
value: 1
selector:
namespaces:
- cascade
labelSelectors:
app: cascade-worker
scheduler:
cron: "@hourly"Database Latency Injection
# Add 100ms latency to database
kubectl patch service postgres -p '{"spec":{"ports":[{"port":5432,"protocol":"TCP","targetPort":5432,"nodePort":32300}]}}'
tc qdisc add dev eth0 root netem delay 100msQuality Gates
Pre-Deployment Checks
#!/bin/bash
# Code quality
go vet ./...
go fmt ./...
# Coverage
COVERAGE=$(go test -cover ./... | grep total | awk '{print $3}' | tr -d '%')
if [ "$COVERAGE" -lt 80 ]; then
echo "FAIL: Coverage $COVERAGE% < 80%"
exit 1
fi
# Security scanning
gosec ./...
# Linting
golangci-lint run
# Run tests
go test -v -race -timeout 10m ./...
echo "SUCCESS: All quality gates passed"Testing Checklist
Unit Tests
- Happy path tests
- Error path tests
- Boundary conditions
- Null/empty inputs
- Edge cases
Integration Tests
- Database operations
- External API mocks
- Error scenarios
- Data consistency
- Transaction handling
E2E Tests
- Happy path workflow
- Error recovery
- Timeouts
- Concurrent workflows
- Data validation
Performance Tests
- Load tests (100+ concurrent)
- Latency benchmarks
- Memory profiling
- Database query performance
- Cache effectiveness
Security Tests
- Authentication
- Authorization
- Data isolation
- SQL injection prevention
- Sensitive data redaction
Best Practices
✅ DO:
- Write tests first (TDD)
- Test error paths
- Mock external dependencies
- Use table-driven tests
- Run tests in CI/CD
- Monitor test coverage
- Test at scale
- Document test strategy
❌ DON’T:
- Skip tests to save time
- Only test happy paths
- Couple tests to implementation
- Write tests after code
- Ignore flaky tests
- Test third-party libraries
- Mock too much
Updated: October 29, 2025
Version: 1.0
Coverage Target: 80%+
Last updated on