Введение в Журналирование Выполнения Тестов
Журналы выполнения тестов являются основой документации по обеспечению качества, предоставляя исчерпывающую запись тестовых активностей, результатов и доказательств. Эти журналы служат юридической документацией, ресурсами для отладки и историческими записями, которые позволяют командам воспроизводить проблемы, анализировать тенденции и демонстрировать соответствие стандартам качества.
Хорошо структурированный журнал выполнения тестов преобразует эфемерные тестовые активности в постоянную, действенную документацию, которая добавляет ценность на протяжении всего жизненного цикла разработки программного обеспечения.
Основные Компоненты Журналов Выполнения
Основные Элементы Журнала
Каждый журнал выполнения тестов должен фиксировать следующую критическую информацию:
Метаданные Выполнения:
- Уникальный ID выполнения
- Идентификатор тест-кейса
- Временная метка выполнения (начало и конец)
- Идентификация тестировщика
- Детали тестовой среды
- Информация о сборке/версии
Результаты Выполнения:
- Статус Pass/Fail/Blocked/Skip
- Фактические vs. ожидаемые результаты
- Ссылки на дефекты
- Длительность выполнения
- Попытки повтора и результаты
Контекст Окружения:
- Операционная система и версия
- Версия браузера/приложения
- Состояние базы данных
- Конфигурация сети
- Доступность сторонних сервисов
Пример Структуры Журнала Выполнения
{
"executionId": "EXEC-20250108-001",
"testCaseId": "TC-AUTH-015",
"testCaseName": "Вход Пользователя с Валидными Учетными Данными",
"executionTime": {
"start": "2025-01-08T10:30:00Z",
"end": "2025-01-08T10:32:15Z",
"duration": 135
},
"executor": {
"name": "Сара Джонсон",
"role": "QA Инженер",
"id": "sjohnson@company.com"
},
"environment": {
"name": "Staging",
"url": "https://staging.app.com",
"buildVersion": "v2.4.1-RC3",
"os": "Windows 11 Pro",
"browser": "Chrome 120.0.6099.109"
},
"status": "PASSED",
"steps": [
{
"stepNumber": 1,
"description": "Перейти на страницу входа",
"expected": "Форма входа отображена",
"actual": "Форма входа отображена корректно",
"status": "PASSED",
"screenshot": "step1_login_page.png"
},
{
"stepNumber": 2,
"description": "Ввести имя пользователя 'testuser@example.com'",
"expected": "Поле имени пользователя заполнено",
"actual": "Поле имени пользователя заполнено",
"status": "PASSED"
},
{
"stepNumber": 3,
"description": "Ввести пароль",
"expected": "Пароль замаскирован точками",
"actual": "Пароль замаскирован точками",
"status": "PASSED"
},
{
"stepNumber": 4,
"description": "Нажать кнопку 'Войти'",
"expected": "Перенаправление на панель управления в течение 2 секунд",
"actual": "Перенаправлен на панель управления за 1.3 секунды",
"status": "PASSED",
"screenshot": "step4_dashboard.png",
"performanceMetric": 1.3
}
],
"evidence": {
"screenshots": ["step1_login_page.png", "step4_dashboard.png"],
"videos": ["full_execution.mp4"],
"logs": ["browser_console.log", "network_traffic.har"]
},
"notes": "Выполнение завершено без проблем. Производительность в пределах допустимого диапазона."
}
Стратегии Сбора Доказательств
Управление Скриншотами
Скриншоты являются критически важным визуальным доказательством, которое фиксирует состояние приложения в конкретные моменты:
Лучшие Практики:
- Делать скриншоты в точках принятия решений и шагах верификации
- Использовать согласованные соглашения по именованию:
{executionId}_{stepNumber}_{описание}.png
- Включать полностраничные скриншоты для контекста
- Аннотировать скриншоты с выделениями для дефектов
- Хранить с метаданными (временная метка, разрешение, браузер)
Автоматизированные Инструменты для Скриншотов:
# Пример скриншота с Selenium WebDriver
from selenium import webdriver
from datetime import datetime
import os
def capture_evidence_screenshot(driver, execution_id, step_number, description):
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{execution_id}_step{step_number}_{description}_{timestamp}.png"
filepath = os.path.join("evidence", "screenshots", filename)
# Убедиться, что директория существует
os.makedirs(os.path.dirname(filepath), exist_ok=True)
# Захватить полностраничный скриншот
driver.save_screenshot(filepath)
# Записать метаданные скриншота
metadata = {
"filename": filename,
"timestamp": timestamp,
"viewport": driver.get_window_size(),
"url": driver.current_url,
"step": step_number
}
return filepath, metadata
# Использование в тесте
driver = webdriver.Chrome()
driver.get("https://example.com/login")
screenshot_path, metadata = capture_evidence_screenshot(
driver, "EXEC-001", 1, "login_page"
)
Видеозапись для Сложных Сценариев
Видеозаписи обеспечивают комплексное доказательство для сложных тестовых сценариев:
# Плагин Pytest для автоматической видеозаписи
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
@pytest.fixture
def video_recording_driver(request):
chrome_options = Options()
chrome_options.add_argument("--disable-dev-shm-usage")
# Включить видеозапись через возможности браузера
chrome_options.set_capability("goog:loggingPrefs", {"performance": "ALL"})
driver = webdriver.Chrome(options=chrome_options)
# Начать запись экрана
test_name = request.node.name
video_path = f"evidence/videos/{test_name}.webm"
yield driver
# Сохранить запись при завершении теста
driver.quit()
# Архивировать видео с результатом теста
if request.node.rep_call.failed:
# Сохранить видео для неудачных тестов
print(f"Тест провален - видео сохранено: {video_path}")
else:
# Опционально: удалить видео успешных тестов для экономии места
pass
def test_checkout_process(video_recording_driver):
driver = video_recording_driver
# Реализация теста
pass
Сбор Лог-Файлов
Комплексный сбор логов обеспечивает воспроизводимость и возможность отладки:
Типы Логов для Сбора:
Тип Лога | Назначение | Метод Сбора |
---|---|---|
Консоль Браузера | Ошибки JavaScript, предупреждения | driver.get_log('browser') |
Сетевой Трафик | API вызовы, время отклика | Экспорт HAR файла |
Логи Приложения | Backend ошибки, stack traces | Инструменты агрегации логов |
Запросы к БД | Операции с данными, производительность | Логирование запросов |
Логи Сервера | Проблемы инфраструктуры | Централизованное логирование (ELK, Splunk) |
# Комплексный сбор логов
def collect_execution_evidence(driver, execution_id):
evidence = {
"browser_console": [],
"network_traffic": None,
"performance_metrics": {}
}
# Собрать логи консоли браузера
for entry in driver.get_log('browser'):
evidence["browser_console"].append({
"timestamp": entry['timestamp'],
"level": entry['level'],
"message": entry['message']
})
# Собрать метрики производительности
navigation_timing = driver.execute_script(
"return window.performance.timing"
)
evidence["performance_metrics"] = {
"page_load_time": navigation_timing['loadEventEnd'] - navigation_timing['navigationStart'],
"dom_content_loaded": navigation_timing['domContentLoadedEventEnd'] - navigation_timing['navigationStart'],
"first_paint": navigation_timing['responseStart'] - navigation_timing['navigationStart']
}
# Экспортировать сетевой трафик (требуется Chrome DevTools Protocol)
# Использование Chrome DevTools Protocol для экспорта HAR
evidence["network_traffic"] = export_network_har(driver)
# Сохранить пакет доказательств
evidence_path = f"evidence/{execution_id}/logs.json"
with open(evidence_path, 'w') as f:
json.dump(evidence, f, indent=2)
return evidence
Документирование Деталей Окружения
Захват Полного Состояния Окружения
Контекст окружения критически важен для воспроизведения результатов тестов:
import platform
import psutil
import subprocess
def capture_environment_details():
env_details = {
"system": {
"os": platform.system(),
"os_version": platform.version(),
"architecture": platform.machine(),
"processor": platform.processor(),
"python_version": platform.python_version()
},
"hardware": {
"cpu_cores": psutil.cpu_count(logical=False),
"cpu_threads": psutil.cpu_count(logical=True),
"memory_total_gb": round(psutil.virtual_memory().total / (1024**3), 2),
"memory_available_gb": round(psutil.virtual_memory().available / (1024**3), 2)
},
"network": {
"hostname": platform.node(),
"ip_addresses": get_ip_addresses()
},
"dependencies": get_installed_packages(),
"browser_versions": get_browser_versions()
}
return env_details
def get_browser_versions():
versions = {}
# Версия Chrome
try:
result = subprocess.run(
['google-chrome', '--version'],
capture_output=True,
text=True
)
versions['chrome'] = result.stdout.strip()
except:
versions['chrome'] = 'Не установлен'
# Версия Firefox
try:
result = subprocess.run(
['firefox', '--version'],
capture_output=True,
text=True
)
versions['firefox'] = result.stdout.strip()
except:
versions['firefox'] = 'Не установлен'
return versions
Матрица Сравнения Окружений
Когда тесты проваливаются в одном окружении, но проходят в другом, систематическое сравнение обязательно:
Компонент | Dev Окружение | Staging Окружение | Production Окружение |
---|---|---|---|
Версия Приложения | v2.4.1-dev | v2.4.1-RC3 | v2.4.0 |
Версия БД | PostgreSQL 15.3 | PostgreSQL 15.3 | PostgreSQL 15.2 |
ОС | Ubuntu 22.04 | Ubuntu 22.04 | Ubuntu 20.04 |
Node.js | v20.10.0 | v20.10.0 | v18.18.0 |
Redis | 7.2.0 | 7.2.0 | 7.0.11 |
Load Balancer | Нет | Nginx 1.24 | Nginx 1.22 |
Обеспечение Воспроизводимости Тестов
Чек-Лист Воспроизводимости
Выполнение теста воспроизводимо, когда другой тестировщик может следовать журналу и достичь идентичных результатов:
Документация Предварительных Требований:
- Скрипты настройки тестовых данных
- Процедуры заполнения базы данных
- Состояния конфигурационных файлов
- Конфигурации моков сторонних сервисов
- Зависимости от времени/даты (если применимо)
Пошаговое Руководство по Воспроизводимости:
# Руководство по Воспроизводимости Теста: EXEC-20250108-001
## Предварительные Требования
1. Окружение: Staging (https://staging.app.com)
2. Версия Сборки: v2.4.1-RC3
3. Тестовые Данные: Аккаунт пользователя testuser@example.com (пароль в vault)
4. Состояние БД: Запустить скрипт заполнения `db/seeds/auth_test_data.sql`
## Настройка Окружения
```bash
# Клонировать репозиторий
git clone https://github.com/company/app.git
cd app
git checkout v2.4.1-RC3
# Установить зависимости
npm install
# Настроить окружение
cp .env.staging .env
# Обновить DATABASE_URL в .env
# Заполнить тестовые данные
psql -U postgres -d app_staging -f db/seeds/auth_test_data.sql
Шаги Выполнения Теста
- Перейти на https://staging.app.com/login
- Проверить, что форма входа отображает поля email и пароля
- Ввести email: testuser@example.com
- Ввести пароль: [из vault]
- Нажать кнопку “Войти”
- Проверить перенаправление на панель управления в течение 2 секунд
- Проверить, что имя пользователя “Test User” отображается в заголовке
Ожидаемые Результаты
- Все шаги проходят
- Панель загружается за < 2 секунд
- Нет ошибок в консоли
- Cookie сессии установлен со сроком действия 24 часа
Очистка
# Удалить тестовые данные
psql -U postgres -d app_staging -f db/seeds/cleanup_auth_test_data.sql
### Автоматизированное Тестирование Воспроизводимости
```python
# Фреймворк валидации воспроизводимости
class ReproducibilityValidator:
def __init__(self, original_execution_log):
self.original = original_execution_log
self.reproduction_attempts = []
def attempt_reproduction(self, max_attempts=3):
for attempt in range(max_attempts):
print(f"Попытка воспроизведения {attempt + 1}/{max_attempts}")
# Настроить окружение
self.setup_environment(self.original['environment'])
# Выполнить тест-кейс
result = self.execute_test_case(self.original['testCaseId'])
# Сравнить результаты
comparison = self.compare_results(self.original, result)
self.reproduction_attempts.append({
"attempt": attempt + 1,
"result": result,
"comparison": comparison,
"is_reproducible": comparison['match_percentage'] >= 95
})
if comparison['match_percentage'] >= 95:
return True
return False
def compare_results(self, original, reproduction):
differences = []
matches = 0
total_checks = 0
# Сравнить статус
if original['status'] == reproduction['status']:
matches += 1
else:
differences.append(f"Несоответствие статуса: {original['status']} vs {reproduction['status']}")
total_checks += 1
# Сравнить шаги
for orig_step, repro_step in zip(original['steps'], reproduction['steps']):
if orig_step['status'] == repro_step['status']:
matches += 1
else:
differences.append(
f"Несоответствие статуса шага {orig_step['stepNumber']}: "
f"{orig_step['status']} vs {repro_step['status']}"
)
total_checks += 1
match_percentage = (matches / total_checks) * 100
return {
"match_percentage": match_percentage,
"differences": differences,
"total_checks": total_checks,
"matches": matches
}
Хранение и Управление Логами
Архитектура Хранения
Эффективное хранение логов балансирует доступность, сохранность и стоимость:
Уровни Хранения:
Уровень | Сохранность | Тип Хранилища | Паттерн Доступа |
---|---|---|---|
Горячий | 30 дней | SSD / База данных | Частый доступ, быстрые запросы |
Теплый | 90 дней | Объектное хранилище (S3) | Периодический доступ |
Холодный | 1-7 лет | Архивное хранилище (Glacier) | Compliance, редкий доступ |
Пример Реализации:
# Система сохранения и архивирования логов
from datetime import datetime, timedelta
import boto3
import json
class ExecutionLogManager:
def __init__(self):
self.db = DatabaseConnection()
self.s3 = boto3.client('s3')
self.bucket = 'test-execution-logs'
def store_execution_log(self, execution_log):
# Сохранить в горячий уровень (база данных) для недавнего доступа
self.db.insert('execution_logs', execution_log)
# Также резервное копирование в S3 для надежности
s3_key = f"logs/{execution_log['executionId']}.json"
self.s3.put_object(
Bucket=self.bucket,
Key=s3_key,
Body=json.dumps(execution_log),
StorageClass='STANDARD'
)
def archive_old_logs(self):
# Переместить логи старше 30 дней на теплый уровень
cutoff_date = datetime.now() - timedelta(days=30)
old_logs = self.db.query(
'SELECT * FROM execution_logs WHERE execution_time < %s',
(cutoff_date,)
)
for log in old_logs:
# Переход на STANDARD_IA (теплый уровень)
s3_key = f"logs/{log['executionId']}.json"
self.s3.copy_object(
Bucket=self.bucket,
CopySource={'Bucket': self.bucket, 'Key': s3_key},
Key=s3_key,
StorageClass='STANDARD_IA'
)
# Удалить из горячей базы данных
self.db.delete('execution_logs', {'executionId': log['executionId']})
def archive_compliance_logs(self):
# Переместить логи старше 90 дней на холодный уровень (Glacier)
cutoff_date = datetime.now() - timedelta(days=90)
# Переход на GLACIER для долгосрочного хранения
lifecycle_policy = {
'Rules': [{
'Id': 'ArchiveOldLogs',
'Status': 'Enabled',
'Prefix': 'logs/',
'Transitions': [{
'Days': 90,
'StorageClass': 'GLACIER'
}],
'Expiration': {
'Days': 2555 # 7 лет для compliance
}
}]
}
self.s3.put_bucket_lifecycle_configuration(
Bucket=self.bucket,
LifecycleConfiguration=lifecycle_policy
)
Лучшие Практики и Распространенные Ошибки
Лучшие Практики
- Стандартизировать Форматы Логов: Использовать согласованные JSON или XML схемы во всех выполнениях тестов
- Автоматизировать Сбор Доказательств: Ручной захват скриншотов подвержен ошибкам; автоматизировать везде, где возможно
- Контроль Версий Тестовых Данных: Хранить скрипты настройки тестовых данных в системе контроля версий
- Немедленно Связывать Дефекты: Ссылаться на баг-тикеты в журналах выполнения, как только обнаружены проблемы
- Включать Метрики Производительности: Всегда логировать длительность выполнения и использование системных ресурсов
- Поддерживать Прослеживаемость: Связывать журналы выполнения с тест-кейсами, требованиями и спринтами
Распространенные Ошибки, Которых Следует Избегать
Ошибка | Влияние | Решение |
---|---|---|
Отсутствие деталей окружения | Невозможно воспроизвести сбои | Автоматический захват окружения |
Недостаточно доказательств | Споры по дефектам, задержки отладки | Обязательные правила скриншотов/логов |
Непоследовательное именование | Хаос в организации доказательств | Строгие соглашения по именованию |
Нет политики хранения логов | Взрывной рост затрат на хранение | Многоуровневая стратегия хранения |
Отсутствие состояния тестовых данных | Ложные сбои | Снимок/восстановление базы данных |
Путаница с часовыми поясами | Баги, связанные с временем | Всегда использовать временные метки UTC |
Заключение
Комплексное журналирование выполнения тестов — это не просто документация — это инвестиция в качество, эффективность и командную работу. Хорошо поддерживаемые журналы выполнения ускоряют отладку, обеспечивают точный анализ трендов, поддерживают требования соответствия и строят институциональные знания, которые сохраняются за пределами отдельных членов команды.
Внедряя структурированные практики журналирования, автоматизированный сбор доказательств и надежные стратегии хранения, QA команды преобразуют тестирование из временной активности в ценный, постоянный актив, который непрерывно улучшает качество программного обеспечения.