JavaRush /Курси /ChatGPT Apps /Тестування GiftGenius — unit, contract, E2E і smoke у CI

Тестування GiftGenius — unit, contract, E2E і smoke у CI

ChatGPT Apps
Рівень 17 , Лекція 2
Відкрита

1. Що ми взагалі тестуємо в ChatGPT App (і що не тестуємо)

У класичному веб‑застосунку все досить прозоро: UI → backend → база даних. Зазвичай ми пишемо unit‑тести для функцій, інтеграційні — для API, а E2E — для сценарію на кшталт «користувач пройшов увесь шлях».

У ChatGPT App усе трохи складніше:

Пользователь ↔ ChatGPT UI ↔ Виджет (Apps SDK, React)
                  ↘
                    MCP-сервер (tools/resources)
                      ↘
                        ACP / backend / внешние API

Модель усередині ChatGPT вирішує, коли викликати ваш suggest_gifts, з якими аргументами, як відобразити structuredContent із MCP і коли показати ваш віджет.

Для тестування зручно поділити все на два шари:

  • Infrastructure tests — те, чим ми займаємося в цій лекції. Ми перевіряємо, що:
    • код віджета не ламається від дій користувача;
    • MCP‑інструменти приймають і повертають дані у форматі, який обіцяний у схемах;
    • ACP‑ендпоїнти й вебхуки «живі» та не падають на типовому JSON.
  • AI behavior evals — те, що буде в модулі 20. Там ми вже дивимося, що саме відповідає модель: чи адекватно пояснює, чи правильно добирає подарунок за змістом, чи не галюцинує.

Сьогодні формула проста:

«Тестуємо все навколо LLM, але не саму LLM».

Саме тому в плані курсу для цієї теми окремо наголошено: «Ми не тестуємо GPT‑відповідь дослівно. Ми тестуємо інфраструктуру навколо неї і контракти даних».

Щоб не потонути в деталях, використаємо просту «піраміду» тестів для GiftGenius.

