← Назад к вопросам

Что такое пирамида тестирования?

1.0 Junior🔥 182 комментариев
#Тестирование

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Пирамида тестирования (Test Pyramid): Стратегия тестирования

Пирамида тестирования — это визуальная модель, которая рекомендует оптимальное соотношение и распределение различных типов тестов в проекте. Концепция предложена Mike Cohn и является best practice в индустрии.

Структура пирамиды

        /\
       /  \       End-to-End (E2E)
      /----\      ~5% тестов
     /      \
    /--------\    Integration (API, БД)
   /          \   ~15% тестов
  /            \
 /____________\ Unit
               ~80% тестов

Основной принцип: внизу пирамиды быстрые дешёвые тесты, вверху медленные дорогие.

1. Unit Tests (Нижняя часть — 80%)

Что тестировать:

  • Отдельные функции и методы
  • Бизнес-логика
  • Вычисления и преобразования
  • Валидацию

Пример для Flutter:

import 'package:flutter_test/flutter_test.dart';

void main() {
  group('User Validation', () {
    test('should validate email format', () {
      // Arrange
      final user = User(email: 'test@example.com');
      
      // Act
      final isValid = user.isEmailValid();
      
      // Assert
      expect(isValid, true);
    });
    
    test('should reject invalid email', () {
      final user = User(email: 'invalid-email');
      expect(user.isEmailValid(), false);
    });
  });
}

class User {
  final String email;
  User({required this.email});
  
  bool isEmailValid() {
    final emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+$');
    return emailRegex.hasMatch(email);
  }
}

Преимущества:

  • Очень быстрые (миллисекунды)
  • Дешёвые (минимум зависимостей)
  • Легко отлаживать
  • Высокий coverage возможен
  • Запускаются часто (на каждый коммит)

Чем писать:

flutter test           # Запуск unit тестов
flutter test --coverage # С coverage отчётом

2. Integration Tests (Средняя часть — 15%)

Что тестировать:

  • Взаимодействие между компонентами
  • API запросы
  • Работа с БД
  • Потоки данных между слоями

Пример для Flutter:

import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;

void main() {
  group('User API Integration', () {
    late MockHttpClient mockHttpClient;
    late UserRepository userRepository;
    
    setUp(() {
      mockHttpClient = MockHttpClient();
      userRepository = UserRepository(httpClient: mockHttpClient);
    });
    
    test('should fetch user from API', () async {
      // Arrange
      when(mockHttpClient.get(any))
          .thenAnswer((_) async => http.Response(
            '{"id": 1, "name": "Alice"}',
            200,
          ));
      
      // Act
      final user = await userRepository.getUser(1);
      
      // Assert
      expect(user.id, 1);
      expect(user.name, 'Alice');
      verify(mockHttpClient.get(any)).called(1);
    });
  });
}

Используемые инструменты:

# pubspec.yaml
dev_dependencies:
  mockito: ^5.0.0
  http_mock_adapter: ^4.0.0

Преимущества:

  • Тестируют реальные взаимодействия
  • Ловят баги на границах компонентов
  • Помедленнее unit тестов
  • Требуют больше setup

3. End-to-End Tests (Вершина — 5%)

Что тестировать:

  • Полный пользовательский flow
  • Реальные сценарии использования
  • UI + логика + API
  • Критические пути в приложении

Пример для Flutter:

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('User Login E2E', () {
    testWidgets('should login and see home page', (WidgetTester tester) async {
      // Arrange
      await tester.pumpWidget(MyApp());
      
      // Act - Введи email
      await tester.enterText(
        find.byType(TextField).first,
        'test@example.com',
      );
      
      // Act - Введи пароль
      await tester.enterText(
        find.byType(TextField).last,
        'password123',
      );
      
      // Act - Нажми кнопку входа
      await tester.tap(find.byText('Login'));
      await tester.pumpAndSettle(); // Ждём анимации
      
      // Assert - Должна быть главная страница
      expect(find.byText('Welcome'), findsOneWidget);
    });
  });
}

Инструменты для E2E:

flutter drive --target=test_driver/app.dart

Или с Playwright MCP (для веба):

await browser.goto('https://app.example.com');
await browser.fill('input[type="email"]', 'test@example.com');
await browser.click('button:has-text("Login")');

Особенности:

  • Медленные (секунды)
  • Дорогие (полное приложение)
  • Нестабильные (зависимость от окружения)
  • Трудно отлаживать
  • Их должно быть МАЛО

Почему именно эта пирамида?

Долгое время использовалась другая модель:

    /\
   /  \    Manual Testing  (плохо!)
  /----\
 /      \  E2E Tests       (слишком много)
/________\ Unit Tests

Проблемы старого подхода:

  • Множество медленных E2E тестов
  • Высокие затраты на поддержку
  • Низкая скорость feedback
  • Нестабильность

Пирамида решает эти проблемы:

  • Быстрый feedback (unit тесты)
  • Дешевизна (минимум зависимостей)
  • Качество (несколько E2E для критичных путей)

Практический пример для Flutter приложения

Проект: Todo App

UNIT (80%): 80 тестов
- TodoItem validation
- TodoList filtering
- DateFormatter
- UserAuthService logic
- Database query builders

INTEGRATION (15%): 15 тестов
- TodoRepository с mock API
- LocalStorage integration
- AuthService с API
- DatabaseService

E2E (5%): 5 тестов
- Create and complete todo
- Login and view todos
- Search todos
- Delete completed todos
- Offline sync

Структура файлов

test/
├── unit/
│   ├── models/
│   │   └── todo_test.dart
│   ├── services/
│   │   └── todo_service_test.dart
│   └── utils/
│       └── date_formatter_test.dart
├── integration/
│   ├── repositories/
│   │   └── todo_repository_test.dart
│   └── services/
│       └── auth_service_test.dart
└── e2e/
    ├── app_test.dart
    └── login_flow_test.dart

Команды для запуска

# Все unit тесты
flutter test test/unit/

# Все integration тесты
flutter test test/integration/

# E2E тесты
flutter drive --target=test_driver/app.dart

# Все тесты с coverage
flutter test --coverage

# Coverage report
genhtml coverage/lcov.info -o coverage/html

Лучшие практики

1. Пишите unit тесты ПЕРВЫМИ (TDD):

// Сначала тест
test('should calculate total price with tax', () {
  expect(calculateTotal(100, 0.1), 110);
});

// Потом реализация
int calculateTotal(int price, double taxRate) {
  return (price * (1 + taxRate)).toInt();
}

2. Избегайте дублирования тестов:

// Плохо
test('user login with valid email', () { ... });
test('user login with valid password', () { ... });
test('user login with valid email and password', () { ... });

// Хорошо
test('should login with valid credentials', () { ... });

3. Фокусируйтесь на критичные E2E:

// E2E только для:
// - Основные пользовательские сценарии
// - Payment flows
// - Authentication
// - Offline sync

// НЕ E2E:
// - Форматирование текста
// - UI деньги
// - Валидация полей (unit test!)

4. Поддерживайте целевой coverage:

Оптимально: 70-90% код coverage
Не гонитесь за 100% — есть код, который не нужно тестировать

Пирамида тестирования — это проверенная временем стратегия, обеспечивающая баланс между качеством, скоростью и затратами на тестирование.