
Все говорят, что AI пишет код за секунды. Это правда. Но почему-то редко уточняют: какой именно код он пишет. Спойлер — это сильно зависит от того, как вы спросили. И ещё сильнее — от того, что вы вообще знаете о предмете.
В Ростелекоме мы уделяем особое внимание развитию экспертизы сотрудников. Это не просто корпоративный слоган — это осознанная стратегия. Мы понимаем, что технологии меняются быстро, но фундаментальные знания остаются ценными десятилетиями. Поэтому в компании работает система внутреннего обучения: курсы по автоматизации тестирования, менторинг от senior-специалистов, разбор реальных кейсов из проектов.
И когда появились AI-ассистенты, возник логичный вопрос: может, теперь курсы не нужны? Зачем учить людей писать тесты, если нейросеть сделает это за них? Некоторые коллеги из индустрии уже начали сокращать обучающие программы, делая ставку на "AI справится".
Мы в Ростелекоме решили не гадать, а проверить эту гипотезу на практике. Провели эксперимент: попросили AI-ассистента написать автотесты для классического PetStore API тремя разными способами. Первый запрос — как написал бы новичок, который только что узнал про AI. Второй — как специалист с базовым пониманием автоматизации. Третий — как инженер после наших внутренних курсов, с глубоким пониманием архитектуры тестовых фреймворков.
Результаты оказались настолько показательными, что мы решили поделиться ими с сообществом.

Меня зовут Дюжев Михаил. Уже пятнадцать лет я в ИТ, из них последние 6 лет— директор департамента обеспечения качества цифровых продуктов в Ростелеком. Занимаюсь тем, что строю процессы, развиваю команды и активно внедряю ИИ в разработку. Веду внутренние курсы по автоматизации — верю, что сильная экспертиза сотрудников важнее любых инструментов.
Условия эксперимента
Честно скажем сразу: мы использовали не какую-то среднюю модель, а Claude Opus — одну из самых мощных языковых моделей на рынке. Это принципиально важно для чистоты эксперимента. Мы хотели исключить фактор "слабой модели" и показать, что даже топовый AI в неопытных руках не даёт нужного результата.
Это как дать новичку профессиональный фотоаппарат за полмиллиона. Технически он может снять шедевр. Практически — получатся те же смазанные фотки, что и на телефон, только дороже.
Claude Opus отлично понимает контекст, знает современные практики разработки, умеет генерировать чистый код. Но он делает ровно то, что его просят. И вот тут начинается самое интересное.
Итерация 1: "Просто напиши тесты"
Начнём с того, как обычно пробуют AI новички. Минимум контекста, максимум надежды на магию. Человек слышал, что AI умеет писать код, открыл чат и написал первое, что пришло в голову.

Запрос:
Напиши автотесты на Java с REST Assured для PetStore API
Прямолинейно. Без деталей. Так мог бы написать стажёр в первый рабочий день или разработчик, которому поручили "быстро накидать тестов" без погружения в тему.
Результат — структура проекта:
src/
└── test/
└── java/
└── PetStoreTests.javaОдин файл. Вся логика в одном месте. Кла��сика "быстрого решения".
Результат — код:
import io.restassured.RestAssured;
import io.restassured.response.Response;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class PetStoreTests {
@BeforeAll
public static void setup() {
RestAssured.baseURI = "https://petstore.swagger.io/v2";
}
@Test
public void testGetPetById() {
given()
.pathParam("petId", 1)
.when()
.get("/pet/{petId}")
.then()
.statusCode(200)
.body("id", equalTo(1))
.body("name", notNullValue());
}
@Test
public void testCreatePet() {
String petJson = "{"
+ "\"id\": 12345,"
+ "\"name\": \"Buddy\","
+ "\"status\": \"available\""
+ "}";
given()
.contentType("application/json")
.body(petJson)
.when()
.post("/pet")
.then()
.statusCode(200)
.body("name", equalTo("Buddy"));
}
@Test
public void testUpdatePet() {
String petJson = "{"
+ "\"id\": 12345,"
+ "\"name\": \"Buddy Updated\","
+ "\"status\": \"sold\""
+ "}";
given()
.contentType("application/json")
.body(petJson)
.when()
.put("/pet")
.then()
.statusCode(200)
.body("name", equalTo("Buddy Updated"));
}
@Test
public void testDeletePet() {
given()
.pathParam("petId", 12345)
.when()
.delete("/pet/{petId}")
.then()
.statusCode(200);
}
}Что получили:
Работающие тесты. Серьёзно — они запустятся и даже что-то проверят. Для демонстрации на митинге или proof of concept — сойдёт. Начальник увидит зелёные галочки и будет доволен. Первые пару недель.
Что не так:
А потом начнутся проблемы. JSON собирается конкатенацией строк — попробуйте добавить поле с кавычками внутри и наслаждайтесь отладкой. Захардкоженные ID означают, что тесты зависят друг от друга — запустите их в другом порядке и получите падения. Нет переиспользования — каждый тест дублирует настройку. Нет структуры — попробуйте найти нужный тест через полгода, когда их станет сотня. Нет обработки ошибок — если тест упадёт, вы увидите stacktrace в три экрана вместо понятного сообщения.
Это код "на выброс". Для реального проекта — технический долг с первого дня. И обратите внимание: Claude Opus написал именно то, что его попросили. Качественно, с правильным синтаксисом, с корректными assertions. Но попросили — мало.
Итерация 2: Добавляем контекст
Теперь представим, что тесты пишет специалист с базовым опытом автоматизации. Он уже знает, что "всё в одном файле" — плохо. Слышал про POJO и понимает, зачем нужно логирование. Но глубокого понимания архитектуры тестовых фреймворков у него пока нет.

