Modern QA2026Регулярные выражения и парсинг
Join

Course12 Programming for QA

Foundations · Chapter 12

Регулярные выражения и парсинг

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"])

Практическое упражнение

  1. Напишите регулярное выражение, извлекающее все строки логов уровня ERROR из файла логов и группирующее их по имени сервиса (текст в квадратных скобках, например [PaymentService])
  2. Распарсите JSON-ответ API и проверьте, что все элементы имеют обязательные поля, корректные типы и не содержат чувствительных данных
  3. Напишите функцию, которая конвертирует HAR-файл (формат JSON) в сводку: общее количество запросов, среднее время ответа и количество ошибок
  4. Создайте файл тестовых данных на YAML и напишите параметризованный тест pytest, читающий из него

Ключевые выводы

  • Регулярные выражения необходимы для парсинга логов, валидации форматов и извлечения данных
  • Знайте основные паттерны: \d, \w, \b, +, *, ?, (), []
  • Всегда используйте yaml.safe_load() для предотвращения выполнения кода
  • Парсинг JSON: обрабатывайте крайние случаи (пустое тело, не-JSON ответ, большие числа)
  • Комбинируйте регулярные выражения с парсингом для мощной обработки тестовых данных