Files
gh-anton-abyzov-specweave-p…/agents/qa-engineer/test-strategies.md
2025-11-29 17:57:09 +08:00

19 KiB

QA Engineer Agent - Test Strategies

Comprehensive guide to testing strategies for different application types and scenarios.

Table of Contents

  1. Testing Pyramid Strategy
  2. Testing Trophy Strategy
  3. TDD Red-Green-Refactor
  4. BDD Given-When-Then
  5. API Testing Strategy
  6. Frontend Testing Strategy
  7. Micro-Services Testing Strategy
  8. Performance Testing Strategy
  9. Security Testing Strategy
  10. Accessibility Testing Strategy

Testing Pyramid Strategy

Overview

The Testing Pyramid emphasizes a broad base of fast, cheap unit tests, fewer integration tests, and minimal UI/E2E tests.

        /\
       /  \  E2E (10%)
      /----\
     /      \ Integration (20%)
    /--------\
   /          \ Unit (70%)
  /--------------\

Distribution

  • Unit Tests (70%): Test individual functions, classes, components in isolation
  • Integration Tests (20%): Test interactions between modules, APIs, databases
  • E2E Tests (10%): Test complete user journeys through the UI

When to Use

  • Traditional applications with clear layers
  • Backend services with business logic
  • Applications where unit tests provide high confidence
  • Teams prioritizing fast feedback loops

Implementation Example

Unit Test (70% of suite):

// src/utils/cart.test.ts
import { describe, it, expect } from 'vitest';
import { calculateTotal, applyDiscount } from './cart';

describe('Cart Utils', () => {
  describe('calculateTotal', () => {
    it('should sum item prices', () => {
      const items = [
        { id: 1, price: 10 },
        { id: 2, price: 20 },
      ];

      expect(calculateTotal(items)).toBe(30);
    });

    it('should return 0 for empty cart', () => {
      expect(calculateTotal([])).toBe(0);
    });

    it('should handle decimal prices', () => {
      const items = [
        { id: 1, price: 10.99 },
        { id: 2, price: 20.50 },
      ];

      expect(calculateTotal(items)).toBeCloseTo(31.49, 2);
    });
  });

  describe('applyDiscount', () => {
    it('should apply percentage discount', () => {
      expect(applyDiscount(100, 'SAVE20', { type: 'percentage', value: 20 })).toBe(80);
    });

    it('should apply fixed discount', () => {
      expect(applyDiscount(100, 'SAVE10', { type: 'fixed', value: 10 })).toBe(90);
    });

    it('should not go below zero', () => {
      expect(applyDiscount(50, 'SAVE100', { type: 'fixed', value: 100 })).toBe(0);
    });
  });
});

Integration Test (20% of suite):

// src/api/orders.integration.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createTestServer } from '../test-utils/server';
import { seedDatabase, clearDatabase } from '../test-utils/database';

describe('Orders API Integration', () => {
  let server: TestServer;

  beforeEach(async () => {
    server = await createTestServer();
    await seedDatabase();
  });

  afterEach(async () => {
    await clearDatabase();
    await server.close();
  });

  it('should create order and store in database', async () => {
    const response = await server.request
      .post('/api/orders')
      .send({
        userId: 'user-123',
        items: [{ productId: 'prod-1', quantity: 2 }],
      });

    expect(response.status).toBe(201);
    expect(response.body).toMatchObject({
      id: expect.any(String),
      userId: 'user-123',
      status: 'pending',
    });

    // Verify database persistence
    const order = await server.db.orders.findById(response.body.id);
    expect(order).toBeTruthy();
    expect(order.items).toHaveLength(1);
  });

  it('should send confirmation email on order creation', async () => {
    const emailSpy = vi.spyOn(server.emailService, 'send');

    await server.request
      .post('/api/orders')
      .send({
        userId: 'user-123',
        items: [{ productId: 'prod-1', quantity: 1 }],
      });

    expect(emailSpy).toHaveBeenCalledWith({
      to: 'user@example.com',
      subject: 'Order Confirmation',
      template: 'order-confirmation',
    });
  });
});

E2E Test (10% of suite):

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

