Awesome-omni-skill testing-strategies

Testing strategies, patterns, and best practices for production code

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/testing-security/testing-strategies" ~/.claude/skills/diegosouzapw-awesome-omni-skill-testing-strategies && rm -rf "$T"
manifest: skills/testing-security/testing-strategies/SKILL.md
source content

Testing Strategies Skill

Overview

This skill provides comprehensive guidelines for implementing effective testing strategies across unit, integration, and end-to-end testing.

Testing Pyramid

       /\
      /  \  E2E Tests (10%)
     /____\  Slow, expensive, critical paths
    /      \
   /________\  Integration Tests (30%)
  /          \  Component interactions, APIs
 /____________\
/              \
Unit Tests (60%)  Fast, cheap, comprehensive

Unit Testing

1. Principles

// FIRST Principles:
// F - Fast (milliseconds)
// I - Isolated (no dependencies)
// R - Repeatable (same result every time)
// S - Self-verifying (no manual checking)
// T - Timely (write with code)

// AAA Pattern:
// Arrange - Set up test data and mocks
// Act     - Execute the code under test
// Assert  - Verify the results

func TestUserService_CreateUser(t *testing.T) {
    // Arrange
    mockRepo := &mockUserRepository{}
    mockRepo.On("Create", mock.Anything, mock.Anything).Return(nil)
    
    service := NewUserService(mockRepo)
    ctx := context.Background()
    req := CreateUserRequest{
        Email: "user@example.com",
        Name:  "John Doe",
    }
    
    // Act
    user, err := service.CreateUser(ctx, req)
    
    // Assert
    assert.NoError(t, err)
    assert.NotNil(t, user)
    assert.Equal(t, "user@example.com", user.Email)
    mockRepo.AssertExpectations(t)
}

2. Table-Driven Tests

func TestCalculateDiscount(t *testing.T) {
    tests := []struct {
        name           string
        price          float64
        discountCode   string
        expectedPrice  float64
        expectedError  bool
    }{
        {
            name:          "valid 10% discount",
            price:         100.0,
            discountCode:  "SAVE10",
            expectedPrice: 90.0,
            expectedError: false,
        },
        {
            name:          "valid 20% discount",
            price:         100.0,
            discountCode:  "SAVE20",
            expectedPrice: 80.0,
            expectedError: false,
        },
        {
            name:          "invalid discount code",
            price:         100.0,
            discountCode:  "INVALID",
            expectedPrice: 0.0,
            expectedError: true,
        },
        {
            name:          "negative price",
            price:         -50.0,
            discountCode:  "SAVE10",
            expectedPrice: 0.0,
            expectedError: true,
        },
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result, err := CalculateDiscount(tt.price, tt.discountCode)
            
            if tt.expectedError {
                assert.Error(t, err)
            } else {
                assert.NoError(t, err)
                assert.InDelta(t, tt.expectedPrice, result, 0.01)
            }
        })
    }
}

3. Mocking

// Interface for dependency injection
type EmailService interface {
    Send(ctx context.Context, to string, subject string, body string) error
}

// Mock implementation
type mockEmailService struct {
    mock.Mock
}

func (m *mockEmailService) Send(ctx context.Context, to string, subject string, body string) error {
    args := m.Called(ctx, to, subject, body)
    return args.Error(0)
}

// Test with mock
func TestNotificationService_NotifyUser(t *testing.T) {
    mockEmail := &mockEmailService{}
    mockEmail.On("Send", 
        mock.Anything, 
        "user@example.com",
        "Welcome!",
        mock.Anything,
    ).Return(nil)
    
    service := NewNotificationService(mockEmail)
    err := service.NotifyUser(context.Background(), "user@example.com", "Welcome!")
    
    assert.NoError(t, err)
    mockEmail.AssertCalled(t, "Send", 
        mock.Anything, 
        "user@example.com",
        "Welcome!",
        mock.Anything,
    )
}

Integration Testing

1. Database Integration

