Testowanie w języku naturalnym
Prompt: “Przetestuj integrację serwisu płatności z obsługą timeoutów i scenariuszami rollback”
Wynik: Kompletny zestaw testów z mockami serwisów i wstrzykiwaniem błędów
PRD: Kompleksowy system testowania integracyjnego
Wymagania: Walidacja interakcji serwisów, kontraktów API, transakcji bazodanowych i komunikacji kolejek wiadomości z niezawodnością >95% przy użyciu scenariuszy testowych generowanych przez AI.
Plan: Wykorzystaj Cursor i Claude Code z wyspecjalizowanymi serwerami MCP (baza danych, testowanie API, kolejka wiadomości) do tworzenia kompleksowych zestawów testów integracyjnych, które wychwytują błędy interakcji i zapewniają niezawodność systemu.
Todo:
Testowanie integracyjne wykracza poza proste wywołania API do inteligentnej walidacji złożonych interakcji systemowych. AI pomaga odkrywać ukryte zależności, generować realistyczne scenariusze testowe i utrzymywać testy w miarę ewolucji architektury.
Testowanie w języku naturalnym
Prompt: “Przetestuj integrację serwisu płatności z obsługą timeoutów i scenariuszami rollback”
Wynik: Kompletny zestaw testów z mockami serwisów i wstrzykiwaniem błędów
Inteligencja kontraktów
AI analizuje schematy API i automatycznie generuje testy kontraktów zorientowane na konsumenta
Walidacja przepływu danych
Śledź spójność danych między serwisami, bazami danych i kolejkami wiadomości
Integracja wydajnościowa
Monitoruj czasy odpowiedzi, przepustowość i wykorzystanie zasobów między granicami serwisów
Scenariusz: Testowanie złożonego systemu przetwarzania zamówień e-commerce z wieloma zależnościami serwisów.
# PRD: Testy integracji przetwarzania zamówień e-commerce# Wymagania: Walidacja kompletnego przepływu zamówień z interakcjami serwisów
"Przeanalizuj naszą architekturę mikroserwisów i utwórz testy integracyjne:
Zaangażowane serwisy:- Serwis zamówień (orkiestrator)- Serwis użytkowników (autentyfikacja)- Serwis inwentarza (zarządzanie stanem)- Serwis płatności (rozliczenia)- Serwis wysyłki (realizacja)- Serwis powiadomień (komunikacja)
Potrzebne scenariusze testowe:1. Ścieżka happy path: Kompletne przetwarzanie zamówienia2. Niedobór inwentarza: Obsługa rollback3. Niepowodzenie płatności: Anulowanie zamówienia4. Niedostępność wysyłki: Aktualizacje statusu5. Timeout serwisu: Aktywacja wyłącznika bezpieczeństwa6. Częściowe awarie: Wzorce kompensacji
Wymagania:- Testowanie kontraktów dla wszystkich granic serwisów- Walidacja spójności transakcji- Wydajność pod obciążeniem- Testowanie propagacji błędów- Walidacja monitorowania i alertów"
// AI generuje kompleksowe testy integracji mikroserwisówdescribe('Integracja przetwarzania zamówień', () => { let testEnvironment: TestEnvironment; let services: ServiceMesh;
beforeAll(async () => { // AI konfiguruje kompletne środowisko testowe testEnvironment = await createIntegrationTestEnvironment({ services: ['order', 'user', 'inventory', 'payment', 'shipping', 'notification'], databases: ['orders', 'users', 'inventory'], messageQueues: ['order-events', 'notifications'], externalMocks: ['stripe', 'fedex-api'] });
services = testEnvironment.services; });
describe('Kompletny przepływ zamówienia', () => { it('przetwarza zamówienie ze wszystkimi serwisami pomyślnie', async () => { // Arrange - AI tworzy realistyczne dane testowe const user = await testEnvironment.createTestUser({ email: 'customer@example.com', paymentMethods: [{ type: 'card', last4: '4242' }] });
const products = await testEnvironment.seedInventory([ { id: 'prod-1', stock: 10, price: 29.99 }, { id: 'prod-2', stock: 5, price: 49.99 } ]);
const orderRequest = { userId: user.id, items: [ { productId: 'prod-1', quantity: 2 }, { productId: 'prod-2', quantity: 1 } ], shippingAddress: AddressFactory.build(), paymentMethod: user.paymentMethods[0].id };
// Act - Złóż zamówienie i śledź przez serwisy const traceId = generateTraceId(); const orderResponse = await services.order.createOrder(orderRequest, { headers: { 'X-Trace-Id': traceId } });
// Czekaj na zakończenie przetwarzania asynchronicznego await testEnvironment.waitForOrderCompletion(orderResponse.id, { timeout: 30000, expectedStatus: 'confirmed' });
// Assert - Waliduj przepływ end-to-end const finalOrder = await services.order.getOrder(orderResponse.id); expect(finalOrder.status).toBe('confirmed'); expect(finalOrder.payment.status).toBe('charged'); expect(finalOrder.shipping.status).toBe('pending');
// Sprawdź czy inwentarz został zmniejszony const updatedInventory = await services.inventory.getStock(['prod-1', 'prod-2']); expect(updatedInventory['prod-1']).toBe(8); // 10 - 2 expect(updatedInventory['prod-2']).toBe(4); // 5 - 1
// Sprawdź czy powiadomienie zostało wysłane const notifications = await testEnvironment.getNotifications(user.id); expect(notifications).toContainEqual( expect.objectContaining({ type: 'order_confirmed', orderId: orderResponse.id }) ); });
it('obsługuje niedobór inwentarza z odpowiednim rollback', async () => { // AI generuje testy scenariuszy awarii const user = await testEnvironment.createTestUser();
// Ustaw niewystarczający inwentarz await testEnvironment.seedInventory([ { id: 'prod-low', stock: 1, price: 99.99 } ]);
const orderRequest = { userId: user.id, items: [{ productId: 'prod-low', quantity: 5 }] // Więcej niż dostępne };
const orderResponse = await services.order.createOrder(orderRequest);
// Czekaj na zakończenie przetwarzania await testEnvironment.waitForOrderCompletion(orderResponse.id, { expectedStatus: 'cancelled' });
// Sprawdź czy nastąpił rollback const cancelledOrder = await services.order.getOrder(orderResponse.id); expect(cancelledOrder.status).toBe('cancelled'); expect(cancelledOrder.cancellationReason).toBe('insufficient_inventory');
// Sprawdź czy płatność nie została pobrana expect(cancelledOrder.payment.status).toBe('not_attempted');
// Sprawdź czy inwentarz nie zmienił się const inventory = await services.inventory.getStock('prod-low'); expect(inventory).toBe(1); }); });});
Scenariusz: Zapewnienie kompatybilności API między serwisami, które ewoluują niezależnie.
# PRD: Testowanie kontraktów zorientowanych na konsumenta# Plan: Użyj Pact.js ze scenariuszami kontraktów generowanymi przez AI
"Utwórz testy kontraktów dla naszego ekosystemu serwisów:
Relacje serwisów:- OrderService (konsument) → UserService (dostawca)- OrderService (konsument) → PaymentService (dostawca)- NotificationService (konsument) → UserService (dostawca)- InventoryService (konsument) → ProductService (dostawca)
Dla każdego kontraktu:1. Generuj realistyczne scenariusze request/response2. Uwzględnij warunki błędów i przypadki brzegowe3. Testuj kompatybilność ewolucji schematu4. Utwórz testy weryfikacji dostawcy5. Skonfiguruj przepływ publikowania kontraktów6. Dodaj wykrywanie zmian powodujących błędy
Skup się na:- Walidacji tokenu autentyfikacji- Spójności formatu danych- Standardach odpowiedzi błędów- Oczekiwaniach wydajnościowych"
// AI generuje kompleksowy zestaw testów kontraktówimport { Pact } from '@pact-foundation/pact';import { OrderService } from '../OrderService';
describe('Testy kontraktów OrderService', () => { const provider = new Pact({ consumer: 'OrderService', provider: 'UserService', port: 8080, log: path.resolve(process.cwd(), 'logs', 'pact.log'), dir: path.resolve(process.cwd(), 'pacts'), logLevel: 'INFO' });
beforeAll(() => provider.setup()); afterEach(() => provider.verify()); afterAll(() => provider.finalize());
describe('Interakcje z serwisem użytkowników', () => { it('pobiera użytkownika dla prawidłowego tworzenia zamówienia', async () => { // AI generuje realistyczne interakcje kontraktów await provider.addInteraction({ state: 'użytkownik z id 12345 istnieje i jest aktywny', uponReceiving: 'żądanie szczegółów użytkownika dla utworzenia zamówienia', withRequest: { method: 'GET', path: '/api/v1/users/12345', headers: { 'Authorization': like('Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...'), 'Accept': 'application/json', 'X-Request-ID': like('req-12345') } }, willRespondWith: { status: 200, headers: { 'Content-Type': 'application/json', 'X-Response-Time': like('45ms') }, body: { id: '12345', email: like('user@example.com'), name: like('Jan Kowalski'), status: 'active', paymentMethods: eachLike({ id: like('pm_12345'), type: like('card'), last4: like('4242'), brand: like('visa') }), addresses: eachLike({ id: like('addr_67890'), type: like('shipping'), street: like('ul. Główna 123'), city: like('Warszawa'), country: like('PL'), postalCode: like('00-001') }), preferences: { currency: like('PLN'), language: like('pl'), notifications: { email: like(true), sms: like(false) } } } } });
// Testuj kod konsumenta const orderService = new OrderService({ userServiceUrl: provider.mockService.baseUrl, apiKey: 'test-key' });
const user = await orderService.getUserForOrder('12345');
// AI dodaje kompleksowe asercje expect(user).toMatchObject({ id: '12345', email: expect.stringMatching(/^[^\s@]+@[^\s@]+\.[^\s@]+$/), status: 'active', paymentMethods: expect.arrayContaining([ expect.objectContaining({ type: expect.stringMatching(/^(card|bank|paypal)$/) }) ]) }); });
it('obsługuje scenariusz nieznalezienia użytkownika', async () => { // AI generuje kontrakty scenariuszy błędów await provider.addInteraction({ state: 'użytkownik z id 99999 nie istnieje', uponReceiving: 'żądanie nieistniejącego użytkownika', withRequest: { method: 'GET', path: '/api/v1/users/99999', headers: { 'Authorization': like('Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...'), 'Accept': 'application/json' } }, willRespondWith: { status: 404, headers: { 'Content-Type': 'application/json' }, body: { error: { code: 'USER_NOT_FOUND', message: 'Użytkownik z id 99999 nie został znaleziony', timestamp: like('2024-01-01T12:00:00Z'), requestId: like('req-error-123') } } } });
const orderService = new OrderService({ userServiceUrl: provider.mockService.baseUrl });
await expect(orderService.getUserForOrder('99999')) .rejects.toThrow('Użytkownik z id 99999 nie został znaleziony'); }); });});
Scenariusz: Testowanie złożonych transakcji bazodanowych i spójności danych w wielu tabelach i serwisach.
// Test integracji bazodanowej generowany przez AIdescribe('Transakcja przetwarzania zamówienia', () => { let db: Database; let orderService: OrderService; let inventoryService: InventoryService;
beforeEach(async () => { db = await createTestDatabase(); orderService = new OrderService(db); inventoryService = new InventoryService(db); });
it('powinien utrzymać spójność podczas tworzenia zamówienia', async () => { // AI generuje test z odpowiednią obsługą transakcji const productId = 'prod-123'; const quantity = 5;
// Ustaw początkowy inwentarz await inventoryService.setStock(productId, 10);
// Rozpocznij transakcję const transaction = await db.beginTransaction();
try { // Utwórz zamówienie const order = await orderService.createOrder({ productId, quantity, userId: 'user-123' }, transaction);
// Zaktualizuj inwentarz await inventoryService.decrementStock( productId, quantity, transaction );
await transaction.commit();
// Sprawdź spójność const finalStock = await inventoryService.getStock(productId); expect(finalStock).toBe(5);
const savedOrder = await orderService.getOrder(order.id); expect(savedOrder.status).toBe('confirmed'); } catch (error) { await transaction.rollback(); throw error; } });
// AI dodaje scenariusz rollback it('powinien wykonać rollback przy niewystarczającym inwentarzu', async () => { const productId = 'prod-456'; await inventoryService.setStock(productId, 3);
await expect(async () => { await db.transaction(async (trx) => { await orderService.createOrder({ productId, quantity: 5, userId: 'user-789' }, trx);
await inventoryService.decrementStock(productId, 5, trx); }); }).rejects.toThrow('Niewystarczający inwentarz');
// Sprawdź czy nie wprowadzono zmian const stock = await inventoryService.getStock(productId); expect(stock).toBe(3); });});
// Test migracji generowany przez AIdescribe('Integracja migracji bazodanowej', () => { it('powinien poprawnie migrować dane', async () => { // Konfiguruj stary schemat const oldDb = await createDatabase('v1_schema'); await seedTestData(oldDb);
// Uruchom migrację await runMigration('v1_to_v2');
// Sprawdź integralność danych const newDb = await createDatabase('v2_schema');
// AI generuje kompleksowe sprawdzenia const users = await newDb.query('SELECT * FROM users'); expect(users).toHaveLength(100); expect(users[0]).toHaveProperty('created_at'); // Nowe pole
const orders = await newDb.query('SELECT * FROM orders'); expect(orders.every(o => o.user_id)).toBe(true);
// Sprawdź utrzymane relacje const userOrders = await newDb.query(` SELECT u.id, COUNT(o.id) as order_count FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id `); expect(userOrders).toMatchSnapshot(); });});
Scenariusz: Walidacja komunikacji sterowanej zdarzeniami między mikroserwisami z niezawodnym dostarczaniem wiadomości.
// Test integracji kolejki wiadomości generowany przez AIdescribe('Przetwarzanie zamówień sterowane zdarzeniami', () => { let messageQueue: MessageQueue; let orderService: OrderService; let paymentService: PaymentService; let shippingService: ShippingService;
beforeEach(async () => { messageQueue = await createTestMessageQueue();
// AI konfiguruje połączenia serwisów orderService = new OrderService(messageQueue); paymentService = new PaymentService(messageQueue); shippingService = new ShippingService(messageQueue);
// Uruchom serwisy await Promise.all([ orderService.start(), paymentService.start(), shippingService.start() ]); });
it('powinien przetworzyć zamówienie przez cały pipeline', async () => { // AI tworzy kompleksowy test przepływu zdarzeń const orderId = 'order-123'; const orderCreatedPromise = waitForEvent( messageQueue, 'order.created' ); const paymentProcessedPromise = waitForEvent( messageQueue, 'payment.processed' ); const orderShippedPromise = waitForEvent( messageQueue, 'order.shipped' );
// Wyzwól tworzenie zamówienia await orderService.createOrder({ id: orderId, items: [{ productId: 'prod-1', quantity: 2 }], total: 99.99 });
// Sprawdź łańcuch zdarzeń const orderCreatedEvent = await orderCreatedPromise; expect(orderCreatedEvent.data.orderId).toBe(orderId);
const paymentEvent = await paymentProcessedPromise; expect(paymentEvent.data.orderId).toBe(orderId); expect(paymentEvent.data.status).toBe('success');
const shippingEvent = await orderShippedPromise; expect(shippingEvent.data.orderId).toBe(orderId); expect(shippingEvent.data.trackingNumber).toBeDefined(); });
// AI dodaje test propagacji błędów it('powinien elegancko obsłużyć niepowodzenie płatności', async () => { // Ustaw serwis płatności do niepowodzenia paymentService.mockPaymentGateway.failNext();
const orderFailedPromise = waitForEvent( messageQueue, 'order.failed' );
await orderService.createOrder({ id: 'order-fail-123', total: 999.99 });
const failureEvent = await orderFailedPromise; expect(failureEvent.data.reason).toBe('payment_failed'); expect(failureEvent.data.orderId).toBe('order-fail-123'); });});
Scenariusz: Testowanie złożonych zapytań i mutacji GraphQL z zagnieżdżonymi relacjami danych.
// Test integracji GraphQL generowany przez AIdescribe('Integracja API GraphQL', () => { let app: Application; let authService: AuthService; let dataLoader: DataLoader;
beforeAll(async () => { app = await createTestApp(); authService = app.get(AuthService); dataLoader = app.get(DataLoader); });
it('powinien efektywnie rozwiązywać zagnieżdżone zapytania', async () => { // AI generuje test złożonych zapytań const query = ` query GetUserWithOrders($userId: ID!) { user(id: $userId) { id name email orders(limit: 10) { id total items { product { id name price } quantity } shippingAddress { street city country } } } } `;
const response = await request(app) .post('/graphql') .send({ query, variables: { userId: 'user-123' } }) .set('Authorization', 'Bearer test-token');
expect(response.body.errors).toBeUndefined(); expect(response.body.data.user).toMatchObject({ id: 'user-123', orders: expect.arrayContaining([ expect.objectContaining({ items: expect.arrayContaining([ expect.objectContaining({ product: expect.objectContaining({ id: expect.any(String), name: expect.any(String) }) }) ]) }) ]) });
// Sprawdź brak zapytań N+1 const queryCount = dataLoader.getQueryCount(); expect(queryCount).toBeLessThan(10); // Odpowiednio zbatchowane });});
Scenariusz: Wykorzystywanie prawdziwych wzorców ruchu produkcyjnego do walidacji zachowania integracji.
Testowanie oparte na ruchu
// Użycie Keploy do testowania prawdziwego ruchuimport { Keploy } from '@keploy/sdk';
describe('Odtwarzanie ruchu produkcyjnego', () => { let keploy: Keploy;
beforeAll(async () => { keploy = new Keploy({ mode: 'test', path: './keploy-tests' }); });
it('powinien obsłużyć przechwycone scenariusze produkcyjne', async () => { // Keploy odtwarza przechwycony ruch const results = await keploy.runCapturedTests({ service: 'payment-service', environment: 'staging', coverageThreshold: 0.9 });
expect(results.passed).toBe(results.total); expect(results.coverage).toBeGreaterThan(0.9);
// Sprawdź brak regresji results.tests.forEach(test => { expect(test.responseMatches).toBe(true); expect(test.latency).toBeLessThan(test.baseline * 1.1); }); });});
// Test rozproszonego śledzenia generowany przez AIdescribe('Przepływ żądań między serwisami', () => { let tracer: Tracer; let services: ServiceMesh;
beforeEach(async () => { tracer = createTestTracer(); services = await deployTestServices(tracer); });
it('powinien utrzymać kontekst śledzenia między serwisami', async () => { const traceId = generateTraceId();
// Wykonaj żądanie z kontekstem śledzenia const response = await services.gateway.request({ path: '/api/checkout', method: 'POST', headers: { 'X-Trace-Id': traceId }, body: { cartId: 'cart-123' } });
//Zbierz spany śledzenia const spans = await tracer.getSpans(traceId);
// AI generuje kompleksową walidację śledzenia expect(spans).toContainEqual( expect.objectContaining({ service: 'gateway', operation: 'checkout.start' }) );
expect(spans).toContainEqual( expect.objectContaining({ service: 'cart-service', operation: 'cart.validate' }) );
expect(spans).toContainEqual( expect.objectContaining({ service: 'payment-service', operation: 'payment.process' }) );
// Sprawdź timing i zależności const gatewaySpan = spans.find(s => s.service === 'gateway'); const paymentSpan = spans.find(s => s.service === 'payment-service');
expect(paymentSpan.parentId).toBe(gatewaySpan.spanId); expect(paymentSpan.startTime).toBeGreaterThan(gatewaySpan.startTime); });});
// Test wydajności integracji generowany przez AIdescribe('Wydajność integracji serwisów', () => { it('powinien obsłużyć równoczesne żądania między serwisami', async () => { const loadTest = new LoadTest({ duration: '30s', vus: 100, // Użytkownicy wirtualni thresholds: { 'http_req_duration': ['p(95) < 500'], // 95% poniżej 500ms 'http_req_failed': ['rate < 0.01'], // Wskaźnik błędów poniżej 1% } });
await loadTest.run(async (vu) => { // AI generuje realistyczną podróż użytkownika const userId = `user-${vu.id}`;
// Logowanie const authResponse = await vu.post('/auth/login', { username: userId, password: 'test123' });
const token = authResponse.json('token');
// Przeglądaj produkty const products = await vu.get('/products', { headers: { Authorization: `Bearer ${token}` } });
// Dodaj do koszyka const product = products.json('products[0]'); await vu.post('/cart/add', { productId: product.id, quantity: 1 });
// Checkout const order = await vu.post('/checkout', { paymentMethod: 'test-card' });
vu.check(order.status === 200, 'Checkout pomyślny'); });
const results = await loadTest.getResults(); expect(results.checks.passes).toBeGreaterThan(0.99); expect(results.metrics.http_req_duration.p95).toBeLessThan(500); });});
Niezależność testów
// Każdy test zarządza własnymi danymi testowymibeforeEach(async () => { testContext = await createIsolatedContext(); // Użyj unikalnych ID, osobnych baz danych, itp.});
Mockowanie serwisów
// Konsekwentnie mockuj zewnętrzne zależnościconst mockPaymentGateway = createMock({ responses: loadFromContract('payment-gateway-v2')});
Spójność danych
// Sprawdź spójność danych między serwisamiexpect(orderService.getOrder(id)) .toEqual(inventoryService.getOrderItems(id));
Propagacja błędów
// Testuj obsługę błędów między granicamiawait expectErrorChain() .from(paymentService) .through(orderService) .to(notificationService);
Przechwytywanie i odtwarzanie prawdziwego ruchu
# Zainstaluj i przechwyć ruchkeploy record -c "npm start"
# Uruchom wygenerowane testykeploy test -c "npm start"
Framework testowania kontraktów
// Zdefiniuj oczekiwania konsumentaawait provider.addInteraction({ state: 'użytkownik istnieje', uponReceiving: 'żądanie pobrania użytkownika', withRequest: { method: 'GET', path: '/user/1' }, willRespondWith: { status: 200, body: userData }});
Wirtualizacja serwisów
stubFor(get(urlEqualTo("/api/user/123")) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody(userJson)));
# Pipeline CI zoptymalizowany przez AI do testów integracyjnychname: Zestaw testów integracyjnych
on: pull_request: push: branches: [main]
jobs: integration-tests: runs-on: ubuntu-latest
services: postgres: image: postgres:14 env: POSTGRES_PASSWORD: test options: >- --health-cmd pg_isready --health-interval 10s
redis: image: redis:7 options: >- --health-cmd "redis-cli ping" --health-interval 10s
steps: - uses: actions/checkout@v3
- name: Konfiguruj środowisko testowe run: | docker-compose -f docker-compose.test.yml up -d npm run wait-for-services
- name: Uruchom testy integracyjne run: | npm run test:integration -- --coverage env: DATABASE_URL: postgres://test:test@localhost:5432/test REDIS_URL: redis://localhost:6379
- name: Uruchom testy kontraktów run: | npm run test:contracts npm run pact:publish
- name: Sprawdzenie regresji wydajności run: | npm run test:performance -- --compare-with main