Test Pyramid Examples - Руководство по Тестовой Стратегии

Всесторонние примеры реализации тестовой пирамиды включая модульные тесты, интеграционные тесты, E2E тесты, организацию тестов и стратегические тестовые паттерны для сбалансированного обеспечения качества программного обеспечения

📝 Основы и Принципы Тестовой Пирамиды markdown

🟢 simple ⭐⭐

Полная реализация тестовой пирамиды с сбалансированными уровнями тестов, правильной организацией и стратегическими руководствами по размещению тестов

⏱️ 60 min 🏷️ test pyramid, strategy, foundation, principles
Prerequisites: Testing fundamentals, JavaScript/TypeScript, Test frameworks
# Test Pyramid Foundation and Principles

## Overview
The Test Pyramid is a testing strategy that emphasizes a balanced approach to software testing with the majority of tests being fast, isolated unit tests at the base, fewer integration tests in the middle, and even fewer end-to-end tests at the top.

## Test Pyramid Structure

### Level 1: Unit Tests (70%)
- **Purpose**: Test individual components in isolation
- **Characteristics**: Fast, cheap, numerous, reliable
- **Examples**: Function validation, class behavior, component logic
- **Tools**: Jest, Mocha, JUnit, Pytest

### Level 2: Integration Tests (20%)
- **Purpose**: Test how components work together
- **Characteristics**: Medium speed, moderate cost, fewer than unit tests
- **Examples**: Database interactions, API integrations, service communication
- **Tools**: TestContainers, Supertest, Spring Boot Test

### Level 3: End-to-End Tests (10%)
- **Purpose**: Test complete user workflows
- **Characteristics**: Slow, expensive, few, comprehensive
- **Examples**: User journeys, critical paths, cross-system workflows
- **Tools**: Playwright, Cypress, Selenium

## Implementation Strategy

### 1. Project Structure
```
project/
├── src/
│   ├── components/          # UI components
│   ├── services/           # Business logic
│   ├── utils/              # Utilities
│   └── api/                # API layer
├── tests/
│   ├── unit/               # Unit tests
│   │   ├── components/
│   │   ├── services/
│   │   └── utils/
│   ├── integration/        # Integration tests
│   │   ├── api/
│   │   ├── database/
│   │   └── services/
│   ├── e2e/                # End-to-end tests
│   │   ├── user-journeys/
│   │   └── critical-paths/
│   └── fixtures/           # Test data and utilities
└── test-configs/           # Test configuration files
```

## Best Practices

### 1. Test Organization
- Group related tests in describe blocks
- Use clear, descriptive test names
- Follow AAA pattern (Arrange, Act, Assert)
- Keep tests independent and isolated

### 2. Test Data
- Use factories or fixtures for test data
- Avoid hardcoded test data
- Clean up test data after each test
- Use realistic but simple data

### 3. Test Maintenance
- Regular review and refactoring
- Remove redundant tests
- Update tests when requirements change
- Monitor test execution time

### 4. Coverage Targets
- Unit tests: 80-90% coverage
- Integration tests: Critical paths coverage
- E2E tests: Core user journeys coverage

This foundation provides a solid base for implementing a comprehensive test pyramid strategy that ensures quality while maintaining development velocity.

💻 Паттерны Реализации Тестовой Пирамиды javascript

🔴 complex ⭐⭐⭐⭐

Продвинутые паттерны реализации включая автоматизацию тестов, CI/CD интеграцию, управление тестовыми данными и корпоративные тестовые стратегии

⏱️ 90 min 🏷️ test pyramid, implementation, automation, enterprise
Prerequisites: Advanced testing, CI/CD, JavaScript/TypeScript, DevOps
// Test Pyramid Implementation Patterns

// 1. test-setup/base-test-setup.js - Shared Test Infrastructure
const { beforeAll, afterAll, beforeEach, afterEach } = require('@jest/globals');
const { setupDatabase, cleanupDatabase } = require('./database-setup');
const { startTestServer, stopTestServer } = require('./test-server');
const TestLogger = require('./test-logger');

