CodeGym /Kurse /ChatGPT Apps /Testen von GiftGenius – Unit-, Contract-, E2E- und Smoke-...

Testen von GiftGenius – Unit-, Contract-, E2E- und Smoke-Tests im CI

ChatGPT Apps
Level 17 , Lektion 2
Verfügbar

1. Was testen wir in der ChatGPT App überhaupt (und was testen wir nicht)

In der klassischen Web‑Anwendung ist alles klar: UI → Backend → Datenbank. Wir schreiben Unit‑Tests für Funktionen, Integrations‑Tests für APIs, E2E‑Tests für „User durchläuft den Flow“.

In der ChatGPT App ist das Bild etwas komplexer:

Nutzer ↔ ChatGPT UI ↔ Widget (Apps SDK, React)
                  ↘
                    MCP‑Server (tools/resources)
                      ↘
                        ACP / Backend / externe APIs

Das Modell innerhalb von ChatGPT entscheidet, wann es Ihr suggest_gifts aufruft, mit welchen Argumenten, wie structuredContent aus dem MCP gerendert wird und wann Ihr Widget angezeigt wird.

Aus Test‑Sicht ist es praktisch, die Welt in zwei Schichten zu teilen:

  • Infrastrukturtests – damit beschäftigen wir uns in dieser Vorlesung. Wir prüfen, dass:
    • der Widget‑Code bei einem Nutzer‑Klick nicht bricht;
    • MCP‑Tools Daten in dem durch die Schemata zugesicherten Format annehmen und zurückgeben;
    • ACP‑Endpoints und Webhooks erreichbar sind und bei einem typischen JSON nicht abstürzen.
  • AI behavior evals – das kommt in Modul 20. Dort betrachten wir, was das Modell tatsächlich antwortet: ob es sinnvoll erklärt, das Geschenk semantisch richtig auswählt und nicht halluziniert.

Grobe Faustformel für heute:

„Wir testen alles rund um die LLM, aber nicht die LLM selbst“.

Genau deshalb wird im Kursplan zu diesem Thema besonders betont: „Wir testen die GPT‑Antwort nicht wörtlich, sondern die Infrastruktur rundherum und die Datenverträge“.

Damit wir nicht untergehen, verwenden wir eine einfache Test‑„Pyramide“ für GiftGenius.

graph TD
  A["Unit-Tests
Utils, Business-Logik der Tools"] --> B[Contract-Tests
Zod/JSON Schema, Webhooks] B --> C[E2E / UI-Tests
Widget + MCP ohne ChatGPT] C --> D["Smoke in CI
\"lebt das überhaupt?\""] 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

Jetzt gehen wir jede Ebene durch und erweitern dabei unser Lernprojekt GiftGenius um Tests. Am Ende erstellen wir zudem eine Checkliste typischer Fehler, die beim Testen von ChatGPT Apps am häufigsten auftreten.

2. Unit‑Tests: Wir zerlegen GiftGenius in kleine Teile

Was gilt als Unit in der ChatGPT App

Ein Unit‑Test in unserem Stack ist die Prüfung einer kleinen, isolierten Logik. Ohne echtes Netzwerk, ohne Datenbank und möglichst ohne den MCP‑Framework selbst aufzurufen.

In GiftGenius kann das sein:

  • eine Funktion zur Berechnung der „Relevanz eines Geschenks“;
  • ein Filter, der Produkte ohne Preis oder mit unpassender Währung entfernt;
  • ein Währungskonverter;
  • ein Mapper vom „rohen“ Produktobjekt zu GiftCardProps für das UI.

Idealerweise zerlegt man auch die Logik der MCP‑Tools: Der MCP‑Routen‑Handler ist nur eine dünne Hülle, die eine Pure Function mit der Business‑Logik aufruft. In Unit‑Tests testen wir genau diese Pure Function.

Beispiel: Funktion zum Ranken von Geschenken

Nehmen wir an, wir haben eine Utility scoreGift, die auf Basis des Preisrahmens und der Popularität einen „Score“ vergibt:

// 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);
}

Wir schreiben einen Unit‑Test mit Jest (Vitest ist nahezu identisch):

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

test('scoreGift bewertet teure Geschenke niedriger', () => {
  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);
});

Hier sieht man das grundlegende „Arrange–Act–Assert“ (Daten vorbereitet, Funktion aufgerufen, Ergebnis geprüft) – genau der strukturierte Ansatz, der auch für komplexere Tests empfohlen wird.

Business‑Logik aus dem MCP‑Handler auslagern

Wahrscheinlich habt ihr derzeit etwas wie:

// app/mcp/route.ts — stark vereinfacht
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) };
  },
});

Den Unit‑Test für scoreGift haben wir schon, aber wir möchten auch die Gesamtfunktion testen: „eine Funktion, die eine Geschenkliste nimmt und die Top‑10 sortiert zurückgibt“. Wir extrahieren sie in ein eigenes Modul:

// 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);
}

Und der Test:

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

test('rankGifts gibt höchstens 10 Geschenke in absteigender Score-Reihenfolge zurück', () => {
  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);
});

Solche Unit‑Tests sind schnell, günstig und liefern unmittelbares Feedback – genau darum empfiehlt man sie als „breite Basis der Testpyramide“ für MCP‑Services.

Unit‑Tests für MCP‑Tools: externe APIs mocken

Ein häufiger Fehler ist der Versuch, den MCP‑Tool‑Handler „unit‑zu‑testen“ und dabei echte HTTP‑Requests an Kataloge, Stripe usw. zu senden. Am Ende wird der Test langsam und fragil.

Die bessere Variante: Im Handler nur „Verkabelung“ (Wiring) lassen und die komplexe Logik in Funktionen auslagern, die wir bereits separat testen. Wenn man dennoch den Handler selbst testen will, Abhängigkeiten durch Mocks ersetzen. Genau das empfehlen ausführliche Übersichten zum Testen von MCP: externe APIs in Tool‑Handlern mocken.

3. Contract‑Tests: Zod/JSON Schema als „Vertrag“ mit dem Modell und dem ACP

Was ist ein Contract‑Test in unserem Kontext

Die Unit‑Logik ist geklärt: kleine Pure Functions haben wir im Griff. Die nächste Ebene der Pyramide ist sicherzustellen, dass Services sich weiterhin über JSON‑Verträge verstehen. Das sind Contract‑Tests.

Contract‑Testing prüft, dass zwei Seiten, die Daten austauschen, einander weiterhin verstehen. Der Fokus liegt nicht auf internen Algorithmen, sondern auf Form und Bedeutung des JSON: Felder, Typen, Verpflichtungen.

In der ChatGPT App gibt es mehrere solcher Verträge:

  • ChatGPT ↔ MCP: inputSchema und outputSchema der MCP‑Tools.
  • MCP ↔ Commerce‑API (ACP): Anfrageformate von create_checkout_session, Struktur der Antworten.
  • ACP ↔ unser Backend via Webhooks: order.created, payment_failed usw.

Wenn Sie ein Schema ändern, aber den Code nicht aktualisieren (oder umgekehrt – Code ändern, Schema alt lassen), entsteht ein leiser Bruch. Das Modell sendet weiter altes JSON, Ihr Code erwartet bereits ein neues Feld – und stürzt zur Laufzeit ab. Genau solche Situationen sollen Contract‑Tests vor der Produktion abfangen.

Zod als Single Source of Truth

In der JavaScript/TypeScript‑Welt eignet sich Zod hervorragend, das Sie bereits mit MCP eingesetzt haben: Das SDK kann Zod‑Schemata in JSON Schema konvertieren, um Tools zu deklarieren.

Beispielsweise beschreiben wir ein Geschenk‑Schema und das Ergebnis einer Empfehlung:

// 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),
});

Typen für den Code erhalten wir über z.infer:

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

