CodeGym /Corsi /ChatGPT Apps /Test di GiftGenius — unit, contract, E2E e smoke in CI

Test di GiftGenius — unit, contract, E2E e smoke in CI

ChatGPT Apps
Livello 17 , Lezione 2
Disponibile

1. Che cosa testiamo effettivamente in una ChatGPT App (e cosa non testiamo)

In un’applicazione web classica tutto è chiaro: UI → backend → DB. Scriviamo test unitari per le funzioni, di integrazione per le API, E2E per “l’utente ha completato il flusso”.

In una ChatGPT App lo scenario è un po’ più complesso:

Utente ↔ ChatGPT UI ↔ Widget (Apps SDK, React)
                  ↘
                    Server MCP (tools/resources)
                      ↘
                        ACP / backend / API esterne

Il modello all’interno di ChatGPT decide quando chiamare il tuo suggest_gifts, con quali argomenti, come renderizzare il structuredContent da MCP e quando mostrare il tuo widget.

Dal punto di vista del testing è comodo dividere il mondo in due livelli:

  • Infrastructure tests — ciò di cui ci occupiamo in questa lezione. Verifichiamo che:
    • il codice del widget non si rompa al click dell’utente;
    • gli MCP‑tools accettino e restituiscano dati nel formato promesso dagli schemi;
    • gli endpoint ACP e i webhook siano vivi e non vadano in errore su un JSON tipico.
  • AI behavior evals — ciò che vedremo nel modulo 20. Lì analizziamo che cosa risponde il modello: se spiega in modo adeguato, se sceglie correttamente il regalo per senso, se non allucina.

Formula grossolana di oggi:

«Testiamo tutto attorno all’LLM, ma non l’LLM in sé».

Ecco perché nel syllabus per questo argomento è sottolineato a parte: “Non testiamo la risposta GPT alla lettera, testiamo l’infrastruttura attorno e i contratti dei dati”.

Per non affogare, usiamo una semplice “piramide” di test per GiftGenius.

graph TD
  A["Test unitari
utils, business logic dei tools"] --> B[Test di contratto
Zod/JSON Schema, webhooks] B --> C[Test E2E / UI
widget + MCP senza ChatGPT] C --> D["Smoke in CI
"è vivo almeno?""] 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

Ora passeremo ogni livello e, allo stesso tempo, estenderemo il nostro GiftGenius didattico con i test. Alla fine raccoglieremo un check‑list degli errori tipici che si incontrano più spesso nel testing di una ChatGPT App.

2. Test unitari: scomponiamo GiftGenius in piccoli pezzi

Cosa considerare “unit” in una ChatGPT App

Un test unitario nel nostro stack è la verifica di una piccola porzione isolata di logica. Senza rete reale, senza database e, possibilmente, senza invocare direttamente il framework MCP.

In GiftGenius questo può essere:

  • una funzione che calcola la “rilevanza del regalo”;
  • un filtro che elimina prodotti senza prezzo o con valuta non adatta;
  • un convertitore di valute;
  • un mapper da un oggetto “grezzo” di prodotto a GiftCardProps per la UI.

Idealmente conviene spezzare anche la logica degli MCP‑tools: il gestore della route MCP è un sottile wrapper che chiama una funzione pura con la business logic. Nei test unitari testiamo proprio la funzione pura.

Esempio: funzione di ranking dei regali

Immaginiamo di avere un’utilità scoreGift che, in base alla fascia di prezzo e alla popolarità, assegna un “punteggio”:

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

Scriviamo un test unitario con Jest (Vitest sarà quasi uguale):

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

test('scoreGift riduce il punteggio per i regali costosi', () => {
  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);
});

Qui è visibile il classico “Arrange–Act–Assert” (preparare i dati, chiamare la funzione, verificare il risultato) — esattamente l’approccio strutturato da usare anche in test più complessi.

Estrarre la business logic dal gestore MCP

Al momento probabilmente avete qualcosa del genere:

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