func TestUserRepository_Create(t *testing.T) {
    // Setup test database
    ctx := context.Background()
    db := setupTestDB(t)
    defer teardownTestDB(t, db)
    
    repo := NewUserRepository(db)
    
    // Test
    user := &User{
        Email: "test@example.com",
        Name:  "Test User",
    }
    
    err := repo.Create(ctx, user)
    require.NoError(t, err)
    assert.NotEmpty(t, user.ID)
    
    // Verify in database
    var count int
    err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM users WHERE email = $1", user.Email).Scan(&count)
    require.NoError(t, err)
    assert.Equal(t, 1, count)
}

// Test database setup
type testDB struct {
    *sql.DB
    name string
}

func setupTestDB(t *testing.T) *testDB {
    // Create isolated test database
    dbName := fmt.Sprintf("test_%s_%d", t.Name(), time.Now().Unix())
    
    connStr := fmt.Sprintf("host=localhost user=postgres password=secret dbname=%s sslmode=disable", dbName)
    db, err := sql.Open("postgres", connStr)
    require.NoError(t, err)
    
    // Run migrations
    runMigrations(db)
    
    return &testDB{DB: db, name: dbName}
}

func teardownTestDB(t *testing.T, db *testDB) {
    db.Close()
    // Drop test database
}

2. HTTP API Testing

// API integration test
import request from 'supertest';
import { app } from '../src/app';
import { setupTestDB, teardownTestDB } from './helpers/database';

describe('POST /api/users', () => {
    beforeAll(async () => {
        await setupTestDB();
    });
    
    afterAll(async () => {
        await teardownTestDB();
    });
    
    it('should create a new user', async () => {
        const response = await request(app)
            .post('/api/users')
            .send({
                email: 'test@example.com',
                name: 'Test User'
            })
            .expect(201);
        
        expect(response.body).toMatchObject({
            email: 'test@example.com',
            name: 'Test User'
        });
        expect(response.body.id).toBeDefined();
    });
    
    it('should return 400 for invalid email', async () => {
        const response = await request(app)
            .post('/api/users')
            .send({
                email: 'invalid-email',
                name: 'Test User'
            })
            .expect(400);
        
        expect(response.body.error).toBeDefined();
    });
    
    it('should return 409 for duplicate email', async () => {
        // Create user first
        await request(app)
            .post('/api/users')
            .send({
                email: 'duplicate@example.com',
                name: 'Test User'
            });
        
        // Try to create again
        await request(app)
            .post('/api/users')
            .send({
                email: 'duplicate@example.com',
                name: 'Test User'
            })
            .expect(409);
    });
});

3. External Service Testing

// Use test containers for external services
func TestWithRedis(t *testing.T) {
    ctx := context.Background()
    
    // Start Redis container
    req := testcontainers.ContainerRequest{
        Image:        "redis:latest",
        ExposedPorts: []string{"6379/tcp"},
        WaitingFor:   wait.ForListeningPort("6379/tcp"),
    }
    
    redisC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
        ContainerRequest: req,
        Started:          true,
    })
    require.NoError(t, err)
    defer redisC.Terminate(ctx)
    
    // Connect to Redis
    endpoint, err := redisC.Endpoint(ctx, "")
    require.NoError(t, err)
    
    client := redis.NewClient(&redis.Options{
        Addr: endpoint,
    })
    
    // Test cache operations
    cache := NewCache(client)
    err = cache.Set(ctx, "key", "value", time.Hour)
    require.NoError(t, err)
    
    val, err := cache.Get(ctx, "key")
    require.NoError(t, err)
    assert.Equal(t, "value", val)
}

End-to-End Testing

1. E2E with Playwright

// e2e/user-journey.spec.ts
import { test, expect } from '@playwright/test';