graph TD
  A["Unit-тести
utils, бізнес-логіка tools"] --> B[Contract-тести
Zod/JSON Schema, webhooks] B --> C[E2E / UI-тести
віджет + MCP без ChatGPT] C --> D["Smoke у CI
"воно взагалі живе?""] style A fill:#e0f7fa,stroke:#00838f,stroke-width:1px style B fill:#e8f5e9,stroke:#2e7d32,stroke-width:1px style C fill:#fff3e0,stroke:#ef6c00,stroke-width:1px style D fill:#ffebee,stroke:#c62828,stroke-width:1px

Зараз пройдемося кожним рівнем і водночас доповнимо наш навчальний GiftGenius тестами. А наприкінці зберемо перелік типових помилок, які найчастіше трапляються під час тестування ChatGPT App.

2. Unit‑тести: ділимо GiftGenius на маленькі шматочки

Що вважати unitʼом у ChatGPT App

Unit‑тест у нашому стеку — це перевірка невеликої ізольованої частини логіки. Без реальної мережі, без бази даних і, за можливості, без виклику самого MCP‑фреймворку.

У GiftGenius це може бути:

  • функція підрахунку «релевантності подарунка»;
  • фільтр, який прибирає товари без ціни або з непридатною валютою;
  • конвертер валют;
  • перетворювач із «сирого» обʼєкта товару в GiftCardProps для UI.

Якщо говорити відверто, логіку самих MCP‑інструментів теж варто дробити. Обробник маршруту MCP — це тонка обгортка, яка викликає чисту функцію з бізнес‑логікою. А в unit‑тестах ми перевіряємо саме чисту функцію.

Приклад: функція ранжування подарунків

Уявімо, що в нас є утиліта scoreGift, яка на основі цінового діапазону й популярності виставляє «оцінку»:

// src/lib/scoreGift.ts
export type Gift = {
  id: string;
  price: number;
  popularity: number; // 0..1
};

export function scoreGift(gift: Gift, maxPrice: number): number {
  if (gift.price > maxPrice) return 0;
  const priceScore = 1 - gift.price / maxPrice;
  return Math.round((priceScore * 0.6 + gift.popularity * 0.4) * 100);
}

Пишемо unit‑тест на Jest (у Vitest він буде майже таким самим):

// src/lib/scoreGift.test.ts
import { scoreGift } from './scoreGift';

test('scoreGift занижує оцінку для дорогих подарунків', () => {
  const cheap = { id: 'c', price: 50, popularity: 0.5 };
  const expensive = { id: 'e', price: 100, popularity: 0.5 };

  const max = 100;
  const cheapScore = scoreGift(cheap, max);
  const expensiveScore = scoreGift(expensive, max);

  expect(cheapScore).toBeGreaterThan(expensiveScore);
});

Тут добре видно базовий підхід «Arrange – Act – Assert» (підготували дані, викликали функцію, перевірили результат). Це саме той структурний шаблон, який варто застосовувати й у складніших тестах.

Винесення бізнес‑логіки з MCP‑обробника

Зараз у вас, найімовірніше, є щось на кшталт:

// app/mcp/route.ts — сильно спрощено
import { createMcpServer } from '@modelcontextprotocol/sdk';
import { scoreGift } from '@/lib/scoreGift';

server.tool('suggest_gifts', {
  // ...
  handler: async ({ input }) => {
    const gifts = await fetchFromCatalog(input);
    const scored = gifts
      .map(g => ({ ...g, score: scoreGift(g, input.maxPrice) }))
      .sort((a, b) => b.score - a.score);

    return { gifts: scored.slice(0, 10) };
  },
});

Unit‑тест для scoreGift ми вже написали. Але хочеться протестувати й повнішу логіку: «функція, яка бере список подарунків і повертає відсортований топ‑10». Винесімо її в окремий модуль.

// src/lib/rankGifts.ts
import { scoreGift, Gift } from './scoreGift';

export function rankGifts(gifts: Gift[], maxPrice: number) {
  return gifts
    .map(g => ({ ...g, score: scoreGift(g, maxPrice) }))
    .sort((a, b) => b.score - a.score)
    .slice(0, 10);
}

І тест:

// src/lib/rankGifts.test.ts
import { rankGifts } from './rankGifts';

test('rankGifts повертає максимум 10 подарунків за спаданням score', () => {
  const gifts = Array.from({ length: 20 }, (_, i) => ({
    id: `g${i}`,
    price: 10 + i,
    popularity: 0.5,
  }));

  const result = rankGifts(gifts, 100);

  expect(result).toHaveLength(10);
  expect(result[0].score).toBeGreaterThanOrEqual(result[9].score);
});

Такі unit‑тести швидкі й недорогі та дають оперативний зворотний звʼязок. Саме тому їх і радять робити «широкою основою піраміди тестування» для MCP‑сервісів.

Unit‑тести для MCP‑інструментів: мокаємо зовнішні API

Поширена помилка — намагатися «unit‑тестувати» обробник MCP‑інструмента разом із реальними HTTP‑запитами до каталогу, Stripe тощо. У підсумку тест стає повільним і крихким.

Найкращий варіант — залишити в обробнику лише «склейку» (wiring), а всю складну логіку винести у функції, які ми вже тестуємо окремо. Якщо ж дуже хочеться протестувати сам handler, підміняйте залежності моками. Це якраз те, що рекомендують у докладних оглядах із тестування MCP: мокати зовнішні API в tool‑handlers.

3. Contract‑тести: Zod/JSON Schema як «договір» із моделлю та ACP

Що таке contract‑тест у нашому контексті

З unit‑логікою розібралися: маленькі чисті функції в нас під контролем. Наступний шар піраміди — переконатися, що сервіси й далі розуміють одне одного за JSON‑контрактами. Це і є contract‑тести.

Контрактне тестування — це перевірка того, що дві сторони, які обмінюються даними, і досі розуміють одна одну. Фокус тут не на внутрішніх алгоритмах, а на формі й сенсі JSON: поля, типи, обовʼязковість.

У ChatGPT App таких контрактів багато:

  • ChatGPT ↔ MCP: inputSchema і outputSchema MCP‑інструментів.
  • MCP ↔ commerce‑API (ACP): формат запитів create_checkout_session, структура відповідей.
  • ACP ↔ наш бекенд через вебхуки: order.created, payment_failed тощо.

Якщо ви змінюєте схему, але забуваєте оновити код (або навпаки — змінюєте код, а схема лишається старою), виникає «тихий» розрив. Модель продовжує надсилати старий JSON, а ваш код уже очікує нове поле — і падає під час виконання. Саме такі ситуації contract‑тести мають ловити ще до продакшену.

Zod як єдине джерело правди

У JavaScript/TypeScript‑екосистемі для цього чудово підходить Zod, який ви вже використовували з MCP. SDK уміє перетворювати Zod‑схеми на JSON Schema для оголошення інструментів.

Наприклад, опишемо схему подарунка й результату рекомендації:

// src/schemas/gift.ts
import { z } from 'zod';

export const GiftSchema = z.object({
  id: z.string(),
  title: z.string(),
  price: z.number().nonnegative(),
  currency: z.string().length(3),
  url: z.string().url(),
});

export const SuggestGiftsResultSchema = z.object({
  gifts: z.array(GiftSchema).min(1),
});

Типи для коду отримуємо через z.infer:

export type Gift = z.infer<typeof GiftSchema>;
export type SuggestGiftsResult = z.infer<typeof SuggestGiftsResultSchema>;

Це вже своєрідна контрактна перевірка під час компіляції (compile‑time): якщо ви десь у коді спробуєте присвоїти currency: 123, TypeScript «засвітиться» й нагадає, що це має бути string.

Runtime‑contract‑тести для схем

Ще надійніше захищають тести під час виконання (runtime): вони проганяють реальні (або близькі до реальних) приклади даних через схеми.

// src/schemas/gift.test.ts
import { GiftSchema, SuggestGiftsResultSchema } from './gift';

test('GiftSchema приймає валідний товар', () => {
  const sample = {
    id: '123',
    title: 'Горнятко з котом',
    price: 19.99,
    currency: 'USD',
    url: 'https://example.com/gift/123',
  };

  expect(() => GiftSchema.parse(sample)).not.toThrow();
});

test('SuggestGiftsResultSchema відхиляє порожній список подарунків', () => {
  const badResult = { gifts: [] };

  expect(() => SuggestGiftsResultSchema.parse(badResult)).toThrow();
});

Чому це важливо:

  • якщо ви в промптах або документації показуєте приклади JSON для моделі, їх можна покласти прямо в такі тести й гарантувати, що «приклад не бреше»;
  • якщо ви змінюєте схему (наприклад, робите поле url обовʼязковим), тести одразу підсвітять усі старі приклади та фікстури, які вже невалідні.

Офіційні рекомендації щодо Apps SDK прямо підкреслюють: structured content має відповідати оголошеній outputSchema, інакше модель може його не зрозуміти. Тести на схеми — перша лінія оборони від таких розбіжностей.

Контракти webhooks і ACP

Цей самий принцип працює і для вебхуків та ACP‑ендпоїнтів. Нехай у нас є OrderCreated:

// src/schemas/acp.ts
import { z } from 'zod';

export const OrderCreatedSchema = z.object({
  id: z.string(),
  userId: z.string(),
  totalAmount: z.number(),
  currency: z.string().length(3),
  status: z.literal('created'),
});

Тест:

// src/schemas/acp.test.ts
import { OrderCreatedSchema } from './acp';

test('OrderCreatedSchema валідовує зразок вебхука', () => {
  const sample = {
    id: 'ord_1',
    userId: 'user_42',
    totalAmount: 59.99,
    currency: 'USD',
    status: 'created',
  };

  expect(() => OrderCreatedSchema.parse(sample)).not.toThrow();
});

Далі в обробнику вебхука ви насамперед робите OrderCreatedSchema.parse(body) — і вже впевнені, що працюєте з валідним обʼєктом.

OpenAI у своєму регресійному переліку перевірок для Appʼів також радить підтримувати схеми актуальними в міру розвитку застосунку. Contract‑тести якраз гарантують, що ви про це не забудете.

4. Тестування віджета і «майже E2E»: як обійтися без chatgpt.com

Unit‑тести тримають у порядку логіку, contract‑тести — форму даних між сервісами. Але на цьому піраміда не закінчується: нам ще потрібно перевірити, що шлях користувача через віджет і MCP справді працює як єдине ціле. Для ChatGPT App це буде особливий, «майже E2E» формат.

Чому не можна просто «запустити Playwright» на ChatGPT

Перша думка зазвичай така: «Давайте відкриємо https://chatgpt.com, запустимо віджет, пройдемо весь сценарій „підібрати подарунок → оформити замовлення“ Playwrightʼом — і матимемо справжній E2E».

На жаль, ні.

Проблеми:

  • автоматизований прогін по chatgpt.com порушує ToS;
  • є захист (Cloudflare, 2FA тощо), який дуже не любить ботів із CI;
  • поведінка моделі варіативна: сьогодні вона викликала ваш suggest_gifts, а завтра вирішила обмежитися текстовою відповіддю.

Тому для ChatGPT App E2E‑тест трактують ширше. Ми тестуємо повний шлях усередині свого застосунку: віджет + MCP + ACP — але без реального ChatGPT UI і без реальної моделі.

У докладних посібниках зазвичай пропонують таку стратегію: окремо тестувати MCP‑сервер headless‑клієнтом, а віджет — у «тестовому хості» з підміненою window.openai.

Тестування віджета як React‑компонента

Базовий варіант — React Testing Library. Нам потрібно:

  1. Відкрити компонент GiftGeniusWidget.
  2. Підставити йому фейковий window.openai з потрібними методами (callTool, openExternal тощо).
  3. Взаємодіяти з ним як користувач: натискати кнопки, вводити текст.
  4. Перевірити, що callTool викликано з правильними аргументами і що UI показує очікуваний результат.

Припустімо, у нас є спрощений віджет:

// src/app/GiftGeniusWidget.tsx
'use client';
import React from 'react';

export function GiftGeniusWidget() {
  const [loading, setLoading] = React.useState(false);

  async function handleClick() {
    setLoading(true);
    await (window as any).openai.callTool('suggest_gifts', {
      occasion: 'birthday',
    });
    setLoading(false);
  }

  return (
    <div>
      <button onClick={handleClick}>Підібрати подарунок</button>
      {loading && <p>Секунду, підбираю ідеї...</p>}
    </div>
  );
}

Тест:

// src/app/GiftGeniusWidget.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { GiftGeniusWidget } from './GiftGeniusWidget';

test('кнопка викликає suggest_gifts через window.openai.callTool', async () => {
  const callToolMock = vi.fn().mockResolvedValue({});
  (window as any).openai = { callTool: callToolMock };

  render(<GiftGeniusWidget />);

  const button = screen.getByText('Підібрати подарунок');
  await fireEvent.click(button);

  expect(callToolMock).toHaveBeenCalledWith('suggest_gifts', {
    occasion: 'birthday',
  });
});

Тут ми повністю контролюємо оточення:

  • жодного справжнього ChatGPT;
  • жодної мережі;
  • чистий, швидкий тест, який перевіряє звʼязок «UI → window.openai».

У документації щодо Apps SDK саме це і рекомендують: мокати window.openai під час тестування віджета, щоб не залежати від реального середовища.

E2E‑light із Playwright: Next.js + MCP

Наступний рівень — ми запускаємо локально Next.js‑застосунок (як у Dev Mode), але відкриваємо його не через ChatGPT, а напряму з браузера тесту.

Сценарій, який має сенс перевірити:

  1. Відкриваємо сторінку /widget (або / — залежно від того, як у вас влаштований проєкт).
  2. Імітуємо мінімум кроків: обираємо тип подарунка, тиснемо кнопку «Показати ідеї».
  3. Перевіряємо, що віджет показав картки подарунків.
  4. (Опційно) натискаємо на картку, тиснемо «Перейти до оплати» і переконуємося, що ACP‑мок повернув успіх.

Міні‑приклад Playwright‑тесту:

// tests/e2e/gift-flow.spec.ts
import { test, expect } from '@playwright/test';

test('користувач може вибрати подарунок і побачити результати', async ({ page }) => {
  await page.goto('http://localhost:3000/widget');

  await page.click('text=Подарунок на день народження');
  await page.click('text=Підібрати');

  await page.waitForSelector('[data-testid="gift-card"]');

  const cards = await page.locator('[data-testid="gift-card"]').all();
  expect(cards.length).toBeGreaterThan(0);
});

У реальному проєкті до цього додадуться:

  • запуск npm run dev або окремого test‑server у beforeAll Playwrightʼа;
  • моки для MCP/ACP, щоб не чіпати продакшн‑сервіси.

Але навіть такий простий сценарій уже ловить типові «злами» між віджетом і MCP: неправильний URL, CORS‑помилки, некоректні structuredContent тощо.

5. Smoke‑тести в CI: перевіряємо, що «воно взагалі запускається»

Залишився верхній, найпростіший шар нашої піраміди — smoke‑тести. Вони не перевіряють увесь сценарій, як E2E‑light, а просто дають відповідь: застосунок узагалі «живий» і підіймається перед деплоєм?

Smoke vs повний E2E

Про «ручний» smoke‑тест ви чули ще в другому модулі: ми тоді запускали найперший «Hello GiftGenius», перевіряли, що віджет рендериться, ChatGPT його бачить, а кнопка відкриває посилання. Мета була проста: переконатися, що Dev Mode + тунель + конфігурація Apps SDK налаштовані коректно.

Тепер завдання схоже, але автоматизоване й у CI:

  • ми не намагаємося змоделювати всі сценарії користувача;
  • ми не взаємодіємо зі справжнім ChatGPT;
  • ми лише перевіряємо, що:
    • Next.js‑застосунок стартує;
    • MCP‑сервер відповідає хоча б на базовий tools/list / tools/call;
    • ACP‑ендпоїнт «живий» і віддає 200 на тестовий JSON.

Це особливо важливо перед деплоєм у продакшн або перед надсиланням нової версії в Store: простіше зловити «все впало й не стартує» в CI, ніж дізнатися про це від користувачів.

Приклад smoke‑тесту для MCP‑tools

Припустімо, у нас є допоміжний модуль, що підіймає MCP‑сервер у тесті або використовує MCP‑клієнт із SDK. Загалом тест виглядає так:

// tests/smoke/mcp-tools.smoke.test.ts
import { createTestMcpClient } from './testClient';

test('MCP відповідає на tools.list і tools.call(suggest_gifts)', async () => {
  const client = await createTestMcpClient(); // підіймає сервер або конектиться до нього

  const tools = await client.listTools();
  expect(tools.some(t => t.name === 'suggest_gifts')).toBe(true);

  const result = await client.callTool('suggest_gifts', {
    occasion: 'birthday',
    budget: { currency: 'USD', max: 50 },
  });

  expect(result.gifts.length).toBeGreaterThan(0);
});

У докладніших оглядах про тестування MCP часто радять саме такий підхід: використовувати MCP‑клієнт у тестах, щоб перевірити повний цикл JSON‑RPC — list → call → відповідь.

Реалізацію createTestMcpClient можна сховати в утиліти: вона або стартує сервер у тому самому процесі, або підʼєднується до вже запущеного екземпляра.

Smoke‑тест для ACP/checkout

Аналогічно можна написати найпростіший тест для commerce‑шару, не імітуючи реальну оплату:

// tests/smoke/acp.smoke.test.ts
import fetch from 'node-fetch';

test('ACP test-intent повертає 200', async () => {
  const res = await fetch('http://localhost:3000/api/acp/test-intent', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      amount: 10,
      currency: 'USD',
    }),
  });

  expect(res.ok).toBe(true);
});

