Modern QA2026Пять ограждений для агентного тестирования
Join

Course03 Agentic Testing Architectures

Cutting-edge · Chapter 03

Пять ограждений для агентного тестирования

Updated Jul 2026

Почему ограждения обязательны

Неограниченные агенты опасны. Они могут работать бесконечно, получать доступ к ресурсам, к которым не должны, давать недетерминированные результаты и потреблять неограниченное количество токенов. Хорошо спроектированная тестовая обвязка ограничивает агента, сохраняя его способность к адаптации.

Принцип: свобода в рамках границ. Агент свободно действует внутри ограждений, но ограждения -- это жёсткие лимиты, которые нельзя превысить.

Пять ограждений

+--------------------------------------------------+
|              AGENT TEST HARNESS                  |
|                                               -  |
|  +---------------------------------------------+ |
|  | 1. MAX STEPS      (prevent infinite loops)  | |
|  | 2. ALLOWED DOMAINS (prevent unauthorized    | |
|  |                     resource access)        | |
|  | 3. TIMEOUT         (wall-clock limit)       | |
|  | 4. TOKEN BUDGET    (cost ceiling)           | |
|  | 5. ACTION ALLOWLIST (restrict operations)   | |
|  +---------------------------------------------+ |
|                                                  |
|  Agent operates freely WITHIN these boundaries   |
+--------------------------------------------------+

Ограждение 1: Максимум шагов

Предотвращает бесконечное зацикливание агента. Если агент выполнил более N действий, не достигнув цели, тест прерывается.

max_steps: int = 30  # Default: 30 actions before forced stop

Как устанавливать лимит:

  • Посчитайте шаги в вашем самом длинном ручном тестовом потоке (например, 15 шагов для потока оформления заказа)
  • Умножьте на 2, чтобы учесть накладные расходы агента на исследование
  • Это и есть ваш max_steps

Что происходит при превышении:

if self.step_count >= self.config.max_steps:
    return TestResult(
        status="ABORTED",
        reason=f"Max steps ({self.config.max_steps}) exceeded. "
               f"Agent could not complete objective in {self.step_count} steps.",
        steps_taken=self.step_count,
        last_observation=self.last_observation
    )

Ограждение 2: Разрешённые домены

Предотвращает навигацию агента по URL вне тестового окружения. Без этого агент, тестирующий веб-приложение, может перейти по ссылкам на внешние сайты, запустить потоки OAuth или даже взаимодействовать с продакшен-системами.

allowed_domains: list[str] = ["staging.myapp.com", "api.staging.myapp.com"]

Реализация:

def is_domain_allowed(self, url: str) -> bool:
    from urllib.parse import urlparse
    domain = urlparse(url).netloc
    if not self.config.allowed_domains:
        return True  # No restriction configured
    return any(
        domain == allowed or domain.endswith(f".{allowed}")
        for allowed in self.config.allowed_domains
    )

Пример из практики: Агент, тестирующий страницу входа, переходит по ссылке «Забыли пароль», которая перенаправляет на страницу OAuth почтового провайдера. Без ограничения доменов агент взаимодействовал бы со страницей входа Google. С ограничением действие блокируется и логируется.

Ограждение 3: Таймаут

Лимит по реальному времени, предотвращающий бесконтрольное выполнение тестов. Даже если агент остаётся в рамках бюджета шагов и токенов, медленно отвечающий сервер или зависший браузер могут держать тест запущенным бесконечно.

timeout_seconds: int = 300  # 5-minute wall clock limit

Реализация:

import time

def check_timeout(self) -> str | None:
    elapsed = time.time() - self.start_time
    if elapsed > self.config.timeout_seconds:
        return (f"Timeout ({self.config.timeout_seconds}s) exceeded. "
                f"Elapsed: {elapsed:.1f}s")
    return None

Совет: Установите два уровня таймаута:

  • Таймаут на действие: 30 секунд на браузерное действие (ловит зависания)
  • Таймаут на тест: 300 секунд на весь тест (ловит медленные циклы)

Ограждение 4: Бюджет токенов

Потолок стоимости, предотвращающий неограниченные вызовы LLM от агента. Каждый вызов стоит токенов, и затраты быстро накапливаются в многошаговых агентах.