test.describe('User Registration Flow', () => {
    test.beforeEach(async ({ page }) => {
        // Setup: Clean database, seed data if needed
        await page.goto('/register');
    });
    
    test('user can register successfully', async ({ page }) => {
        // Fill registration form
        await page.fill('[name="email"]', 'newuser@example.com');
        await page.fill('[name="password"]', 'SecurePass123!');
        await page.fill('[name="confirmPassword"]', 'SecurePass123!');
        
        // Submit form
        await page.click('button[type="submit"]');
        
        // Verify redirect to dashboard
        await expect(page).toHaveURL('/dashboard');
        await expect(page.locator('h1')).toContainText('Welcome');
        
        // Verify user is logged in
        await expect(page.locator('[data-testid="user-menu"]')).toBeVisible();
    });
    
    test('shows error for existing email', async ({ page }) => {
        await page.fill('[name="email"]', 'existing@example.com');
        await page.fill('[name="password"]', 'SecurePass123!');
        await page.click('button[type="submit"]');
        
        await expect(page.locator('[data-testid="error-message"]'))
            .toContainText('Email already exists');
    });
    
    test('validates password requirements', async ({ page }) => {
        await page.fill('[name="email"]', 'test@example.com');
        await page.fill('[name="password"]', 'weak');
        await page.click('button[type="submit"]');
        
        await expect(page.locator('[data-testid="password-error"]'))
            .toContainText('Password must be at least 8 characters');
    });
});

2. Critical Path Testing

// Test only critical user journeys
const criticalPaths = [
    {
        name: 'Checkout Flow',
        steps: [
            'Add item to cart',
            'Proceed to checkout',
            'Fill shipping info',
            'Enter payment',
            'Confirm order'
        ]
    },
    {
        name: 'User Authentication',
        steps: [
            'Register new account',
            'Login',
            'Access protected resource',
            'Logout'
        ]
    }
];

// Run these in CI pipeline

Test Organization

1. File Structure

project/
├── src/
│   └── user/
│       ├── service.go
│       ├── service_test.go         # Unit tests
│       └── service_integration_test.go  # Integration tests (build tag)
├── tests/
│   ├── integration/
│   │   ├── api/
│   │   │   └── user_api_test.go
│   │   └── database/
│   │       └── user_repo_test.go
│   └── e2e/
│       └── user-journey.spec.ts
└── testdata/
    └── fixtures/
        └── users.json

2. Test Tags

// Unit tests (default)
// go test ./...

// Integration tests with build tag
//go:build integration
// +build integration

package user_test

func TestUserRepositoryIntegration(t *testing.T) {
    // Requires database
}

// Run: go test -tags=integration ./...

// E2E tests
//go:build e2e
// +build e2e

func TestUserJourney(t *testing.T) {
    // Full system test
}

// Run: go test -tags=e2e ./...

Coverage and Metrics

1. Coverage Targets

# Overall coverage > 80%
go test -cover ./...

# Critical code coverage > 90%
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html

# Coverage by package
go test -coverprofile=coverage.out ./...
grep -v "_test.go" coverage.out | awk -F: '{print $1}' | sort | uniq -c | sort -rn

2. Mutation Testing

# Go
# Install: go install github.com/zimmski/go-mutesting@latest
go-mutesting ./...

# JavaScript
# Install: npm install -g stryker-cli
stryker run

Best Practices

DO:

  • ✅ Test behavior, not implementation
  • ✅ Use table-driven tests for multiple cases
  • ✅ Keep tests independent and isolated
  • ✅ Use meaningful test names
  • ✅ Test edge cases and error conditions
  • ✅ Use test fixtures and factories
  • ✅ Mock external dependencies
  • ✅ Run tests in CI/CD pipeline

DON'T:

  • ❌ Test private methods (test public API)
  • ❌ Share state between tests
  • ❌ Use sleep() in tests (use async/await)
  • ❌ Ignore flaky tests (fix them)
  • ❌ Test everything (focus on critical paths)
  • ❌ Write tests that depend on order
  • ❌ Use production data in tests

When to Use

Use this skill when:

  • Writing unit tests
  • Setting up integration tests
  • Planning test strategy
  • Reviewing test coverage
  • Debugging test failures
  • Setting up CI/CD testing