Тут не так важливо, що саме робить test-intent — він може просто перевіряти доступ до бази даних і повертати {"status":"ok"}. Головне, що CI зловить:

  • забутий env‑ключ;
  • зламаний роут;
  • некоректний парсинг JSON.

Мінімальний pipeline CI

Детально про CI/CD буде в модулях про деплой, але базовий pipeline може виглядати так (на прикладі GitHub Actions):

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npm test           # unit + contract
      - run: npm run test:e2e   # e2e/ui
      - run: npm run test:smoke # smoke mcp/acp

Команди npm run test:e2e і npm run test:smoke усередині вже можуть запускати dev‑сервер, чекати на його готовність і стартувати Playwright / Node‑скрипти.

6. Міні‑карта тестів для GiftGenius

Щоб не загубитися, зберемо все в одну таблицю: що ми тестуємо на кожному рівні й які запитання це закриває.

Рівень Приклади для GiftGenius Інструменти На яке питання відповідає
Unit scoreGift, rankGifts, валідатори бюджету Jest / Vitest Логіка працює правильно?
Contract (schemas) Zod‑схеми Gift, SuggestGiftsResult, OrderCreated Zod, AJV Ми все ще «говоримо» однією JSON‑мовою з GPT/ACP?
UI/Component Поведінка віджета під час натискання, виклик window.openai.callTool React Testing Library UI викликає правильні дії?
E2E‑light Користувач пройшов сценарій вибору подарунка й побачив картки Playwright/Cypress Усі частини GiftGenius складаються в робочий сценарій?
Smoke у CI MCP відповідає на tools.list/call, ACP test-intent 200 Node‑скрипти, MCP client Застосунок узагалі «живий» і правильно звʼязаний?

