Регулярные выражения и парсинг
Updated Jul 2026
Регулярные выражения и парсинг данных — повседневные инструменты QA-инженера. Вы парсите файлы логов для поиска паттернов ошибок, валидируете форматы ответов, извлекаете идентификаторы транзакций из текста и обрабатываете конфигурационные файлы. Эти навыки превращают необработанный текст в пригодные для использования тестовые данные.
Регулярные выражения для парсинга логов
import re
log_line = '2024-03-15 14:23:01 ERROR [PaymentService] Transaction TX-98712 failed: insufficient_funds'
# Extract transaction ID
match = re.search(r'TX-\d+', log_line)
tx_id = match.group() # "TX-98712"
# Extract log level
level = re.search(r'\b(DEBUG|INFO|WARN|ERROR|FATAL)\b', log_line).group() # "ERROR"
# Extract timestamp
timestamp = re.search(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}', log_line).group()
# Parse multiple log lines
with open("app.log") as f:
errors = [line for line in f if re.search(r'\bERROR\b', line)]
print(f"Found {len(errors)} error lines")
Основные паттерны регулярных выражений для QA
| Паттерн | Что совпадает | Применение |
|---|---|---|
\d{3}-\d{4} |
123-4567 | Фрагменты телефонных номеров |
\b[A-Z]{2,}\b |
ERROR, WARN | Извлечение уровня логирования |
"[^"]*" |
Любая строка в кавычках | Извлечение значений из текста |
\d{4}-\d{2}-\d{2} |
2024-03-15 | Извлечение даты в формате ISO |
\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z]{2,}\b |
email@example.com | Валидация email |
\b\d{1,3}(\.\d{1,3}){3}\b |
192.168.1.1 | Извлечение IP-адреса |
TX-\d+ |
TX-98712 | Извлечение ID транзакции/заказа |
v\d+\.\d+\.\d+ |
v2.4.1 | Извлечение номера версии |
[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12} |
Формат UUID | Извлечение UUID |
Регулярные выражения в тестовых проверках
# Validate response format
import re
def test_user_id_format(api_response):
"""User IDs should be UUIDs."""
user_id = api_response.json()["id"]
uuid_pattern = r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
assert re.match(uuid_pattern, user_id), f"Invalid UUID format: {user_id}"
def test_email_format(api_response):
"""Email should be valid format."""
email = api_response.json()["email"]
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
assert re.match(email_pattern, email), f"Invalid email format: {email}"
def test_error_message_no_stack_trace(error_response):
"""Error messages should not contain internal stack traces."""
body = error_response.text
assert not re.search(r'at .*\(.*:\d+:\d+\)', body), "Stack trace exposed in error"
assert not re.search(r'Traceback \(most recent call last\)', body), "Python traceback exposed"
assert not re.search(r'/usr/src/app', body), "Internal file paths exposed"
Регулярные выражения в TypeScript
test("API response contains valid ISO date", () => {
const createdAt = response.data.created_at;
const isoDatePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
expect(createdAt).toMatch(isoDatePattern);
});
test("Error response does not leak internals", () => {
const body = JSON.stringify(errorResponse.data);
expect(body).not.toMatch(/at .*\(.*:\d+:\d+\)/);
expect(body).not.toMatch(/node_modules/);
});
Строительные блоки регулярных выражений
| Символ | Значение | Пример |
|---|---|---|
. |
Любой символ | a.c совпадает с "abc", "a1c" |
\d |
Цифра | \d{3} совпадает с "123" |
\w |
Символ слова (буква, цифра, подчёркивание) | \w+ совпадает с "hello_world" |
\s |
Пробельный символ | \s+ совпадает с пробелами, табуляциями, переносами строк |
\b |
Граница слова | \berror\b совпадает с "error", но не с "errors" |
^ |
Начало строки | ^Hello совпадает с "Hello world" |
$ |
Конец строки | world$ совпадает с "Hello world" |
* |
Ноль или более | ab*c совпадает с "ac", "abc", "abbc" |
+ |
Один или более | ab+c совпадает с "abc", "abbc" (не с "ac") |
? |
Ноль или один | colou?r совпадает с "color" и "colour" |
{n,m} |
От n до m | \d{2,4} совпадает с "12", "123", "1234" |
[abc] |
Класс символов | [aeiou] совпадает с любой гласной |
[^abc] |
Отрицание класса | [^0-9] совпадает с нецифровыми символами |
(...) |
Группа захвата | (\d+)-(\d+) захватывает "123" и "456" из "123-456" |
(?:...) |
Группа без захвата | Группировка без захвата |
\1 |
Обратная ссылка | Совпадает с первой захваченной группой |
Парсинг JSON
JSON — универсальный формат данных для API. Свободное владение JSON-обработкой необходимо.
import json
# Parse API response
data = json.loads(response.text)
assert data["users"][0]["name"] == "Alice"
# Pretty print for debugging
print(json.dumps(data, indent=2))
# Validate nested structure
def validate_user_structure(user: dict):
required = {"id", "name", "email", "created_at"}
assert required.issubset(user.keys()), f"Missing: {required - user.keys()}"
assert isinstance(user["id"], int)
assert isinstance(user["name"], str) and len(user["name"]) > 0
assert "@" in user["email"]
for user in data["users"]:
validate_user_structure(user)
Обработка крайних случаев JSON
# Empty response body
if response.text:
data = response.json()
else:
data = None
# Non-JSON response (HTML error page)
try:
data = response.json()
except json.JSONDecodeError:
pytest.fail(f"Response is not JSON: {response.text[:200]}")
# Large numbers (precision loss)
# JSON spec does not define number precision. Some APIs return IDs as strings.
user_id = data["id"] # Could be int or string depending on API
Парсинг YAML
YAML распространён в конфигурационных файлах, CI/CD-пайплайнах и тестовых данных.
import yaml
# Load test configuration
with open("config.yaml") as f:
config = yaml.safe_load(f)
base_url = config["environments"]["staging"]["url"]
timeout = config["environments"]["staging"]["timeout"]
# Load test data from YAML
with open("test_users.yaml") as f:
test_data = yaml.safe_load(f)
# test_users.yaml:
# users:
# - email: admin@test.com
# role: admin
# - email: viewer@test.com
# role: viewer
Всегда используйте yaml.safe_load(), а не yaml.load(). Последний может выполнять произвольный код.
Парсинг CSV для тестовых данных
import csv
# Read test data from CSV
with open("test_cases.csv") as f:
reader = csv.DictReader(f)
test_data = list(reader)
# [{"email": "test1@test.com", "expected_status": "200"}, ...]
# Use in parametrized tests
@pytest.mark.parametrize("row", test_data)
def test_from_csv(api_client, row):
response = api_client.post("/auth/login", json={
"email": row["email"], "password": row["password"]
})
assert response.status_code == int(row["expected_status"])
Практическое упражнение
- Напишите регулярное выражение, извлекающее все строки логов уровня ERROR из файла логов и группирующее их по имени сервиса (текст в квадратных скобках, например
[PaymentService]) - Распарсите JSON-ответ API и проверьте, что все элементы имеют обязательные поля, корректные типы и не содержат чувствительных данных
- Напишите функцию, которая конвертирует HAR-файл (формат JSON) в сводку: общее количество запросов, среднее время ответа и количество ошибок
- Создайте файл тестовых данных на YAML и напишите параметризованный тест pytest, читающий из него
Ключевые выводы
- Регулярные выражения необходимы для парсинга логов, валидации форматов и извлечения данных
- Знайте основные паттерны:
\d,\w,\b,+,*,?,(),[] - Всегда используйте
yaml.safe_load()для предотвращения выполнения кода - Парсинг JSON: обрабатывайте крайние случаи (пустое тело, не-JSON ответ, большие числа)
- Комбинируйте регулярные выражения с парсингом для мощной обработки тестовых данных