class BaseTestSetup {
  constructor(options = {}) {
    this.options = {
      useDatabase: options.useDatabase !== false,
      useTestServer: options.useTestServer !== false,
      logLevel: options.logLevel || 'info',
      timeout: options.timeout || 10000,
      ...options
    };

    this.testLogger = new TestLogger(this.options.logLevel);
  }

  async setupGlobal() {
    if (this.options.useDatabase) {
      await setupDatabase();
      this.testLogger.info('Database setup complete');
    }

    if (this.options.useTestServer) {
      this.server = await startTestServer();
      this.testLogger.info('Test server started');
    }
  }

  async cleanupGlobal() {
    if (this.server) {
      await stopTestServer(this.server);
      this.testLogger.info('Test server stopped');
    }

    if (this.options.useDatabase) {
      await cleanupDatabase();
      this.testLogger.info('Database cleanup complete');
    }
  }

  async setupEach() {
    if (this.options.useDatabase) {
      await this.resetDatabase();
    }

    this.testContext = {
      startTime: Date.now(),
      testId: Math.random().toString(36).substr(2, 9),
      logger: this.testLogger
    };
  }

  async cleanupEach(testResult) {
    if (this.testContext) {
      const duration = Date.now() - this.testContext.startTime;
      this.testLogger.info(`Test completed in ${duration}ms`, {
        testId: this.testContext.testId,
        result: testResult
      });
    }
  }

  async resetDatabase() {
    // Reset database to clean state
    await cleanupDatabase();
    await setupDatabase();
  }

  getTestContext() {
    return this.testContext;
  }
}

// 2. test-helpers/api-test-helper.js - API Testing Utilities
class ApiTestHelper {
  constructor(baseUrl = 'http://localhost:3000') {
    this.baseUrl = baseUrl;
    this.defaultHeaders = {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    };
  }

  async request(method, endpoint, data = null, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    const config = {
      method,
      headers: { ...this.defaultHeaders, ...options.headers },
      ...options
    };

    if (data && method !== 'GET') {
      config.body = JSON.stringify(data);
    }

    const response = await fetch(url, config);

    return {
      status: response.status,
      headers: response.headers,
      data: await response.json(),
      ok: response.ok
    };
  }

  // Assertion helpers
  assertSuccess(response, expectedStatus = 200) {
    if (response.status !== expectedStatus) {
      throw new Error(`Expected status ${expectedStatus}, got ${response.status}`);
    }
  }

  assertContains(response, expectedData) {
    expect(response.data).toMatchObject(expectedData);
  }
}

// 3. test-helpers/database-test-helper.js - Database Testing Utilities
class DatabaseTestHelper {
  constructor(connection) {
    this.connection = connection;
    this.fixtures = {};
  }

  async loadFixtures(fixtureName, data) {
    await this.connection.query(
      `INSERT INTO ${fixtureName} (${Object.keys(data[0]).join(', ')}) VALUES ?`,
      [data.map(item => Object.values(item))]
    );

    this.fixtures[fixtureName] = data;
  }

  async clearTable(tableName) {
    await this.connection.query(`DELETE FROM ${tableName}`);
    delete this.fixtures[tableName];
  }

  async resetDatabase() {
    const tables = Object.keys(this.fixtures);
    for (const table of tables) {
      await this.clearTable(table);
    }
  }

  async countRecords(tableName) {
    const [result] = await this.connection.query(`SELECT COUNT(*) as count FROM ${tableName}`);
    return result[0].count;
  }

  async findRecord(tableName, criteria) {
    const whereClause = Object.keys(criteria)
      .map(key => `${key} = ?`)
      .join(' AND ');

    const values = Object.values(criteria);

    const [result] = await this.connection.query(
      `SELECT * FROM ${tableName} WHERE ${whereClause}`,
      values
    );

    return result[0] || null;
  }
}

module.exports = {
  BaseTestSetup,
  ApiTestHelper,
  DatabaseTestHelper
};