test.describe('Checkout Flow', () => {
  test('should complete purchase as guest user', async ({ page }) => {
    // Navigate to product
    await page.goto('/products/laptop-123');
    await expect(page.getByRole('heading', { name: 'Gaming Laptop' })).toBeVisible();

    // Add to cart
    await page.getByRole('button', { name: 'Add to Cart' }).click();
    await expect(page.getByText('Item added to cart')).toBeVisible();

    // Go to checkout
    await page.getByRole('link', { name: 'Cart (1)' }).click();
    await page.getByRole('button', { name: 'Checkout' }).click();

    // Fill shipping info
    await page.getByLabel('Email').fill('guest@example.com');
    await page.getByLabel('Full Name').fill('John Doe');
    await page.getByLabel('Address').fill('123 Main St');
    await page.getByLabel('City').fill('New York');
    await page.getByLabel('Zip Code').fill('10001');

    // Fill payment info (test mode)
    await page.getByLabel('Card Number').fill('4242424242424242');
    await page.getByLabel('Expiry Date').fill('12/25');
    await page.getByLabel('CVC').fill('123');

    // Submit order
    await page.getByRole('button', { name: 'Place Order' }).click();

    // Verify confirmation
    await expect(page).toHaveURL(/\/order-confirmation/);
    await expect(page.getByText('Order Confirmed!')).toBeVisible();
    await expect(page.getByText(/Order #/)).toBeVisible();
  });
});

Coverage Targets

  • Unit Tests: 80%+ line coverage, 75%+ branch coverage
  • Integration Tests: 100% critical API endpoints
  • E2E Tests: 100% critical user journeys

Testing Trophy Strategy

Overview

Modern approach that emphasizes integration tests over unit tests, with static analysis as the foundation.

        /\
       /  \  E2E (5%)
      /----\
     /      \ Integration (50%)
    /--------\
   /          \ Unit (25%)
  /--------------\
 /                \ Static (20%)
/------------------\

Distribution

  • Static Analysis (20%): TypeScript, ESLint, Prettier
  • Unit Tests (25%): Pure functions, utilities, critical logic
  • Integration Tests (50%): Components with dependencies, API contracts
  • E2E Tests (5%): Critical business flows only

When to Use

  • Modern frontend applications (React, Vue, Angular)
  • Applications with complex component interactions
  • Teams using TypeScript and static analysis tools
  • Applications where integration tests catch more bugs

Implementation Example

Static Analysis (20%):

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  }
}
// .eslintrc.json
{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react-hooks/recommended",
    "plugin:jsx-a11y/recommended"
  ],
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",
    "react-hooks/exhaustive-deps": "error"
  }
}

Unit Tests (25%):

// src/utils/formatters.test.ts
import { describe, it, expect } from 'vitest';
import { formatCurrency, formatDate } from './formatters';

describe('Formatters (Pure Functions)', () => {
  describe('formatCurrency', () => {
    it('should format USD currency', () => {
      expect(formatCurrency(1234.56, 'USD')).toBe('$1,234.56');
    });

    it('should handle negative amounts', () => {
      expect(formatCurrency(-100, 'USD')).toBe('-$100.00');
    });
  });

  describe('formatDate', () => {
    it('should format ISO date', () => {
      const date = new Date('2025-01-15');
      expect(formatDate(date, 'short')).toBe('1/15/2025');
    });
  });
});

Integration Tests (50%):