Il test unitario per scoreGift l’abbiamo già scritto, ma vogliamo testare anche l’intera funzione: “una funzione che prende la lista dei regali e restituisce la top‑10 ordinata”. Estraiamola in un modulo separato:

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

E il test:

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

test('rankGifts restituisce al massimo 10 regali in ordine decrescente di 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);
});

Questi test unitari sono veloci, economici e forniscono feedback immediato — per questo sono consigliati come “ampia base della piramide di test” per i servizi MCP.

Test unitari per MCP‑tools: mockare le API esterne

Errore comune — provare a “testare unitariamente” l’handler dell’MCP‑tool insieme a richieste HTTP reali al catalogo, Stripe ecc. Il risultato è un test lento e fragile.

La soluzione migliore: lasciare nell’handler solo il “collante” (wiring) e spostare tutta la logica complessa in funzioni che testiamo già separatamente. Se proprio volete testare l’handler, sostituite le dipendenze con mock. È esattamente ciò che raccomandano nelle review dettagliate sul testing MCP: fare mock delle API esterne nei tool‑handlers.

3. Contract test: Zod/JSON Schema come “accordo” con il modello e l’ACP

Che cos’è un contract test nel nostro contesto

Con la logica a livello unit ci siamo: piccole funzioni pure sono sotto controllo. Il livello successivo della piramide è assicurarci che i servizi continuino a capirsi sui contratti JSON. Questo è il dominio dei contract test.

Il test di contratto verifica che due parti che si scambiano dati continuino a capirsi. Il focus non è sugli algoritmi interni, ma sulla forma e sul significato del JSON: campi, tipi, obbligatorietà.

In una ChatGPT App abbiamo molti di questi contratti:

  • ChatGPT ↔ MCP: inputSchema e outputSchema degli MCP‑tools.
  • MCP ↔ commerce‑API (ACP): formato delle richieste create_checkout_session, struttura delle risposte.
  • ACP ↔ il nostro backend via webhook: order.created, payment_failed ecc.

Se cambiate lo schema ma dimenticate di aggiornare il codice (o viceversa — cambiate il codice e lasciate lo schema com’era), nasce una rottura silenziosa. Il modello continua a inviare il vecchio JSON, mentre il vostro codice aspetta un campo nuovo — e cade a runtime. Proprio queste situazioni i contract test devono intercettare prima della produzione.

Zod come fonte di verità unica

Nell’ecosistema JavaScript/TypeScript Zod è perfetto per questo scopo, e lo avete già usato con MCP: l’SDK sa convertire gli schemi Zod in JSON Schema per dichiarare gli strumenti.

Per esempio, descriviamo lo schema di un regalo e del risultato di una raccomandazione:

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

I tipi per il codice li otteniamo con z.infer:

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

Questo è già una sorta di contract test a compile‑time: se da qualche parte nel codice provate ad assegnare currency: 123, TypeScript protesterà ricordando che deve essere una string.

Contract test a runtime per gli schemi

Ancora più efficaci sono i test a runtime, che fanno passare esempi di dati reali (o quasi) attraverso gli schemi.

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