max_tokens: int = 50_000  # Token budget per test run

Почему это важно финансово:

Поведение агента Токенов на тест Стоимость (Claude Sonnet)
10-шаговый простой тест ~10K $0.05
30-шаговый сложный тест ~30K $0.15
Неконтролируемый агент (без бюджета) ~500K+ $2.50+

Реализация:

def track_tokens(self, response) -> None:
    self.token_count += response.usage.input_tokens + response.usage.output_tokens
    if self.token_count >= self.config.max_tokens:
        raise TokenBudgetExceeded(
            f"Token budget ({self.config.max_tokens}) exhausted. "
            f"Used: {self.token_count}"
        )

Ограждение 5: Список разрешённых действий

Ограничивает, какие действия может выполнять агент. В мониторинге продакшена нужны только действия чтения. В CI -- всё, кроме деструктивных операций.

allowed_actions: list[str] = ["NAVIGATE", "CLICK", "TYPE", "ASSERT", "SCREENSHOT"]
# Blocked: "DELETE", "DROP", "RESET", "ADMIN_*"

Реализация:

def is_action_allowed(self, action) -> bool:
    if self.config.allowed_actions:
        if action.type not in self.config.allowed_actions:
            return False
    # Additional checks for specific action types
    if action.type == "NAVIGATE":
        if not self.is_domain_allowed(action.url):
            return False
    if action.type == "TYPE" and action.selector.contains("password"):
        # Never type real credentials -- use test tokens
        if not action.text.startswith("test_"):
            return False
    return True

Конфигурация ограждений по окружениям

Разные окружения требуют разных профилей ограждений:

Ограждение Разработка CI/CD Мониторинг продакшена
Максимум шагов 50 (щедрый) 30 (стандарт) 10 (минимальный)
Таймаут 10 минут 5 минут 2 минуты
Бюджет токенов 100K (исследование) 50K (стандарт) 10K (сфокусированный)
Разрешённые домены * (любой) staging.* prod.* (только чтение)
Действия Все Все кроме DELETE Только чтение (GET, наблюдение)
Поведение при сбое Лог + продолжить Провалить тест + скриншот Оповещение + скриншот

Загрузка конфигурации из окружения

from dataclasses import dataclass, field
import os

@dataclass
class HarnessConfig:
    max_steps: int = field(
        default_factory=lambda: int(os.getenv("AGENT_MAX_STEPS", "30"))
    )
    timeout_seconds: int = field(
        default_factory=lambda: int(os.getenv("AGENT_TIMEOUT", "300"))
    )
    max_tokens: int = field(
        default_factory=lambda: int(os.getenv("AGENT_MAX_TOKENS", "50000"))
    )
    allowed_domains: list[str] = field(
        default_factory=lambda: os.getenv("AGENT_ALLOWED_DOMAINS", "").split(",")
    )
    allowed_actions: list[str] = field(
        default_factory=lambda: os.getenv(
            "AGENT_ALLOWED_ACTIONS",
            "NAVIGATE,CLICK,TYPE,ASSERT,SCREENSHOT"
        ).split(",")
    )

Нарушения ограждений: логирование и оповещение

Каждое нарушение ограждения должно быть залогировано с достаточным контекстом для диагностики проблемы:

@dataclass
class GuardrailViolation:
    guardrail: str          # "max_steps", "timeout", "token_budget", etc.
    threshold: str          # "30 steps", "300s", "50000 tokens"
    actual: str             # "31 steps", "305s", "51234 tokens"
    test_objective: str
    last_observation: str   # What the agent saw before violation
    last_action: str        # What the agent tried to do
    history_summary: str    # Compressed history for debugging
    screenshot_path: str | None

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

Пять ограждений (максимум шагов, разрешённые домены, таймаут, бюджет токенов, список разрешённых действий) формируют систему глубокой обороны. Каждое ловит свой тип сбоя. Вместе они делают агентное тестирование безопасным для CI/CD-пайплайнов, где неограниченный агент может заблокировать деплой, потребить дорогие токены или взаимодействовать с системами за пределами тестовой области.