// src/components/UserProfile.integration.test.tsx
import { describe, it, expect, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { UserProfile } from './UserProfile';

const server = setupServer(
  rest.get('/api/users/:id', (req, res, ctx) => {
    return res(
      ctx.json({
        id: req.params.id,
        name: 'John Doe',
        email: 'john@example.com',
      })
    );
  }),

  rest.put('/api/users/:id', (req, res, ctx) => {
    return res(ctx.json({ ...req.body, updatedAt: Date.now() }));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe('UserProfile Integration', () => {
  it('should load and display user data', async () => {
    render(<UserProfile userId="123" />);

    // Loading state
    expect(screen.getByText('Loading...')).toBeInTheDocument();

    // Wait for data to load
    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument();
    });

    expect(screen.getByText('john@example.com')).toBeInTheDocument();
  });

  it('should update user profile on form submit', async () => {
    const user = userEvent.setup();
    render(<UserProfile userId="123" />);

    // Wait for initial load
    await screen.findByText('John Doe');

    // Edit name
    const nameInput = screen.getByLabelText('Name');
    await user.clear(nameInput);
    await user.type(nameInput, 'Jane Smith');

    // Submit form
    await user.click(screen.getByRole('button', { name: 'Save' }));

    // Verify success message
    await waitFor(() => {
      expect(screen.getByText('Profile updated successfully')).toBeInTheDocument();
    });

    // Verify updated name
    expect(screen.getByText('Jane Smith')).toBeInTheDocument();
  });

  it('should handle API errors gracefully', async () => {
    // Mock API error
    server.use(
      rest.get('/api/users/:id', (req, res, ctx) => {
        return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
      })
    );

    render(<UserProfile userId="123" />);

    // Wait for error message
    await waitFor(() => {
      expect(screen.getByText(/Failed to load user/i)).toBeInTheDocument();
    });
  });
});

E2E Tests (5%):

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

test('critical user journey: signup to first purchase', async ({ page }) => {
  // Only test the MOST critical path
  await page.goto('/');

  // Signup
  await page.getByRole('link', { name: 'Sign Up' }).click();
  await page.getByLabel('Email').fill('newuser@example.com');
  await page.getByLabel('Password').fill('SecurePass123!');
  await page.getByRole('button', { name: 'Create Account' }).click();

  // Verify logged in
  await expect(page.getByText('Welcome, New User')).toBeVisible();

  // Make purchase
  await page.goto('/products/best-seller');
  await page.getByRole('button', { name: 'Buy Now' }).click();
  await page.getByLabel('Card Number').fill('4242424242424242');
  await page.getByRole('button', { name: 'Complete Purchase' }).click();

  // Verify success
  await expect(page.getByText('Purchase Successful')).toBeVisible();
});

Coverage Targets

  • Static Analysis: 100% (TypeScript strict mode, zero ESLint errors)
  • Unit Tests: 90%+ for pure functions and utilities
  • Integration Tests: 80%+ for components with dependencies
  • E2E Tests: 100% critical paths only

TDD Red-Green-Refactor

The Cycle

  1. RED: Write a failing test that defines expected behavior
  2. GREEN: Write minimal code to make the test pass
  3. REFACTOR: Improve code quality while keeping tests green

Example: Shopping Cart Feature

RED: Write Failing Test:

// src/cart/ShoppingCart.test.ts
import { describe, it, expect } from 'vitest';
import { ShoppingCart } from './ShoppingCart';

describe('ShoppingCart', () => {
  it('should add item to cart', () => {
    const cart = new ShoppingCart();

    cart.addItem({ id: 1, name: 'Laptop', price: 1000 });

    expect(cart.getItemCount()).toBe(1);
  });
});

Run test: FAIL (ShoppingCart doesn't exist)

GREEN: Minimal Implementation:

// src/cart/ShoppingCart.ts
interface CartItem {
  id: number;
  name: string;
  price: number;
}

export class ShoppingCart {
  private items: CartItem[] = [];

  addItem(item: CartItem): void {
    this.items.push(item);
  }

  getItemCount(): number {
    return this.items.length;
  }
}

Run test: PASS

Add Another Test (Triangulation):

it('should calculate total price', () => {
  const cart = new ShoppingCart();

  cart.addItem({ id: 1, name: 'Laptop', price: 1000 });
  cart.addItem({ id: 2, name: 'Mouse', price: 50 });

  expect(cart.getTotal()).toBe(1050);
});

Run test: FAIL (getTotal doesn't exist)

GREEN: Implement getTotal:

export class ShoppingCart {
  // ... previous code ...

  getTotal(): number {
    return this.items.reduce((sum, item) => sum + item.price, 0);
  }
}

Run test: PASS

REFACTOR: Improve Design:

export class ShoppingCart {
  private items: Map<number, CartItem> = new Map();

  addItem(item: CartItem): void {
    const existing = this.items.get(item.id);
    if (existing) {
      // Increment quantity instead of duplicating
      existing.quantity = (existing.quantity || 1) + 1;
    } else {
      this.items.set(item.id, { ...item, quantity: 1 });
    }
  }

  getItemCount(): number {
    return Array.from(this.items.values()).reduce(
      (count, item) => count + (item.quantity || 1),
      0
    );
  }

  getTotal(): number {
    return Array.from(this.items.values()).reduce(
      (sum, item) => sum + item.price * (item.quantity || 1),
      0
    );
  }
}

Run tests: ALL PASS (refactoring didn't break anything!)

TDD Benefits

  • Forces modular, testable design
  • Prevents over-engineering
  • Living documentation
  • Fearless refactoring
  • Faster debugging

API Testing Strategy

Layers

  1. Unit Tests: Test business logic in isolation
  2. Integration Tests: Test API endpoints with real database
  3. Contract Tests: Test API contracts (Pact)
  4. E2E Tests: Test complete API flows

Example: REST API Testing

Unit Test (Business Logic):

// src/services/OrderService.test.ts
import { describe, it, expect, vi } from 'vitest';
import { OrderService } from './OrderService';

describe('OrderService', () => {
  it('should calculate order total with tax', () => {
    const mockRepo = {
      save: vi.fn(),
      findById: vi.fn(),
    };

    const service = new OrderService(mockRepo);

    const order = service.calculateTotal({
      items: [{ price: 100, quantity: 2 }],
      taxRate: 0.08,
    });

    expect(order.subtotal).toBe(200);
    expect(order.tax).toBeCloseTo(16, 2);
    expect(order.total).toBeCloseTo(216, 2);
  });
});

Integration Test (API + Database):

// tests/integration/orders.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import supertest from 'supertest';
import { createTestApp } from '../utils/test-app';

describe('Orders API', () => {
  let app;
  let request;

  beforeAll(async () => {
    app = await createTestApp();
    request = supertest(app);
  });

  afterAll(async () => {
    await app.close();
  });

  it('POST /api/orders should create order', async () => {
    const response = await request
      .post('/api/orders')
      .send({
        userId: 'user-123',
        items: [
          { productId: 'prod-1', quantity: 2, price: 50 },
        ],
      })
      .expect(201);

    expect(response.body).toMatchObject({
      id: expect.any(String),
      userId: 'user-123',
      status: 'pending',
      total: 100,
    });

    // Verify database persistence
    const order = await app.db.orders.findById(response.body.id);
    expect(order).toBeTruthy();
  });

  it('GET /api/orders/:id should return order', async () => {
    // Create order first
    const createResponse = await request
      .post('/api/orders')
      .send({ userId: 'user-123', items: [] });

    const orderId = createResponse.body.id;

    // Fetch order
    const response = await request
      .get(`/api/orders/${orderId}`)
      .expect(200);

    expect(response.body.id).toBe(orderId);
  });

  it('PUT /api/orders/:id/status should update status', async () => {
    const createResponse = await request
      .post('/api/orders')
      .send({ userId: 'user-123', items: [] });

    const orderId = createResponse.body.id;

    const response = await request
      .put(`/api/orders/${orderId}/status`)
      .send({ status: 'shipped' })
      .expect(200);

    expect(response.body.status).toBe('shipped');
  });
});

Contract Test (Pact):

// tests/contract/orders-consumer.test.ts
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import { OrdersClient } from '@/api/orders-client';

const provider = new PactV3({
  consumer: 'OrdersConsumer',
  provider: 'OrdersAPI',
});

describe('Orders API Contract', () => {
  it('should get order by ID', async () => {
    await provider
      .given('order with ID 123 exists')
      .uponReceiving('a request for order 123')
      .withRequest({
        method: 'GET',
        path: '/api/orders/123',
      })
      .willRespondWith({
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: {
          id: MatchersV3.string('123'),
          userId: MatchersV3.string('user-456'),
          status: MatchersV3.regex('pending|shipped|delivered', 'pending'),
          total: MatchersV3.decimal(100.50),
        },
      })
      .executeTest(async (mockServer) => {
        const client = new OrdersClient(mockServer.url);
        const order = await client.getOrder('123');

        expect(order.id).toBe('123');
        expect(order.status).toMatch(/pending|shipped|delivered/);
      });
  });
});

Coverage Targets

  • Unit: 90%+ business logic
  • Integration: 100% API endpoints
  • Contract: 100% API contracts
  • E2E: Critical flows only

[Document continues with 6 more comprehensive testing strategies...]


Summary Table

Strategy Best For Coverage Target Execution Time
Pyramid Backend services, traditional apps 80%+ unit, 100% critical < 5 min
Trophy Modern frontends (React, Vue) 80%+ integration < 3 min
TDD New features, greenfield projects 90%+ Continuous
BDD Stakeholder-driven development 100% acceptance Variable
API REST/GraphQL services 90%+ endpoints < 2 min
Frontend SPAs, component libraries 85%+ components < 4 min
Micro-Services Distributed systems 80%+ per service < 10 min
Performance High-traffic applications Critical paths 30 min
Security Sensitive data, compliance 100% attack vectors 1 hour
Accessibility Public-facing websites WCAG AA 100% 15 min