Ця звʼязка — той самий «мінімально життєздатний набір тестів» для ChatGPT App, про який ідеться в плані модуля: без повноцінного enterprise‑QA‑штату, але з базовою гарантією, що продакшн не ламається від кожної дрібниці.

7. Типові помилки під час тестування ChatGPT App

Помилка № 1: намагатися детерміновано тестувати відповіді моделі.
Іноді розробники намагаються написати тести на кшталт: «очікую, що GPT відповість рядком Ось 5 ідей подарунків». Такі тести крихкі за визначенням: модель не зобовʼязана повторювати формулювання слово в слово, та й сама модель може оновитися. У цьому модулі ми взагалі не чіпаємо зміст відповідей — лише перевіряємо, що інструменти викликаються, схеми валідні, а сценарій не падає. Оцінювання якості текстів — окрема дисципліна (М20, LLM‑evals).

Помилка № 2: відсутність contract‑тестів для MCP‑схем.
Дуже спокусливо описати Zod‑схему один раз і «забути» про неї. Потім ви додаєте в результат інструмента поле discount, оновлюєте код, але не оновлюєте схему. Модель продовжує надсилати старий формат, а ваш код уже очікує нове поле — і в продакшні починаються дивні падіння. Контрактні тести на Zod/JSON Schema якраз запобігають таким «тихим» збоям, тож нехтувати ними — популярний і дуже болючий промах.