Запрос:
Напиши автотесты на Java с REST Assured для PetStore API.
Требования:
Использовать JUnit 5
Создать отдельные классы для разных endpoints (Pet, Store, User)
Использовать POJO для сериализации/десериализации JSON
Вынести базовую конфигурацию в отдельный класс
Добавить логирование запросов и ответов
Уже конкретнее. Человек указал технологии, обозначил структуру, упомянул несколько практик. Это уровень специалиста, который прошёл пару онлайн-курсов и почитал статьи.
Результат — структура проекта:
src/
├── main/java/
│ └── models/
│ ├── Pet.java
│ ├── Category.java
│ └── Tag.java
└── test/java/
├── config/
│ └── TestConfig.java
├── tests/
│ ├── PetTests.java
│ ├── StoreTests.java
│ └── UserTests.java
└── BaseTest.javaУже похоже на проект, а не на скрипт выходного дня.
Результат — код (ключевые фрагменты):
// models/Pet.java
public class Pet {
private Long id;
private String name;
private String status;
private Category category;
private List<Tag> tags;
public Pet() {}
public Pet(Long id, String name, String status) {
this.id = id;
this.name = name;
this.status = status;
}
// Getters и Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}// BaseTest.java
public class BaseTest {
@BeforeAll
public static void setup() {
RestAssured.baseURI = TestConfig.BASE_URL;
RestAssured.filters(
new RequestLoggingFilter(),
new ResponseLoggingFilter()
);
}
}// tests/PetTests.java
public class PetTests extends BaseTest {
private static Long createdPetId;
@Test
@Order(1)
public void testCreatePet() {
Pet pet = new Pet(null, "Buddy", "available");
createdPetId = given()
.contentType(ContentType.JSON)
.body(pet)
.when()
.post("/pet")
.then()
.statusCode(200)
.body("name", equalTo("Buddy"))
.extract()
.jsonPath()
.getLong("id");
}
@Test
@Order(2)
public void testGetPetById() {
given()
.pathParam("petId", createdPetId)
.when()
.get("/pet/{petId}")
.then()
.statusCode(200)
.body("id", equalTo(createdPetId.intValue()))
.body("name", equalTo("Buddy"));
}
@Test
@Order(3)
public void testUpdatePet() {
Pet updatedPet = new Pet(createdPetId, "Buddy Updated", "sold");
given()
.contentType(ContentType.JSON)
.body(updatedPet)
.when()
.put("/pet")
.then()
.statusCode(200)
.body("name", equalTo("Buddy Updated"));
}
@Test
public void testGetPetByInvalidId() {
given()
.pathParam("petId", 999999999)
.when()
.get("/pet/{petId}")
.then()
.statusCode(404);
}
}Что улучшилось:
Прогресс очевиден. POJO вместо строковой конкатенации — теперь компилятор поймает опечатку в названии поля. Разделение по файлам — легче навигация и поддержка. Базовый класс — конфигурация в одном месте, изменил URL — изменилось везде. Логирование — при отладке видно, что реально отправляется на сервер. Даже негативный тест появился — начали думать не только про happy path.
Что всё ещё не так:
Приглядитесь к аннотации @Order. Тесты выполняются строго в определённой последовательности, потому что зависят друг от друга через статическую переменную createdPetId. Это хрупкая конструкция: параллельный запуск невозможен, изменение одного теста может сломать все остальные, отладка превращается в детектив.
Нет слоя абстракции над API — если завтра изменится формат ответа, придётся править каждый тест. Бойлерплейт given().contentType().body() повторяется снова и снова. Нет фабрик для тестовых данных — при масштабировании будете копировать new Pet(...) с разными параметрами сотни раз.
Для небольшого проекта с командой в два человека — приемлемо. Для enterprise-решения с десятками тестировщиков — недостаточно.
Итерация 3: Профессиональный подход
А теперь представим, что запрос формирует инженер, прошедший нашу внутреннюю программу обучения в Ростелекоме. Он понимает принципы проектирования тестовых фреймворков, знает паттерны, видел, как устроены зрелые проекты в production. И он умеет донести это понимание до AI.

Запрос:
Напиши автотесты на Java с REST Assured для PetStore API.
Архитектура проекта — трёхуровневая:
API Layer (низкий уровень):
Классы-клиенты для каждого контроллера (PetApi, StoreApi, UserApi)
Методы возвращают Response для гибкой обработки
Инкапсуляция всех HTTP-деталей (headers, auth, content-type)
Service Layer (средний уровень):
Бизнес-методы с понятными именами (createPet, getPetById)
Автоматическая десериализация в POJO
Методы для типовых проверок (assertPetExists, assertPetDeleted)
Test Layer (высокий уровень):
Чистые тесты без технических деталей
Читаются как спецификация
Независимые друг от друга
Дополнительно:
TestDataFactory для генерации тестовых данных
Кастомные assertions через AssertJ
Builder pattern для POJO
Конфигурация через properties файл
Независимые тесты: каждый создаёт и удаляет свои данные
Это уже не просто запрос — это техническое задание. Так формулирует требования человек, который точно знает, что хочет получить на выходе. И Claude Opus, получив такой ввод, выдаёт совершенно другой результат.
Результат — структура проекта:
src/
├── main/java/
│ └── com/petstore/
│ └── models/
│ ├── Pet.java
│ ├── Category.java
│ ├── Tag.java
│ └── builders/
│ └── PetBuilder.java
└── test/java/
└── com/petstore/
├── api/
│ ├── BaseApi.java
│ ├── PetApi.java
│ ├── StoreApi.java
│ └── UserApi.java
├── services/
│ ├── PetService.java
│ └── assertions/
│ └── PetAssertions.java
├── utils/
│ ├── ConfigReader.java
│ └── TestDataFactory.java
├── tests/
│ ├── BaseTest.java
│ └── pet/
│ ├── CreatePetTests.java
│ ├── GetPetTests.java
│ ├── UpdatePetTests.java
│ └── DeletePetTests.java
└── resources/
└── config.propertiesЭто уже не набор файлов — это архитектура. Каждый компонент на своём месте, каждый слой отвечает за свою зону ответственности.
Результат — код (ключевые фрагменты):
// models/Pet.java с Builder
public class Pet {
private Long id;
private String name;
private String status;
private Category category;
private List<Tag> tags;
private Pet(PetBuilder builder) {
this.id = builder.id;
this.name = builder.name;
this.status = builder.status;
this.category = builder.category;
this.tags = builder.tags;
}
public Long getId() { return id; }
public String getName() { return name; }
public String getStatus() { return status; }
public static PetBuilder builder() {
return new PetBuilder();
}
}// models/builders/PetBuilder.java
public class PetBuilder {
Long id;
String name;
String status;
Category category;
List<Tag> tags = new ArrayList<>();
public PetBuilder withId(Long id) {
this.id = id;
return this;
}
public PetBuilder withName(String name) {
this.name = name;
return this;
}
public PetBuilder withStatus(String status) {
this.status = status;
return this;
}
public Pet build() {
return new Pet(this);
}
}// api/BaseApi.java
public abstract class BaseApi {
protected final RequestSpecification spec;
protected BaseApi() {
this.spec = new RequestSpecBuilder()
.setBaseUri(ConfigReader.getBaseUrl())
.setContentType(ContentType.JSON)
.addFilter(new AllureRestAssured())
.addFilter(new RequestLoggingFilter())
.addFilter(new ResponseLoggingFilter())
.build();
}
protected Response get(String path, Object... pathParams) {
return given().spec(spec).when().get(path, pathParams);
}
protected Response post(String path, Object body) {
return given().spec(spec).body(body).when().post(path);
}
protected Response put(String path, Object body) {
return given().spec(spec).body(body).when().put(path);
}
protected Response delete(String path, Object... pathParams) {
return given().spec(spec).when().delete(path, pathParams);
}
}// api/PetApi.java
public class PetApi extends BaseApi {
private static final String PET_ENDPOINT = "/pet";
private static final String PET_BY_ID_ENDPOINT = "/pet/{petId}";
public Response createPet(Pet pet) {
return post(PET_ENDPOINT, pet);
}
public Response getPetById(Long petId) {
return get(PET_BY_ID_ENDPOINT, petId);
}
public Response updatePet(Pet pet) {
return put(PET_ENDPOINT, pet);
}
public Response deletePet(Long petId) {
return delete(PET_BY_ID_ENDPOINT, petId);
}
}// services/PetService.java
public class PetService {
private final PetApi petApi = new PetApi();
public Pet createPet(Pet pet) {
return petApi.createPet(pet)
.then()
.statusCode(200)
.extract()
.as(Pet.class);
}
public Pet getPetById(Long petId) {
return petApi.getPetById(petId)
.then()
.statusCode(200)
.extract()
.as(Pet.class);
}
public Optional<Pet> findPetById(Long petId) {
Response response = petApi.getPetById(petId);
if (response.statusCode() == 200) {
return Optional.of(response.as(Pet.class));
}
return Optional.empty();
}
public void deletePet(Long petId) {
petApi.deletePet(petId).then().statusCode(200);
}
public void deletePetIfExists(Long petId) {
if (findPetById(petId).isPresent()) {
deletePet(petId);
}
}
}// services/assertions/PetAssertions.java
public class PetAssertions {
public static void assertPetEquals(Pet actual, Pet expected) {
assertThat(actual)
.as("Pet should match expected")
.satisfies(pet -> {
assertThat(pet.getName()).isEqualTo(expected.getName());
assertThat(pet.getStatus()).isEqualTo(expected.getStatus());
});
}
public static void assertPetExists(PetService service, Long petId) {
assertThat(service.findPetById(petId))
.as("Pet with id %d should exist", petId)
.isPresent();
}
public static void assertPetNotExists(PetService service, Long petId) {
assertThat(service.findPetById(petId))
.as("Pet with id %d should not exist", petId)
.isEmpty();
}
}// utils/TestDataFactory.java
public class TestDataFactory {
private static final Faker faker = new Faker();
private static final AtomicLong idGenerator =
new AtomicLong(System.currentTimeMillis());
public static Pet randomPet() {
return Pet.builder()
.withId(idGenerator.incrementAndGet())
.withName(faker.animal().name())
.withStatus(randomStatus())
.build();
}
public static Pet petWithName(String name) {
return Pet.builder()
.withId(idGenerator.incrementAndGet())
.withName(name)
.withStatus("available")
.build();
}
private static String randomStatus() {
String[] statuses = {"available", "pending", "sold"};
return statuses[faker.random().nextInt(statuses.length)];
}
}// tests/BaseTest.java
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public abstract class BaseTest {
protected PetService petService;
protected List<Long> createdPetIds;
@BeforeAll
void setupAll() {
petService = new PetService();
}
@BeforeEach
void setup() {
createdPetIds = new ArrayList<>();
}
@AfterEach
void cleanup() {
createdPetIds.forEach(id -> petService.deletePetIfExists(id));
}
protected Pet createAndTrack(Pet pet) {
Pet created = petService.createPet(pet);
createdPetIds.add(created.getId());
return created;
}
}// tests/pet/CreatePetTests.java
@DisplayName("Create Pet API Tests")
public class CreatePetTests extends BaseTest {
@Test
@DisplayName("Should create pet with valid data")
void shouldCreatePetWithValidData() {
// Arrange
Pet petToCreate = TestDataFactory.randomPet();
// Act
Pet createdPet = createAndTrack(petToCreate);
// Assert
assertThat(createdPet.getId()).isNotNull();
assertPetEquals(createdPet, petToCreate);
}
@Test
@DisplayName("Should create pet with specific name")
void shouldCreatePetWithSpecificName() {
// Arrange
String expectedName = "Fluffy";
Pet petToCreate = TestDataFactory.petWithName(expectedName);
// Act
Pet createdPet = createAndTrack(petToCreate);
// Assert
assertThat(createdPet.getName()).isEqualTo(expectedName);
}
@Test
@DisplayName("Should create pet and verify it exists")
void shouldCreatePetAndVerifyExists() {
// Arrange
Pet petToCreate = TestDataFactory.randomPet();
// Act
Pet createdPet = createAndTrack(petToCreate);
// Assert
assertPetExists(petService, createdPet.getId());
}
}// tests/pet/DeletePetTests.java
@DisplayName("Delete Pet API Tests")
public class DeletePetTests extends BaseTest {
@Test
@DisplayName("Should delete existing pet")
void shouldDeleteExistingPet() {
// Arrange — не используем createAndTrack, удалим вручную
Pet createdPet = petService.createPet(TestDataFactory.randomPet());
// Act
petService.deletePet(createdPet.getId());
// Assert
assertPetNotExists(petService, createdPet.getId());
}
}Что получили:
Посмотрите на тесты. Они читаются как спецификация: "должен создать питомца с валидными данными". Никакого технического шума — только бизнес-логика.
Каждый тест независим — создаёт свои данные и подчищает за собой. Можно запускать параллельно, можно в любом порядке. Упал один — остальные продолжают работать.
API-слой инкапсулирует все HTTP-детали. Service-слой даёт понятные бизнес-методы. TestDataFactory генерирует уникальные данные. Builder pattern делает создание объектов читаемым.
Это код, который не стыдно показать на code review. И который будет жить в проекте годами без накопления технического долга.
Сравнение результатов
Критерий | Итерация 1 | Итерация 2 | Итерация 3 |
|---|---|---|---|
Файлов в проекте | 1 | 8 | 18+ |
Слои абстракции | 0 | 1 | 3 |
Переиспользование кода | Нет | Частичное | Полное |
Независимость тестов | Нет | Нет | Да |
Параллельный запуск | Невозможен | Невозможен | Да |
Читаемость тестов | Низкая | Средняя | Высокая |
Масштабируемость | Нет | Ограничено | Да |
Подходит для | Демо, PoC | Малый проект | Enterprise |
Claude Opus одинаково хорошо сгенерировал код во всех трёх случаях. Синтаксис корректный, библиотеки использованы правильно, код работает. Разница — исключительно в том, что попросили.
Почему обучение всё ещё важно
Этот эксперимент наглядно показывает то, что мы в Ростелекоме интуитивно понимали, но теперь можем доказать: AI-ассистенты не заменяют экспертизу. Они её усиливают. И чем глубже экспертиза — тем сильнее усиление.
Человек, который не знает про трёхуровневую архитектуру, не сможет её попросить. Человек, который не понимает, зачем нужен Builder pattern, не включит его в требования. Человек, который не сталкивался с проблемами зависимых тестов, не подумает о независимости.
AI — это мультипликатор. Ноль, умноженный на любое число, даёт ноль. А глубокая экспертиза, умноженная на возможности современных моделей, даёт результаты, которые раньше требовали команды и недель работы.
Именно поэтому Ростелеком продолжает инвестировать в развитие сотрудников. Мы понимаем, что забота о людях — это не только про комфортные офисы и ДМС. Это про возможность расти профессионально, осваивать новые технологии, становиться экспертами в своей области. Внутренние курсы, менторинг, передача опыта от senior-специалистов к junior — всё это не устарело с появлением AI. Наоборот — стало ещё важнее.
С появлением AI-ассистентов ценность глубокой экспертизы только выросла. Раньше разница между junior и senior проявлялась в скорости написания кода. Теперь, когда скорость уравнялась благодаря AI, разница проявляется в качестве того, что написано. Junior с Claude Opus напишет код уровня итерации 1 — быстро и работающий, но хрупкий. Senior с тем же Claude Opus напишет итерацию 3 — и сделает это за то же время.
Выводы
Для руководителей: Инвестиции в обучение сотрудников окупаются. AI-инструменты усиливают существующие компетенции, но не создают их из ничего. Команда обученных специалистов с AI будет в разы эффективнее команды новичков с тем же AI.
Для специалистов: Изучайте архитектуру, паттерны, лучшие практики. Это не устаревающие знания — это фундамент, который позволит вам использовать любые инструменты на максимум. Сегодня это Claude, завтра будет что-то новое. Но принципы останутся.
Для индустрии: Менторинг и передача опыта не умерли — они стали ещё важнее. AI убрал барьер входа в написание кода, но поднял планку качества. Теперь недостаточно просто писать работающий код — нужно писать правильный код. А этому по-прежнему учат люди.
Новогодний подарок: KILLER TEST API
В завершение статьи — подарок для тех, кто хочет проверить своих AI-ассистентов на прочность или прокачать навыки автоматизации.
Мы создали специальный OpenAPI-спек KILLER TEST API — это "полоса препятствий" для генераторов кода. Обычный PetStore слишком прост: простые CRUD-операции, линейные схемы, никаких подводных камней. Наш спек — другое дело.

Полиморфные типы — когда одно поле может быть чем угодно. Представьте конструктор LEGO, где в одну ячейку можно вставить семь разных типов деталей, и у каждой свои правила сборки. Один из эндпоинтов принимает массив шагов, где каждый шаг может быть одним из семи видов: запрос к внешнему сервису, преобразование данных, условный переход, параллельное выполнение, цикл, ожидание или вызов вложенного процесса. Ассистент должен понять, какие поля обязательны для каждого типа, и не перепутать их между собой. Большинство моделей на этом спотыкаются.
Рекурсивные схемы — когда структура ссылается сама на себя. Условия фильтрации построены как матрёшка: внутри условия "И" может быть другое условие "ИЛИ", внутри которого снова "И", и так до бесконечности. Это стандартный паттерн для сложных запросов, но ассистенты часто ломаются на таких циклических ссылках — либо уходят в бесконечную генерацию, либо выдают невалидные структуры.
Глубокая вложенность — много уровней внутри одного запроса. Один из эндпоинтов принимает пачку операций пяти разных видов: создание, обновление, удаление, перемещение, копирование. У каждой операции свой набор обязательных и необязательных полей. Плюс вложенные настройки с условной логикой. Это проверка на то, как ассистент справляется со сложной иерархией данных.
Гибридный подход — два стиля в одном. Отдельный эндпоинт принимает произвольные запросы с динамической структурой. Это тест на работу со схемами, где заранее неизвестно, какие поля придут. Многие генераторы кода просто игнорируют такие случаи или выдают заглушки.
Параметры отовсюду. Один из эндпоинтов требует одновременно четыре параметра в пути запроса, три в строке запроса и два в заголовках. При этом часть параметров — строго из списка допустимых значений, часть должна соответствовать определённому формату, часть необязательна и имеет значения по умолчанию. Собрать всё это вместе корректно — отдельный вызов.
Граничные случаи — мело��и, которые ломают всё. Поля, которые могут быть пустыми. Ограничения на минимальное и максимальное количество элементов в массиве. Строгие форматы для идентификаторов и дат. Регулярные выражения для валидации. Всё то, что часто игнорируется при генерации, но гарантированно ломает код при запуске.
Как использовать
Для бенчмарка AI-ассистентов. Попросите разных ассистентов сгенерировать тесты для этого API. Сравните результаты: кто обработал полиморфизм, кто справился с рекурсией, кто учёл все параметры.
Для обучения автоматизации. Это готовый набор сложных кейсов: как тестировать полиморфные эндпоинты, как генерировать тестовые данные для вложенных структур, как работать с GraphQL в контексте REST-тестов.
Для проверки своего фреймворка. Если ваша архитектура тестов справляется с KILLER TEST API — она справится с чем угодно.
Файл
Спек доступен в формате OpenAPI 3.0.3 — стандарт, который понимают все современные инструменты. Загружайте в Swagger UI для визуализации, импортируйте в Postman для экспериментов, скармливайте своим AI-агентам для генерации кода.
{
"openapi": "3.0.3",
"info": {
"title": "KILLER TEST API - Maximum Complexity",
"version": "1.0.0",
"description": "Stress test for LLM code generation"
}
// ... полный файл прикреплён к статье
}Спек доступен в формате OpenAPI 3.0.3: [KILLER_TEST_API.json](https://gist.github.com/Mdyuzhev/d84e82c8be0053c7e2dc652f1bc9deb6)
Попробуйте попросить вашего AI-ассистента написать тесты для KILLER TEST API. А потом сравните с тем, что получится после того, как вы дадите ему детальное ТЗ уровня итерации 3. Разница вас впечатлит.
С наступающим Новым годом! Пусть ваши тесты всегда проходят с первого раза, а AI-ассистенты понимают вас с полуслова.
Автор: Директор департамента обеспечения качества цифровых продуктов, Ростелеком Дюжев Михаил