test('GiftSchema accetta un prodotto valido', () => {
  const sample = {
    id: '123',
    title: 'Tazza con gatto',
    price: 19.99,
    currency: 'USD',
    url: 'https://example.com/gift/123',
  };

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

test('SuggestGiftsResultSchema rifiuta una lista vuota di regali', () => {
  const badResult = { gifts: [] };

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

Perché è importante:

  • se nei prompt/documentazione mostrate esempi di JSON per il modello, potete metterli direttamente in questi test e garantire che “l’esempio non mente”;
  • se cambiate uno schema (per esempio rendete il campo url obbligatorio), i test subito evidenziano tutti i vecchi esempi e fixture non più validi.

Le raccomandazioni ufficiali di Apps SDK sottolineano: lo structured content deve corrispondere all’outputSchema dichiarato, altrimenti il modello potrebbe non capirlo. I test sugli schemi sono la prima linea di difesa contro le discrepanze.

Contratti dei webhook e dell’ACP

Lo stesso principio si applica ai webhook e agli endpoint ACP. Supponiamo di avere 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 convalida il sample del webhook', () => {
  const sample = {
    id: 'ord_1',
    userId: 'user_42',
    totalAmount: 59.99,
    currency: 'USD',
    status: 'created',
  };

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

Poi, nel gestore del webhook, la prima cosa che fate è OrderCreatedSchema.parse(body) — e siete già sicuri di lavorare con un oggetto valido.

OpenAI, nel suo checklist di regressione per le App, raccomanda anche di mantenere gli schemi aggiornati man mano che l’app evolve — i contract test garantiscono di non dimenticarvene.

4. Test del widget e “quasi E2E”: come fare senza chatgpt.com

I test unitari mantengono in ordine la logica, i contract test — la forma dei dati tra i servizi. Ma la piramide non finisce qui: dobbiamo ancora controllare che l’intero percorso dell’utente attraverso widget e MCP funzioni davvero come un tutto unico. Per una ChatGPT App questo sarà un formato particolare, “quasi E2E”.

Perché non si può semplicemente “lanciare Playwright” su ChatGPT

L’impulso intuitivo: “Apriamo https://chatgpt.com, avviamo il widget, percorriamo tutto lo scenario ‘trovare un regalo → checkout’ con Playwright, e avremo un vero E2E”.

Purtroppo, no.

Problemi:

  • l’esecuzione automatizzata su chatgpt.com viola i ToS;
  • ci sono protezioni (Cloudflare, 2FA ecc.) che non amano i bot da CI;
  • il comportamento del modello è variabile: oggi chiama il vostro suggest_gifts, domani decide di rispondere solo con testo.

Quindi, per le ChatGPT App, l’E2E è interpretato più ampiamente: testiamo l’intero percorso all’interno della nostra applicazione — widget + MCP + ACP — ma senza la UI reale di ChatGPT e senza il modello reale.

Nelle guide dettagliate si suggerisce proprio questa strategia: testare separatamente il server MCP con un client headless e il widget in un “host di test” con window.openai mockato.

Testare il widget come componente React

La base — React Testing Library. Ci serve:

  1. Montare il componente GiftGeniusWidget.
  2. Passargli un window.openai finto con i metodi necessari (callTool, openExternal ecc.).
  3. Simulare l’utente: cliccare i pulsanti, inserire testo.
  4. Verificare che callTool venga chiamato con gli argomenti corretti e che la UI mostri il risultato atteso.

Supponiamo di avere un widget semplificato:

// 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}>Scegli un regalo</button>
      {loading && <p>Un secondo, sto trovando idee...</p>}
    </div>
  );
}

Test:

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

test('il pulsante chiama suggest_gifts tramite window.openai.callTool', async () => {
  const callToolMock = vi.fn().mockResolvedValue({});
  (window as any).openai = { callTool: callToolMock };

  render(<GiftGeniusWidget />);

  const button = screen.getByText('Scegli un regalo');
  await fireEvent.click(button);

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

Qui controlliamo completamente l’ambiente:

  • nessun ChatGPT reale;
  • nessuna rete;
  • test pulito e veloce che verifica la catena “UI → window.openai”.

Anche la documentazione di Apps SDK consiglia questo: mockare window.openai quando si testa il widget, così da non dipendere dall’ambiente reale.

E2E‑light con Playwright: Next.js + MCP

Livello successivo — avviamo in locale l’app Next.js (come in Dev Mode), ma vi accediamo non tramite ChatGPT, bensì direttamente dal browser di test.

Ha senso verificare lo scenario seguente:

  1. Aprire la pagina /widget (o / — dipende dal progetto).
  2. Simulare i passi minimi: scegliere il tipo di regalo, cliccare “Mostra idee”.
  3. Controllare che il widget mostri le card dei regali.
  4. (Opzionale) cliccare una card, premere “Vai al pagamento” e verificare che il mock dell’ACP restituisca successo.

Mini‑esempio di test Playwright:

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

test('l’utente può scegliere un regalo e vedere i risultati', async ({ page }) => {
  await page.goto('http://localhost:3000/widget');

  await page.click('text=Regalo di compleanno');
  await page.click('text=Scegli');

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

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

Su un progetto reale si aggiungeranno:

  • l’avvio di npm run dev o di un test‑server dedicato in beforeAll di Playwright;
  • mock per MCP/ACP, per non toccare i servizi di produzione.

Ma anche uno scenario così semplice intercetta i tipici “punti di rottura” tra widget e MCP: URL errato, errori CORS, structuredContent non corretti ecc.

5. Smoke test in CI: verifichiamo che “si avvia davvero”

Rimane il livello superiore, il più leggero — gli smoke test. Non verificano l’intero scenario come un E2E‑light, ma rispondono semplicemente: l’app è viva e si avvia prima del deploy?

Smoke vs E2E completo

Avete già sentito parlare dello smoke test “manuale” nel secondo modulo: allora avviavamo il primo “Hello GiftGenius”, verificavamo che il widget si renderizzasse, che ChatGPT lo vedesse e che il pulsante aprisse il link. L’obiettivo era: assicurarci che Dev Mode + tunnel + configurazione Apps SDK fossero corretti.

Ora il compito è simile, solo che è automatico e in CI:

  • non proviamo a modellare tutti gli scenari utente;
  • non comunichiamo con il ChatGPT reale;
  • verifichiamo soltanto che:
    • l’app Next.js si avvia;
    • il server MCP risponde almeno a tools/list / tools/call di base;
    • l’endpoint ACP risponde 200 a un JSON di test.

Questo è particolarmente importante prima del deploy in produzione o prima dell’invio di una nuova versione allo Store: è più semplice intercettare in CI “tutto è caduto e non si avvia” che scoprirlo dagli utenti.

Esempio di smoke test per gli MCP‑tools

Supponiamo di avere un modulo di supporto che avvia il server MCP nel test o usa un client MCP dell’SDK. Concettualmente il test è così:

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

test('MCP risponde a tools.list e tools.call(suggest_gifts)', async () => {
  const client = await createTestMcpClient(); // avvia il server o si connette ad esso

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

Nelle analisi più approfondite del testing MCP si consiglia proprio questo approccio: usare il client MCP nei test per verificare l’intero ciclo JSON‑RPC — list → call → response.

L’implementazione di createTestMcpClient può stare nelle utility: avvia il server nello stesso processo oppure si connette a un’istanza già in esecuzione.

Smoke test per ACP/checkout

Analogamente possiamo scrivere un test semplicissimo per lo strato commerce, senza simulare un pagamento reale:

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

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

Qui non importa cosa faccia esattamente test-intent — può limitarsi a verificare l’accesso al DB e restituire {"status":"ok"}. L’importante è che il CI intercetti:

  • una variabile d’ambiente mancante;
  • una route rotta;
  • un parsing JSON errato.

Pipeline CI minimale

Parleremo di CI/CD nei moduli sul deploy, ma una pipeline di base può essere così (esempio con 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

I comandi npm run test:e2e e npm run test:smoke possono già avviare il dev server, attendere che sia pronto e lanciare Playwright / script Node.

6. Mini‑mappa dei test per GiftGenius

Per non perdersi, raccogliamo tutto in una tabella — cosa testiamo a ogni livello e quali domande copre.

Livello Esempi per GiftGenius Strumenti A quale domanda risponde
Unit scoreGift, rankGifts, validatori di budget Jest / Vitest La logica calcola correttamente?
Contract (schemi) Schemi Zod Gift, SuggestGiftsResult, OrderCreated Zod, AJV Parliamo ancora lo stesso linguaggio JSON con GPT/ACP?
UI/Component Comportamento del widget al click, chiamata a window.openai.callTool React Testing Library La UI invoca le azioni corrette?
E2E‑light L’utente percorre il flusso di scelta del regalo e vede le card Playwright/Cypress Tutte le parti di GiftGenius si compongono in un flusso funzionante?
Smoke in CI MCP risponde a tools.list/call, ACP test-intent 200 Script Node, client MCP L’applicazione è viva e connessa?

Questa combinazione è il “set minimo di test” per una ChatGPT App, come descritto nel piano del modulo: senza un team QA Enterprise, ma con la garanzia di base che la produzione non cada a ogni soffio.

7. Errori tipici nel testing di una ChatGPT App

Errore n. 1: cercare di testare in modo deterministico le risposte del modello.
A volte gli sviluppatori provano a scrivere test tipo “mi aspetto che GPT risponda con la stringa Ecco 5 idee regalo”. Tali test sono fragili per definizione: il modello non è tenuto a ripetere la formulazione parola per parola, e il modello stesso può aggiornarsi. In questo modulo non tocchiamo affatto il contenuto delle risposte — verifichiamo solo che gli strumenti vengano chiamati, che gli schemi siano validi e che il flusso non cada. La valutazione della qualità dei testi è una disciplina a parte (M20, LLM‑evals).

Errore n. 2: assenza di contract test per gli schemi MCP.
È molto allettante descrivere uno schema Zod una volta e dimenticarsene. Poi aggiungete nel risultato dello strumento il campo discount, aggiornate il codice ma non lo schema. Il modello continua a mandare il vecchio formato e il vostro codice aspetta un campo nuovo — in produzione iniziano crash strani. I contract test con Zod/JSON Schema prevengono proprio queste rotture “silenziose”, quindi trascurarli è un errore comune e doloroso.

Errore n. 3: tentare di eseguire E2E su chatgpt.com dal CI.
Qualcuno ci prova comunque: lancia Playwright contro il ChatGPT reale, fa login, clicca sulla UI — e ottiene ban da Cloudflare, test instabili e potenziale violazione dei termini d’uso. La strada giusta è testare il vostro host Next.js + MCP in isolamento, mockando window.openai e le API esterne, come raccomandano le guide su Apps SDK e MCP.

Errore n. 4: scrivere solo E2E e dimenticare il livello unit.
Capita di vedere progetti con un unico, “enorme” test E2E che clicca mezza applicazione e zero test unitari. Questo approccio dà una falsa sensazione di sicurezza: il test è o verde o rosso, ma localizzare la causa è quasi impossibile e ogni esecuzione dura minuti. È molto più efficace avere decine di test unitari veloci per funzioni pure e un paio di scenari E2E‑light accurati sui percorsi critici.

Errore n. 5: usare API esterne reali nei test ordinari.
Stripe, cataloghi esterni, CRM — tutto ottimo per test di integrazione in un ambiente controllato, ma non per un normale npm test. Se i vostri test dipendono dalla rete, dai rate limit altrui e dal server di produzione di qualcun altro, cadranno per motivi non legati al vostro codice. L’approccio migliore è mockare le API esterne (con nock, msw ecc.) e avere a parte alcune verifiche “live” in un ambiente dedicato.

Errore n. 6: dimenticare gli smoke test prima del deploy.
Avete assemblato una feature, aggiornato lo schema MCP, corretto la UI, premuto “Deploy” — e Next.js non parte perché qualcuno ha rotto il next.config o ha rimosso il .env. Senza smoke test automatizzati il CI lascia passare queste ovvietà in produzione. Un semplice suite di smoke che verifica “il server è su”, “MCP risponde a una chiamata di base” e “l’endpoint di test ACP dà 200” fa risparmiare ore di debug in battaglia e un mare di nervi.

Errore n. 7: complicare eccessivamente il perimetro di test nelle prime fasi.
Talvolta, ispirati dalle best practice delle grandi aziende, si vuole subito impostare una decina di ambienti, test di contratto complessi con generazione dati, scenari di carico, ecc. La squadra finisce per passare settimane sull’infrastruttura e smette di rilasciare feature. Per iniziare con una ChatGPT App basta quel “Sanity Suite” di cui abbiamo parlato: unit + contract + un paio di E2E‑light + smoke in CI. Poi si può evolvere man mano che crescono traffico e requisiti.

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