В чем разница между функциональными и интеграционными тестами?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Функциональные vs Интеграционные тесты
Это два разных уровня тестирования, которые проверяют разные аспекты приложения. Часто путают, но они решают совершенно разные задачи.
Функциональные тесты (Functional Tests)
Функциональные тесты проверяют, что система работает согласно требованиям с точки зрения пользователя. Они проверяют поведение приложения как целого, проходя через весь стек.
Характеристики:
- Black-box тестирование — тестируем только входы и выходы
- Фокус на пользовательском сценарии — "Может ли пользователь зарегистрироваться?"
- Проверяет требования — соответствие функциональным требованиям
- Может быть медленным — может вовлекать БД, сеть, файловую систему
- E2E природа — от точки входа до точки выхода
- Примеры: веб-тесты через браузер, API тесты
import pytest
from app import create_app
from db import Database
@pytest.fixture
def client():
app = create_app()
app.config['TESTING'] = True
with app.test_client() as client:
yield client
def test_user_registration(client):
"""Функциональный тест: пользователь может зарегистрироваться"""
# Пользователь отправляет форму
response = client.post('/api/register', json={
'email': 'user@example.com',
'password': 'password123'
})
# Проверяем ответ
assert response.status_code == 201
assert response.json['id'] is not None
# Проверяем, что пользователь действительно создан
user = client.get(f"/api/users/{response.json['id']}")
assert user.json['email'] == 'user@example.com'
def test_user_login_workflow(client):
"""Функциональный тест: весь цикл входа пользователя"""
# Шаг 1: Регистрация
register = client.post('/api/register', json={
'email': 'user@example.com',
'password': 'password123'
})
assert register.status_code == 201
# Шаг 2: Логин
login = client.post('/api/login', json={
'email': 'user@example.com',
'password': 'password123'
})
assert login.status_code == 200
token = login.json['token']
# Шаг 3: Используем токен
headers = {'Authorization': f'Bearer {token}'}
profile = client.get('/api/me', headers=headers)
assert profile.status_code == 200
assert profile.json['email'] == 'user@example.com'
Интеграционные тесты (Integration Tests)
Интеграционные тесты проверяют, что отдельные компоненты системы работают вместе правильно. Они тестируют взаимодействие между модулями, но меньше, чем функциональные тесты.
Характеристики:
- White-box/grey-box тестирование — знаем внутреннюю структуру
- Фокус на компонентах — тестируем их взаимодействие
- Проверяет интеграцию — работают ли модули вместе
- Быстрее функциональных — могут мокировать внешние системы
- Часть цепочки — тестируем несколько компонентов, но не всё
- Примеры: тесты базы данных и API, микросервисы общаются
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from services.user_service import UserService
from services.auth_service import AuthService
from models import User, Base
@pytest.fixture
def db_session():
"""Интеграционная БД для тестов"""
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.close()
def test_user_service_saves_to_database(db_session):
"""Интеграционный тест: UserService сохраняет в БД"""
user_service = UserService(db_session)
# Создаём пользователя через service
user = user_service.create_user(
email='user@example.com',
password='password123'
)
# Проверяем, что действительно в БД
saved_user = db_session.query(User).filter_by(email='user@example.com').first()
assert saved_user is not None
assert saved_user.id == user.id
def test_auth_service_integrates_with_user_service(db_session):
"""Интеграционный тест: AuthService работает с UserService"""
user_service = UserService(db_session)
auth_service = AuthService(db_session, user_service)
# AuthService создаёт пользователя через UserService
token = auth_service.register(
email='user@example.com',
password='password123'
)
# Проверяем, что токен валиден и соответствует пользователю
user = auth_service.get_user_from_token(token)
assert user.email == 'user@example.com'
def test_services_with_mocked_external_api(db_session):
"""Интеграционный тест с мокированием внешних ресурсов"""
from unittest.mock import Mock, patch
user_service = UserService(db_session)
# Мокируем внешний сервис (например, email отправка)
with patch('services.user_service.EmailService') as mock_email:
user = user_service.create_user_and_send_email(
email='user@example.com',
password='password123'
)
# Проверяем интеграцию: user создан И email отправлен
assert user.id is not None
mock_email.send.assert_called_once()
Сравнение в таблице
| Аспект | Функциональные | Интеграционные |
|---|---|---|
| Фокус | Пользовательский сценарий | Взаимодействие компонентов |
| Охват | Весь стек (E2E) | Часть системы |
| Скорость | Медленнее | Быстрее |
| Знание внутреннего | Black-box | Grey-box |
| Примеры | UI тесты, API workflows | Unit → Integration |
| Внешние системы | Реальные или мокированные | Обычно мокированные |
| Уровень пирамиды | Вершина | Середина |
Пирамида тестирования
┌─────────────────┐
│ E2E тесты │ Функциональные (медленные, редкие)
│ (Selenium) │
├─────────────────┤
│ Интеграционные │ Компоненты вместе (средние)
│ (API, БД) │
├─────────────────┤
│ Unit тесты │ Функции отдельно (быстрые, много)
│ (Pytest) │
└─────────────────┘
Практический пример: одна сценария разными типами тестов
Unit тест (самый быстрый)
def test_password_hashing():
"""Unit: проверяем функцию хеширования пароля"""
from security import hash_password
hashed = hash_password('password123')
assert hashed != 'password123'
assert len(hashed) > 20
Интеграционный тест
def test_user_service_hashes_password(db_session):
"""Интеграционный: UserService правильно хеширует"""
from services.user_service import UserService
service = UserService(db_session)
user = service.create_user('user@ex.com', 'password123')
# Проверяем, что в БД пароль захеширован
assert user.password_hash != 'password123'
Функциональный тест
def test_user_cannot_login_with_wrong_password(client):
"""Функциональный: пользователь не может войти с неправильным паролем"""
# Регистрируемся
client.post('/api/register', json={
'email': 'user@ex.com',
'password': 'password123'
})
# Пытаемся войти с неправильным паролем
response = client.post('/api/login', json={
'email': 'user@ex.com',
'password': 'wrongpassword'
})
assert response.status_code == 401
Когда писать что
Функциональные тесты — для:
- Критичных user journeys — регистрация, оплата, логин
- Регрессионного тестирования — убедиться, что всё ещё работает
- Приёмочного тестирования — подтвердить требования
Интеграционные тесты — для:
- Взаимодействия сервисов — AuthService + UserService
- Работы с реальной БД — но в изолированной среде
- API endpoints — с настоящей БД
Практические рекомендации
# ПЛОХО: смешивание уровней
def test_everything(client, db_session):
# Unit + интеграция + функциональное
response = client.post('/api/register', ...)
user = db_session.query(User).filter(...)
assert hash_password(...) == user.password_hash
# ХОРОШО: разделение ответственности
# Unit тест
def test_hash_password():
assert hash_password('pwd') != 'pwd'
# Интеграционный
def test_user_service_saves_hashed(db_session):
service = UserService(db_session)
user = service.create_user('u@ex.com', 'pwd')
assert db_session.query(User).filter_by(id=user.id).first().password_hash == user.password_hash
# Функциональный
def test_register_endpoint(client):
response = client.post('/api/register', json={'email': 'u@ex.com', 'password': 'pwd'})
assert response.status_code == 201
Заключение
Функциональные тесты проверяют, что система работает с точки зрения пользователя — весь цикл от входа до выхода. Интеграционные тесты проверяют, что компоненты взаимодействуют правильно, но обычно мокируют внешние системы. В правильной пирамиде тестов много unit-тестов (быстрых), средне интеграционных, и мало функциональных (медленных). Оба типа критичны, но покрывают разные требования.