Помилка № 3: намагатися ганяти E2E по chatgpt.com із CI.
Дехто все одно пробує: запускає Playwright проти реального ChatGPT, входить в обліковий запис, клацає по UI — і отримує бан від Cloudflare, нестабільні тести та потенційне порушення умов використання. Правильний шлях — тестувати власний Next.js‑хост + MCP в ізоляції, мокаючи window.openai і зовнішні API, як рекомендують гайди щодо Apps SDK і MCP.

Помилка № 4: писати лише E2E й забувати про unit‑рівень.
Іноді трапляється проєкт, де є один «великий» E2E‑тест, що проходить через пів застосунку, і нуль unit‑тестів. Такий підхід дає хибне відчуття захищеності: тест або зелений, або червоний, але локалізувати причину майже неможливо, і кожен запуск займає хвилини. Набагато ефективніше мати десятки швидких unit‑тестів для чистих функцій і кілька акуратних E2E‑light сценаріїв для критичних шляхів.

Помилка № 5: використовувати реальні зовнішні API у звичайних тестах.
Stripe, зовнішні каталоги, CRM — усе це чудово підходить для інтеграційних тестів у контрольованому середовищі, але не для звичайного npm test. Якщо ваші тести залежать від мережі, чужих rate‑limitʼів і чийогось продакшн‑сервера, вони падатимуть з причин, не повʼязаних із вашим кодом. Найкращий підхід — мокати зовнішнє API (через nock, msw тощо) і окремо мати кілька «живих» перевірок у спеціальному оточенні.

Помилка № 6: забувати про smoke‑тести перед розгортанням.
Зібрали функціональність, оновили MCP‑schema, підправили UI, натиснули «Deploy» — а Next.js не стартує, бо хтось зламав next.config або видалив .env. Без автоматизованих smoke‑тестів CI пропускає такі очевидні провали в продакшн. Один простий smoke‑suite, який перевіряє «сервер піднявся», «MCP відповідає на базовий виклик» і «ACP тестовий endpoint дає 200», економить години продакшн‑дебагу й чимало нервів.

Помилка № 7: надмірно ускладнювати тестовий контур на ранньому етапі.
Іноді, надихнувшись найкращими практиками великих компаній, хочеться одразу завести десяток середовищ, складні контрактні тести з генерацією даних, навантажувальні сценарії тощо. У підсумку команда витрачає тижні на інфраструктуру й перестає випускати функціональність. Для старту з ChatGPT App достатньо того «sanity suite», про який ми говорили: unit + contract + кілька E2E‑light + smoke у CI. Далі вже можна еволюціонувати в міру зростання трафіку й вимог.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