Testowanie i zapewnianie jakości wspomagane przez AI
Twój pakiet testów przechodzi lokalnie, ale sypie się w CI. Pokrycie utknęło na 60% od miesiąca, bo nikomu nie chce się pisać nudnych przypadków. A jeden test integracyjny, który wyłapałby zeszłotygodniową regresję, nigdy nie powstał, bo skonfigurowanie przeglądarki było uciążliwe. Testowanie to dokładnie ten rodzaj masowej, wzorcowej pracy, w której agent Cursora zarabia na siebie — pod warunkiem że pokierujesz go właściwymi promptami i sam będziesz pilnować tego, co naprawdę jest asercjonowane.
Z czym odejdziesz
Dział zatytułowany „Z czym odejdziesz”- Konfigurację Auto-Run w Cursorze, dzięki której agent uruchamia testy i iteruje bez niańczenia
- Gotowy do skopiowania prompt TDD, który zmusza agenta do napisania testu najpierw, zobaczenia jak zawodzi, a dopiero potem implementacji
- Prompty na przypadki, które programiści pomijają: ponawianie async, ścieżki błędów i luki sygnalizowane przez raport pokrycia
- Konfigurację Playwright MCP do testów E2E w przeglądarce, które agent może przeprowadzić od początku do końca
- Przepływ debugowania dla najgorszego rodzaju awarii — tej sporadycznej
- Jasną listę miejsc, w których testy generowane przez AI zawodzą, i jak to wychwycić
Skonfiguruj Auto-Run, aby agent mógł iterować
Dział zatytułowany „Skonfiguruj Auto-Run, aby agent mógł iterować”Pętla TDD działa tylko wtedy, gdy agent może sam uruchamiać twoje testy, czytać błędy i próbować ponownie bez zatrzymywania się po pozwolenie przy każdym npm test. W aktualnym Cursorze znajdziesz to w Cursor Settings -> Agents -> Auto-Run (to funkcja, którą wcześniej nazywano „YOLO Mode”).
Ustaw Auto-Run Mode na Run Everything, jeśli ufasz agentowi w tym repozytorium, albo zostaw na Ask Every Time i dodaj bezpieczne polecenia do Command Allowlist, aby komendy testów i builda działały bez pytań, a polecenia destrukcyjne nadal się zatrzymywały.
Przy tak skonfigurowanym Auto-Run agent może uruchamiać pakiet, naprawiać zawodzące testy aktualizując kod źródłowy i ponawiać aż do zieleni — to rdzeń pętli TDD opisanej niżej.
Test-driven development z agentem
Dział zatytułowany „Test-driven development z agentem”Cykl Red -> Green -> Refactor czysto przekłada się na pętlę agenta: pisze zawodzący test, uruchamia go, by potwierdzić, że zawodzi z właściwego powodu, implementuje aż do zieleni, a potem refaktoryzuje z testami jako siatką bezpieczeństwa. Liczy się dyscyplina zmuszania agenta do napisania testu najpierw — inaczej pisze kod i testy razem, a testy tylko przyklepują to, co właśnie wyprodukował.
Typowy przebieg wygląda tak: agent pisze specyfikację, uruchamia ją, widzi błędy i iteruje.
describe('markdownToHtml', () => { it('converts headings', () => { expect(markdownToHtml('# Title')).toBe('<h1>Title</h1>'); expect(markdownToHtml('## Subtitle')).toBe('<h2>Subtitle</h2>'); });
it('escapes HTML special characters in text', () => { expect(markdownToHtml('5 < 6 & 7 > 2')).toBe('<p>5 < 6 & 7 > 2</p>'); });
it('handles unclosed bold markers gracefully', () => { expect(markdownToHtml('**bold')).toBe('<p>**bold</p>'); });});Przypadki z escapowaniem i niezamkniętymi znacznikami to te, o których człowiek zwykle zapomina — i te, które w produkcji zamieniają się w błędy XSS albo zepsute wyjście. Nazwanie ich wprost w prompcie sprawia, że agent je pokrywa.
Wzorce testów jednostkowych
Dział zatytułowany „Wzorce testów jednostkowych”Agent jest najmocniejszy w testach jednostkowych, bo wzorce są dobrze ugruntowane i może je uruchamiać w ciasnej pętli. Wskaż mu istniejące testy w twoim repozytorium, żeby dopasował się do twoich konwencji, zamiast wymyślać nowe.
Write Vitest + React Testing Library tests for this UserProfilecomponent. Cover: renders name and avatar from props, falls back toinitials when avatar is missing, calls onEdit when the edit button isclicked, and disables the edit button while saving is true. Follow thequery and matcher conventions in src/components/__tests__/Card.test.tsx.describe('UserProfile', () => { it('renders user name and avatar', () => { render(<UserProfile user={{ name: 'Ada', avatar: '/a.png' }} />); expect(screen.getByText('Ada')).toBeInTheDocument(); expect(screen.getByRole('img')).toHaveAttribute('src', '/a.png'); });
it('falls back to initials when avatar is missing', () => { render(<UserProfile user={{ name: 'Ada Lovelace' }} />); expect(screen.getByText('AL')).toBeInTheDocument(); });
it('calls onEdit when the edit button is clicked', async () => { const onEdit = vi.fn(); render(<UserProfile user={{ name: 'Ada' }} onEdit={onEdit} />); await userEvent.click(screen.getByRole('button', { name: /edit/i })); expect(onEdit).toHaveBeenCalledOnce(); });});Write supertest + Vitest tests for the POST /api/users endpoint.Cover: 201 with a body containing id on valid input, 400 with an"email" error message on a malformed email, and 409 on a duplicateemail. Mock the database layer; do not hit a real database.describe('POST /api/users', () => { it('creates a user with valid data', async () => { const res = await request(app) .post('/api/users') .send({ name: 'Test', email: 'test@example.com' }); expect(res.status).toBe(201); expect(res.body).toHaveProperty('id'); });
it('rejects a malformed email', async () => { const res = await request(app) .post('/api/users') .send({ name: 'Test', email: 'not-an-email' }); expect(res.status).toBe(400); expect(res.body.error).toContain('email'); });});Operacje asynchroniczne to miejsce, gdzie zwykle kryje się luka w pokryciu
Dział zatytułowany „Operacje asynchroniczne to miejsce, gdzie zwykle kryje się luka w pokryciu”Zielony test „happy path” dla kodu async ukrywa przypadki, które naprawdę psują się w produkcji: ponowienie, timeout, odrzucony promise. Zmuś agenta, żeby testował je wprost.
describe('fetchUserData', () => { it('retries once on network failure, then succeeds', async () => { fetch .mockRejectedValueOnce(new Error('Network error')) .mockResolvedValueOnce({ ok: true, json: async () => ({ id: 1 }) });
const result = await fetchUserData(1); expect(fetch).toHaveBeenCalledTimes(2); expect(result).toEqual({ id: 1 }); });
it('does not retry on a 4xx response', async () => { fetch.mockResolvedValueOnce({ ok: false, status: 404 }); await expect(fetchUserData(1)).rejects.toThrow(); expect(fetch).toHaveBeenCalledOnce(); });});Testy E2E z Playwright MCP
Dział zatytułowany „Testy E2E z Playwright MCP”Do testów end-to-end sterowanych przeglądarką daj agentowi serwer Playwright MCP. Pozwala on agentowi sterować prawdziwą przeglądarką — nawigować, wypełniać formularze, klikać i asercjonować — dzięki czemu może zarówno napisać test, jak i uruchomić go na twojej lokalnej aplikacji, by potwierdzić, że naprawdę działa.
test('successful login redirects to dashboard', async ({ page }) => { await page.goto('http://localhost:3000/login'); await page.getByLabel('Email').fill('user@example.com'); await page.getByLabel('Password').fill('correct-password'); await page.getByRole('button', { name: 'Sign in' }).click(); await expect(page).toHaveURL(/\/dashboard/);});
test('shows error for invalid credentials', async ({ page }) => { await page.goto('http://localhost:3000/login'); await page.getByLabel('Email').fill('user@example.com'); await page.getByLabel('Password').fill('wrong-password'); await page.getByRole('button', { name: 'Sign in' }).click(); await expect(page.getByRole('alert')).toContainText('Invalid credentials');});Lokatory dostępnościowe (getByRole, getByLabel) przetrwają refaktoryzacje CSS, które zepsułyby kruche selektory #id/.class — dlatego warto upierać się przy nich w prompcie.
Debugowanie najgorszej awarii: tej sporadycznej
Dział zatytułowany „Debugowanie najgorszej awarii: tej sporadycznej”Test, który zawodzi raz na dziesięć przebiegów, to tryb awarii pochłaniający najwięcej czasu, i dokładnie tu opłaca się zdolność agenta do wielokrotnego uruchamiania testu pod rząd.
W praktyce agent wyciąga na wierzch zwykłych winowajców: poprzedni test, który nie posprzątał połączeń z bazą danych, afterEach, którego nigdy nie dodano, albo asercję ścigającą się z odbitym (debounced) aktualizowaniem. Naprawą prawie zawsze jest izolacja — porządny hook teardown albo zaczekanie na ustabilizowanie się stanu — a nie dłuższy timeout.
Domykanie luk w pokryciu
Dział zatytułowany „Domykanie luk w pokryciu”Procent pokrycia sam w sobie to metryka próżności — 100% pokrycia linii ze słabymi asercjami i tak wypuszcza błędy. Użyj raportu, by znaleźć, które ścieżki nie są przetestowane, a potem poproś o sensowne testy dla tych ryzykownych.
File | % Stmts | % Branch | % Funcs | % Lines |------------------|---------|----------|---------|---------|auth/login.js | 75.00 | 66.67 | 100.00 | 75.00 |auth/reset.js | 45.00 | 33.33 | 66.67 | 45.00 |Raport taki jak ten mówi agentowi, gdzie celować: auth/reset.js ma najcieńsze pokrycie gałęzi, więc to przypadki brzegowe resetowania hasła (wygasły token, już użyty token, nieznany e-mail) warto napisać.
Gdy to się psuje
Dział zatytułowany „Gdy to się psuje”Agent edytuje asercje zamiast naprawiać kod. Gdy test zawodzi, agent czasem „naprawia” go, osłabiając asercję (toBe zmienia się w toBeDefined). Zawsze dodawaj „fix the implementation, not the test” do promptów TDD i przeglądaj diff — jeśli test stał się łatwiejszy zamiast kodu poprawnym, odrzuć go i poproś ponownie.
Testy przechodzą, ale funkcja jest zepsuta (fałszywe negatywy). Przemockowane testy asercjonują względem mocka, nie rzeczywistego zachowania. Jeśli test nigdy nie ćwiczy integracji, którą rzekomo pokrywa, daje fałszywe poczucie pewności. Prompt: „Review these tests — which ones would still pass if the implementation were deleted? Strengthen them to assert real behavior.” Zachowaj przynajmniej jeden test integracyjny lub E2E, który uderza w prawdziwe szwy.
Auto-Run robi coś destrukcyjnego. „Run Everything” jest potężne i niewybredne. Jeśli agent uruchomi polecenie, które usuwa dane albo wypycha do zdalnego repozytorium, przełącz się na Ask Every Time z Command Allowlist (zobacz politykę powyżej), aby tylko bezpieczne polecenia działały automatycznie, a wszystko inne zatrzymywało się po zatwierdzenie.
Playwright MCP nie może dosięgnąć twojej aplikacji. Testy przeglądarkowe agenta zawodzą natychmiast z błędami połączenia, gdy serwer deweloperski nie działa albo jest na innym porcie. Najpierw uruchom aplikację (npm run dev), potwierdź, że adres URL w prompcie się zgadza, i powiedz agentowi, żeby zaczekał na gotowość serwera, zanim zacznie nawigować.
Chwiejne testy „naprawione” dłuższymi timeoutami. Jeśli naprawa chwiejnego testu przez agenta to tylko większy timeout, zamaskował wyścig, a nie go rozwiązał. Poproś ponownie o źródłową przyczynę (wyciekający stan, niezaczekany promise) i nalegaj na deterministyczną poprawkę potwierdzoną 25 kolejnymi przejściami pod rząd.