Das ist bereits eine Art Compile‑Time‑Contract‑Test: Wenn Sie irgendwo im Code currency: 123 zuweisen, warnt TypeScript und erinnert daran, dass das eine string sein muss.

Runtime‑Contract‑Tests für Schemata

Noch robuster sind Runtime‑Tests, die reale (oder realitätsnahe) Beispieldaten durch die Schemata laufen lassen.

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

test('GiftSchema akzeptiert ein gültiges Produkt', () => {
  const sample = {
    id: '123',
    title: 'Tasse mit Katze',
    price: 19.99,
    currency: 'USD',
    url: 'https://example.com/gift/123',
  };

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

test('SuggestGiftsResultSchema lehnt eine leere Geschenkliste ab', () => {
  const badResult = { gifts: [] };

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

Warum das wichtig ist:

  • Wenn Sie in Prompts/Dokumentation JSON‑Beispiele für das Modell zeigen, können Sie sie direkt in solche Tests legen und garantieren, dass „das Beispiel nicht lügt“;
  • Wenn Sie ein Schema ändern (z. B. url auf „pflichtig“ setzen), markieren die Tests sofort alle alten Beispiele und Fixtures, die nicht mehr valide sind.

Offizielle Empfehlungen zum Apps SDK betonen ausdrücklich: Structured Content muss dem deklarierten outputSchema entsprechen, sonst versteht das Modell ihn eventuell nicht. Schema‑Tests sind die erste Verteidigungslinie gegen Abweichungen.

Verträge für Webhooks und ACP

Dasselbe Prinzip gilt für Webhooks und ACP‑Endpoints. Nehmen wir 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'),
});

Test:

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

test('OrderCreatedSchema validiert das Webhook-Sample', () => {
  const sample = {
    id: 'ord_1',
    userId: 'user_42',
    totalAmount: 59.99,
    currency: 'USD',
    status: 'created',
  };

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

Im Webhook‑Handler rufen Sie als Erstes OrderCreatedSchema.parse(body) auf – und wissen damit, dass Sie anschließend mit einem validen Objekt arbeiten.

OpenAI empfiehlt in seinem Regressions‑Checklistenformat für Apps ebenfalls, die Schemata aktuell zu halten – Contract‑Tests sorgen genau dafür, dass man es nicht vergisst.

4. Widget‑Tests und „Fast‑E2E“: ganz ohne chatgpt.com

Unit‑Tests halten die Logik in Ordnung, Contract‑Tests die Datenform zwischen Services. Aber die Pyramide endet hier nicht: Wir müssen noch prüfen, dass der gesamte Weg durch Widget und MCP als Ganzes funktioniert. Für die ChatGPT App ist das ein besonderer, „fast E2E“ Ansatz.

Warum nicht einfach „Playwright auf ChatGPT loslassen“

Die intuitive Idee: „Wir öffnen https://chatgpt.com, starten das Widget, laufen den gesamten Ablauf ‚Geschenk aussuchen → Bestellung auslösen‘ mit Playwright nach, und das ist echtes E2E.“

Leider nein.

Probleme:

  • Automatisiertes Testen gegen chatgpt.com verstößt gegen die ToS;
  • es gibt Schutzmechanismen (Cloudflare, 2FA usw.), die Bots aus dem CI gar nicht mögen;
  • Modellverhalten ist variabel: Heute ruft es Ihr suggest_gifts auf, morgen begnügt es sich mit einer Textantwort.

Darum verstehen wir E2E‑Tests bei der ChatGPT App breiter: Wir testen den vollständigen Weg innerhalb der eigenen Anwendung – Widget + MCP + ACP – aber ohne echtes ChatGPT UI und ohne das reale Modell.

Ausführliche Guides empfehlen genau diese Strategie: den MCP‑Server separat mit einem Headless‑Client testen und das Widget in einem „Test‑Host“ mit gemocktem window.openai.

Widget‑Tests als React‑Komponente

Die Basisvariante ist die React Testing Library. Wir brauchen:

  1. Den GiftGeniusWidget‑Komponentenbaum zu rendern.
  2. Ein Fake‑window.openai mit den benötigten Methoden (callTool, openExternal usw.) zu unterschieben.
  3. Den Nutzer zu simulieren: Buttons klicken, Text eingeben.
  4. Prüfen, dass callTool mit den richtigen Argumenten aufgerufen wird und das UI das erwartete Ergebnis zeigt.

Nehmen wir ein vereinfachtes Widget:

// 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}>Geschenk finden</button>
      {loading && <p>Einen Moment, ich sammle Ideen...</p>}
    </div>
  );
}

Test:

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

test('Button ruft suggest_gifts über window.openai.callTool auf', async () => {
  const callToolMock = vi.fn().mockResolvedValue({});
  (window as any).openai = { callTool: callToolMock };

  render(<GiftGeniusWidget />);

  const button = screen.getByText('Geschenk finden');
  await fireEvent.click(button);

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

Hier kontrollieren wir die Umgebung vollständig:

  • kein echtes ChatGPT;
  • kein Netzwerk;
  • ein sauberer, schneller Test, der die Verbindung „UI → window.openai“ prüft.

Auch in den Apps‑SDK‑Dokumenten wird empfohlen: window.openai beim Testen des Widgets mocken, um nicht von der echten Umgebung abhängig zu sein.

E2E‑Light mit Playwright: Next.js + MCP

Die nächste Stufe: Wir starten die Next.js‑App lokal (wie im Dev Mode), greifen aber nicht über ChatGPT darauf zu, sondern direkt aus dem Test‑Browser.

Ein sinnvoller Ablauf zum Prüfen:

  1. Seite /widget öffnen (oder / – je nachdem, wie Ihr Projekt aufgebaut ist).
  2. Die minimalen Schritte simulieren: Geschenktyp wählen, auf „Vorschlagen“ klicken.
  3. Prüfen, dass das Widget Geschenk‑Karten anzeigt.
  4. (Optional) Auf eine Karte klicken, „Zur Kasse“ anstoßen und sicherstellen, dass der ACP‑Mock Erfolg zurückliefert.

Mini‑Beispiel eines Playwright‑Tests:

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

test('Benutzer kann ein Geschenk auswählen und Ergebnisse sehen', async ({ page }) => {
  await page.goto('http://localhost:3000/widget');

  await page.click('text=Geschenk zum Geburtstag');
  await page.click('text=Vorschlagen');

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

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

Im echten Projekt kommt hinzu:

  • Starten von npm run dev oder eines separaten Test‑Servers im beforeAll von Playwright;
  • Mocks für MCP/ACP, damit produktive Services nicht berührt werden.

Selbst ein so einfacher Ablauf fängt die typischen „Brüche“ zwischen Widget und MCP ab: falsche URL, CORS‑Fehler, inkorrektes structuredContent usw.

5. Smoke‑Tests im CI: prüfen, dass „es überhaupt startet“

Es bleibt die obere, leichteste Ebene unserer Pyramide – Smoke‑Tests. Sie prüfen nicht den kompletten Ablauf wie E2E‑Light, sondern beantworten lediglich: Ist die Anwendung überhaupt am Leben und startet sie vor dem Deploy?

Smoke vs. vollständiges E2E

Vom „manuellen“ Smoke‑Test haben Sie schon im zweiten Modul gehört: Wir starteten das allererste „Hello GiftGenius“, prüften, dass das Widget rendert, ChatGPT es sieht und der Button einen Link öffnet. Ziel war: Sicherstellen, dass Dev Mode + Tunnel + Apps SDK‑Konfig korrekt sind.

Jetzt ist die Aufgabe ähnlich, nur automatisiert und im CI:

  • Wir versuchen nicht, alle Nutzer‑Szenarien nachzubilden;
  • wir sprechen nicht mit echtem ChatGPT;
  • wir prüfen lediglich, dass:
    • die Next.js‑App startet;
    • der MCP‑Server zumindest auf tools/list / tools/call antwortet;
    • der ACP‑Endpoint lebt und auf Test‑JSON mit 200 antwortet.

Das ist besonders wichtig vor Deployments in die Produktion oder vor dem Einreichen einer neuen Version in den Store: „Alles ist kaputt und startet nicht“ fängt man lieber im CI ab, als es von Nutzern zu erfahren.

Beispiel‑Smoke‑Test für MCP‑Tools

Angenommen, wir haben ein Hilfsmodul, das im Test den MCP‑Server startet oder den MCP‑Client aus dem SDK nutzt. Konzeptionell sieht der Test so aus:

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

test('MCP antwortet auf tools.list und tools.call(suggest_gifts)', async () => {
  const client = await createTestMcpClient(); // startet den Server oder verbindet sich zu ihm

  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);
});

In tieferen Darstellungen zum MCP‑Testen wird genau dieser Ansatz empfohlen: in Tests den MCP‑Client verwenden, um den vollständigen JSON‑RPC‑Zyklus zu prüfen – list → call → Antwort.

Die Implementierung von createTestMcpClient kann in Utils versteckt werden: Sie startet entweder den Server im selben Prozess oder verbindet sich zu einem bereits laufenden Exemplar.

Smoke‑Test für ACP/Checkout

Analog lässt sich ein einfacher Test für die Commerce‑Schicht schreiben, ohne echte Zahlung zu simulieren:

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

test('ACP test-intent liefert 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);
});

Hier ist egal, was test-intent genau macht – er kann einfach den DB‑Zugriff prüfen und {"status":"ok"} zurückgeben. Wichtig ist, dass das CI erkennt:

  • vergessene Env‑Keys;
  • kaputte Routen;
  • fehlerhaftes JSON‑Parsing.

Minimaler CI‑Pipeline

Details zu CI/CD folgen in den Modulen zum Deploy, aber eine Basis‑Pipeline könnte so aussehen (am Beispiel 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

Die Skripte npm run test:e2e und npm run test:smoke können intern den Dev‑Server starten, auf seine Bereitschaft warten und Playwright/Node‑Skripte ausführen.

6. Kompakte Testübersicht für GiftGenius

Damit wir den Überblick behalten, fassen wir alles in einer Tabelle zusammen – was wir auf jeder Ebene testen und welche Fragen das abdeckt.

Ebene Beispiele für GiftGenius Werkzeuge Welche Frage wird beantwortet
Unit scoreGift, rankGifts, Budget‑Validatoren Jest / Vitest Stimmt die Logik?
Contract (Schemas) Zod‑Schemata Gift, SuggestGiftsResult, OrderCreated Zod, AJV Sprechen wir immer noch dieselbe JSON‑Sprache mit GPT/ACP?
UI/Component Widget‑Verhalten beim Klicken, Aufruf window.openai.callTool React Testing Library Löst das UI die richtigen Aktionen aus?
E2E‑Light Benutzer durchläuft den Geschenk‑Auswahl‑Flow und sieht Karten Playwright/Cypress Fügen sich alle Teile von GiftGenius zu einem funktionierenden Flow zusammen?
Smoke im CI MCP antwortet auf tools.list/call, ACP test-intent 200 Node‑Skripte, MCP‑Client Ist die Anwendung überhaupt lebendig und verbunden?

Diese Kombination ist genau der „minimal funktionsfähige Testsatz“ für die ChatGPT App, von dem im Modulplan die Rede ist: ohne Enterprise‑QA‑Team, aber mit der Grundsicherheit, dass die Produktion nicht bei jedem kleinen Fehler ausfällt.

7. Typische Fehler beim Testen der ChatGPT App

Fehler Nr. 1: Versuchen, Modellantworten deterministisch zu testen.
Manchmal schreiben Entwickler Tests wie „Ich erwarte, dass GPT mit der Zeichenfolge Hier sind 5 Geschenkideen antwortet“. Solche Tests sind per Definition fragil: Das Modell muss Formulierungen nicht wortgleich wiederholen, und das Modell selbst kann sich ändern. In diesem Modul kümmern wir uns gar nicht um die Inhalte der Antworten – wir prüfen nur, dass Tools aufgerufen werden, Schemata valide sind und der Flow nicht abstürzt. Die Bewertung der Textqualität ist eine eigene Disziplin (M20, LLM‑Evals).

Fehler Nr. 2: Keine Contract‑Tests für MCP‑Schemata.
Es ist verlockend, ein Zod‑Schema einmal zu beschreiben und zu vergessen. Später fügen Sie im Tool‑Ergebnis das Feld discount hinzu, aktualisieren den Code, aber nicht das Schema. Das Modell sendet weiterhin das alte Format, Ihr Code erwartet das neue Feld – in der Produktion beginnen merkwürdige Abstürze. Contract‑Tests mit Zod/JSON Schema verhindern genau solche „stillen“ Brüche, daher ist ihre Vernachlässigung ein verbreiteter und sehr schmerzhafter Patzer.

Fehler Nr. 3: E2E über chatgpt.com im CI fahren wollen.
Manche versuchen es trotzdem: Playwright gegen das echte ChatGPT, Login, UI‑Klicks – und kassieren Cloudflare‑Sperren, instabile Tests und möglicherweise einen ToS‑Verstoß. Der richtige Weg ist, den eigenen Next.js‑Host + MCP isoliert zu testen und dabei window.openai sowie externe APIs zu mocken, wie es die Apps‑SDK‑ und MCP‑Guides empfehlen.

Fehler Nr. 4: Nur E2E schreiben und den Unit‑Level vergessen.
Man sieht gelegentlich Projekte mit einem „riesigen“ E2E‑Test, der durch ein halbes Produkt klickt, und null Unit‑Tests. Dieser Ansatz vermittelt eine trügerische Sicherheit: Der Test ist grün oder rot, aber die Ursache lässt sich kaum eingrenzen, und jeder Lauf dauert Minuten. Weitaus effektiver sind Dutzende schneller Unit‑Tests für Pure Functions plus ein paar saubere E2E‑Light‑Szenarien für kritische Pfade.

Fehler Nr. 5: Reale externe APIs in gewöhnlichen Tests verwenden.
Stripe, externe Kataloge, CRM – großartig für Integrations‑Tests in kontrollierter Umgebung, aber nicht für das alltägliche npm test. Wenn Ihre Tests vom Netzwerk, fremden Rate‑Limits und externen Produktionsservern abhängen, fallen sie aus Gründen, die nichts mit Ihrem Code zu tun haben. Besser ist es, externe APIs zu mocken (z. B. mit nock, msw) und zusätzlich ein paar „lebende“ Prüfungen in einer speziellen Umgebung zu haben.

Fehler Nr. 6: Smoke‑Tests vor dem Deploy vergessen.
Feature zusammengebaut, MCP‑Schema aktualisiert, UI angepasst, auf „Deploy“ gedrückt – und Next.js startet nicht, weil jemand next.config gebrochen oder .env gelöscht hat. Ohne automatisierte Smoke‑Tests lässt das CI solche offensichtlichen Ausfälle in die Produktion. Ein einfacher Smoke‑Suite, der „Server gestartet“, „MCP beantwortet Basisaufruf“ und „ACP‑Test‑Endpoint liefert 200“ prüft, spart Stunden Live‑Debugging und viele Nerven.

Fehler Nr. 7: Den Test‑Stack zu früh überkomplizieren.
Beeindruckt von den Best Practices großer Unternehmen möchte man sofort ein Dutzend Umgebungen, komplexe Contract‑Tests mit Datengenerierung, Lastszenarien usw. einführen. Am Ende verbringt das Team Wochen mit Infrastruktur und liefert keine Features. Für den Start mit der ChatGPT App genügt das „Sanity Suite“, über das wir sprachen: Unit + Contract + ein paar E2E‑Light + Smoke im CI. Danach kann man entsprechend Traffic und Anforderungen weiterentwickeln.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION