Привет! Меня зовут Александр и я Flutter-разработчик. В этой статье хочу рассказать о том как я подружил ИИ-агентов с интеграционными тестами Flutter, какой инструмент пришлось для этого написать и что вообще из этого вышло. Летс гоу.
Проблема
Представьте, что вы попросили агента написать для вас интеграционный тест. На моих проектах очень часто это выглядело следующим образом:
-
Агент изучает код
-
Пишет тест
-
Запускает flutter test
-
Тест не проходит
-
Агент пытается понять в чем дело, делает фикс
-
Переходит к пункту 3
И таких итераций может быть много. Каждая из них это сжигание токенов, контекстного окна, времени на очередное "я нашел в чем проблема, сейчас точно заработает" и времени на пересборку. По моему личному опыту, на такой цикл может потратиться и 15 и 20 минут или он вообще может закончиться без успешного результата, с забитым контекстым окном и несколькими саммарайзами.
Таким образом определились следующие узкие места при разработке интеграционных тестов:
-
Сжигание токенов и контекстного окна на чтение всех логов
-
Время на пересборку
-
Непонимание только по логам на каком этапе теста возникла проблема
-
Время и токены на исправление несуществующих проблем и создание новых
-
Отсутствие у агента визуального представления того что на экране
Все это приводит к дополнительным итерациям и увеличению времени каждой из них. В ходе решения этой дилеммы, я написал инструмент который покрывает все эти проблемы.
Решение
Testwire - это утилита для пошагового исполнения интеграционных тестов. Тест разбивается на логические шаги, которые имеют состояния: не выполнен, выполнен, выполняется, выполнен с ошибкой. Агент запускает тест, подключается по MCP к тесту через VM Service и контролирует его выполнение.
Как это выглядит с точки зрения кода (о том почему тест пишется в виде отдельного класса чуть позже):
class MyTest extends TestwireTest {
MyTest() : super(
'Submit feedback form',
setUp: (tester) async {
app.main();
await tester.pumpAndSettle();
},
);
@override
Future<void> body(WidgetTester tester) async {
await step(
description: 'Navigate to Leave Review',
context: 'Tap the "Leave Review" tile on the home screen.',
action: () async {
await tester.tap(find.byKey(const Key('leave_review_tile')));
await tester.pumpAndSettle();
},
);
await step(
description: 'Enter name',
context: 'Type "Alex" into the name field.',
action: () async {
await tester.enterText(
find.byKey(const Key('name_field')), 'Alex');
await tester.pumpAndSettle();
},
);
await step(
description: 'Tap 5-star rating',
context: 'Tap the 5th star to set rating to 5.',
action: () async {
await tester.tap(find.byKey(const Key('star_5')));
await tester.pumpAndSettle();
},
);
await step(
description: 'Verify result',
context: 'Check that the success message is displayed.',
action: () async {
expect(find.text('Thank you!'), findsOneWidget);
expect(find.text('5 stars from Alex'), findsOneWidget);
},
);
}
}
Доступные MCP инструменты:
|
Инструмент |
Что делает |
|---|---|
|
|
Подключиться к тесту через VM service URI |
|
|
Следующий шаг, потом пауза |
|
|
Выполнить все оставшиеся шаги (стоп при ошибке) |
|
|
Перепрогнать упавший шаг |
|
|
Статус всех шагов |
|
|
Hot reload с сохранением прогресса |
|
|
Полный рестарт |
|
|
Скриншот UI |
|
|
Отключиться |
Hot Reload - ключевая механика
Для разработки, в агентном режиме, тест запускается через flutter run, таким образом позволяя агенту подключиться к VM (в том числе через Dart MCP), считывать состояние, делать скриншоты.
Если какой-то шаг зафейлился, то выполнение теста приостанавливается и агент выясняет причины фейла уже имея доступ не только к логам но и к VM, а так же к визуальному состоянию. После фикса агент делает hot reload и делает ретрай последнего шага.
Именно из-за того что агенту необходим hot reload, тесты в testwire это именно отдельный класс а не просто функция.
Как это работает
Три компонента:
-
Тест - запускается через
flutter run(неtest!) с--dart-define=AGENT_MODE=true. Приложение стартует в дебаг-режиме, тест регистрирует экстеншены Dart VM service и ждет -
testwire_mcp - MCP сервер, который подключается к тесту через VM service, предоставляя агенту необходимые инструменты
-
ИИ-агент - ваш любимый MCP-клиент, использует предоставленные инструменты, имеет доступ к состоянию каждого шага
Как это меняет мою разработку
Неожиданным образом я так же обнаружил, что теперь могу дать агенту задачу и для верификации ее выполнения попросить написать интеграционный тест с использованием testwire (заранее подготовив для этого отдельный скилл), убивая при этом одним выстрелом двух зайцев: логически работающая фича, работающий интеграционный тест. Интеграционный тест при этом становится не чем-то что я может быть потом напишу, а может и нет, а обязательным пунктом, частью задачи.
Один файл - два режима
При этом на CI тест запускается в том же файле, но как обычный, без флага AGENT_MODE. То есть два режима:
# Агентский режим с hot reload
flutter run --dart-define=AGENT_MODE=true integration_test/my_test.dart
# Обычный CI прогон
flutter test integration_test/my_test.dart
Заключение
Сам инструмент не гарантирует, что все с первого раза заведется и заработает в вашем конкретном случае. Интеграция в проект может потребовать дополнительных настроек в виде написания дополнительных агентских скиллов, документации по проекту и т.д.
Буду рад фидбэку. Посмотреть примеры и как стартануть можно по ссылке в GitHub репозитории.
Автор: afppvv
