Паттерн критик-актёр
Updated Jul 2026
Состязательное качество через разделение ответственности
Паттерн критик-актёр разделяет генерацию тестов и их ревью. Один агент (актёр) генерирует тесты. Другой агент (критик) ревьюит и оспаривает их. Это вдохновлено состязательным обучением в машинном обучении и генеративно-состязательными сетями (GAN).
Ключевой инсайт: одиночный агент, генерирующий и оценивающий собственный результат, имеет конфликт интересов. Он склонен одобрять то, что создал. Разделение этих ролей производит измеримо более качественные тесты.
Архитектура
+----------+ +----------+
| ACTOR |--------→| CRITIC |
| (writes | | (reviews |
| tests) |←--------| tests) |
+----------+ +----------+
|
| After critic approves
v
+----------+
| FINAL |
| SUITE |
+----------+
Поток:
- Актёр генерирует тесты из спецификации
- Критик ревьюит каждый тест относительно спецификации
- Критик возвращает обратную связь: APPROVE, REVISE или REJECT для каждого теста
- Актёр дорабатывает на основе обратной связи
- Повторять, пока критик не одобрит 90%+ тестов (или достигнут лимит раундов)
- Финальный набор = все одобренные тесты
Реализация
class CriticActorPipeline:
def __init__(self, actor: Agent, critic: Agent, max_rounds=3):
self.actor = actor
self.critic = critic
self.max_rounds = max_rounds
def generate_reviewed_tests(self, spec: str) -> TestSuite:
# Actor generates initial test suite
tests = self.actor.generate(spec)
print(f"Actor generated {len(tests)} tests")
for round_num in range(self.max_rounds):
# Critic reviews all tests
review = self.critic.review(tests, spec)
print(f"Round {round_num + 1}: "
f"{review.approved_count} approved, "
f"{review.revise_count} need revision, "
f"{review.rejected_count} rejected")
if review.approval_rate >= 0.9: # 90%+ tests approved
print(f"Critic satisfied after {round_num + 1} rounds")
break
# Actor revises based on critic feedback
tests = self.actor.revise(tests, review.feedback)
# Final suite: only approved tests
return TestSuite(tests=[t for t in tests if t.status == "approved"])
Промпт ревью критика
Критик -- это движок качества. Его промпт должен быть строгим и конкретным:
You are a senior QA architect reviewing AI-generated tests. For each test:
1. Does the assertion actually verify the requirement? (not a tautology)
2. Would this test catch the bug it claims to test? (mutation analysis)
3. Is this test independent and deterministic?
4. Is there a simpler way to test the same thing?
5. Is anything missing from the specification that should be tested?
Rate each test: APPROVE, REVISE (with specific feedback), or REJECT (with reason).
Also identify any GAPS: scenarios from the spec that no test covers.
Specification:
{spec}
Tests to review:
{tests}
Output as JSON:
{
"reviews": [
{
"test_name": "...",
"verdict": "APPROVE|REVISE|REJECT",
"feedback": "...", // Required for REVISE and REJECT
"mutation_survives": true|false // Would removing the assertion miss a bug?
}
],
"gaps": [
"Description of untested scenario"
]
}
Пример вывода критика
{
"reviews": [
{
"test_name": "test_create_user_valid",
"verdict": "REVISE",
"feedback": "Assertion only checks status code 201. Add assertions for response body: verify 'id' is present, 'email' matches input, 'created_at' is recent. Without these, a mutation that breaks user creation but returns 201 would go undetected.",
"mutation_survives": true
},
{
"test_name": "test_create_user_duplicate_email",
"verdict": "APPROVE",
"feedback": "Good test. Verifies 409 status and error message mentioning 'email'. Tight assertions.",
"mutation_survives": false
},
{
"test_name": "test_get_user",
"verdict": "REJECT",
"feedback": "This is a tautology. The mock returns {'name': 'Alice'} and the assertion checks name == 'Alice'. This tests the mock, not the code. Replace with a test that verifies the actual database query or service logic.",
"mutation_survives": true
}
],
"gaps": [
"No test for creating a user with an email longer than 254 characters (RFC 5321 limit)",
"No test for concurrent creation of two users with the same email (race condition)",
"No test for the password hashing (verify stored password is not plaintext)"
]
}
Промпт доработки актёра
Когда критик возвращает обратную связь REVISE, актёр получает целевые инструкции:
The following tests need revision based on code review feedback.
For each test, apply the suggested changes while maintaining the test's intent.
Original test:
{original_test_code}
Reviewer feedback:
{critic_feedback}
Requirements:
1. Apply the specific feedback
2. Do not change other aspects of the test
3. Maintain the same naming convention
4. Keep the test self-contained (no new external dependencies)
Also generate new tests for these identified gaps:
{gaps}
Многораундовая сходимость
Цикл критик-актёр обычно сходится за 2-3 раунда:
Round 1: Actor generates 30 tests
Critic: 15 approved, 10 revise, 5 rejected
Approval rate: 50%
Round 2: Actor revises 10, generates 5 new for gaps
Critic: 25 approved, 5 revise, 0 rejected
Approval rate: 83%
Round 3: Actor revises 5
Critic: 29 approved, 1 revise, 0 rejected
Approval rate: 97% → DONE
Почему max_rounds важен: Без ограничения цикл критик-актёр может итерировать бесконечно, при этом критик находит всё более незначительные замечания. Установите max_rounds=3, чтобы сбалансировать качество и стоимость.
Бенчмарки качества
В бенчмарках пайплайны критик-актёр производят измеримо лучшие тесты:
| Метрика | Генерация одним агентом | Пайплайн критик-актёр | Улучшение |
|---|---|---|---|
| Доля тавтологий | 15-20% | 2-5% | Снижение на 75% |
| Показатель мутаций | 55-65% | 70-80% | +15-25 пунктов |
| Выявленные пробелы покрытия | 0 (по определению) | 3-5 на спецификацию | Н/Д |
| Плотность утверждений (на тест) | 1.5 | 3.2 | 2x |
| Время ревью (человеком) | 30 мин / 30 тестов | 10 мин / 30 тестов | Быстрее в 3 раза |
Время человеческого ревью сокращается, потому что критик уже выявил наиболее распространённые проблемы (тавтологии, слабые утверждения, пропущенные сценарии).
Вариант: пайплайн из трёх агентов
Для ещё более высокого качества добавьте третьего агента -- аналитика спецификаций:
+------------+ +----------+ +----------+
| SPEC |------→| ACTOR |------→| CRITIC |
| ANALYST | | (writes | | (reviews |
| (extracts | | tests) |←------| tests) |
| test | +----------+ +----------+
| scenarios)|
+------------+
Аналитик спецификаций читает спецификацию и выдаёт структурированный список тестовых сценариев до того, как актёр напишет какой-либо код. Это предотвращает пропуск актёром сценариев, которые критик позже определил бы как пробелы.
class SpecAnalyst:
def analyze(self, spec: str) -> list[TestScenario]:
return self.llm.generate(f"""
Analyze this specification and enumerate every testable scenario:
{spec}
For each scenario:
- Category: happy_path | error | boundary | security | performance
- Description: one sentence
- Input: what data to use
- Expected outcome: what should happen
- Priority: must_have | should_have | nice_to_have
""")
Когда использовать паттерн критик-актёр
Лучше всего подходит для:
- Когда качество тестов важнее скорости
- Критических путей (аутентификация, платежи, целостность данных)
- Регулируемых сред (здравоохранение, финансы), где качество тестов подлежит аудиту
- Команд, только начинающих работу с ИИ-генерацией тестов (критик ловит типичные ошибки ИИ)
Компромисс: В 2-3 раза дороже, чем генерация одним агентом (несколько вызовов LLM за раунд). Но улучшение качества часто экономит больше времени на человеческом ревью, чем стоит в токенах.
Ключевой вывод
Паттерн критик-актёр производит ИИ-генерированные тесты наивысшего качества, разделяя генерацию и оценку. Критик ловит тавтологии, слабые утверждения и пробелы покрытия, которые одиночные агенты пропускают. В бенчмарках этот паттерн достигает на 15-25% более высоких показателей мутаций, чем однопроходная генерация, что делает его золотым стандартом для наборов тестов, критичных к качеству.