
왜 테스트는 통과했는데 서비스는 터질까?
테스트를 모두 완료했는데 실제 사용자 환경에서 에러가 나나면, 그건 사이드 이펙트(Side Effect) 때문일지도 모릅니다.
“겉보기에는 한 일만 한 것 같지만, 몰래 다른 일까지 벌이는 코드”
즉, 의도한 결과 외에 부수적인 영향(side effect) 을 미치는 행위로
함수가 입력값만 사용해야 하는데 외부에 영향을 주면, 그게 바로 사이드 이펙트입니다.
function add(a, b) {
return a + b;
}
입력(a,b)이 같으면 항상 같은 결과를 주고, 외부에는 아무 영향도 안줍니다.
→ 순수 함수(pure function)
function addAndLog(a, b) {
console.log(a + b); // 콘솔에 로그 출력
return a + b;
}
단순히 더하기 결과를 반환하는게 아니라 외부 환경(콘솔, 로그 시스템)에 영향을 줍니다.
→ 사이드 이펙트 존재
// 1️⃣ 브라우저 전체 상태 변경
localStorage.setItem('user', ...)
// 2️⃣ 외부 서버 호출
fetch('/api/user')
// 3️⃣ DOM 직접 조작
document.querySelector('#id').click()
// 4️⃣ 다른 프레임과 통신
window.postMessage(...)
이런건 다른 시스템, 환경, 시간에 따라 결과가 달라질 수 있어서 테스트할 때 "예측 불가능성"이 생깁니다.
End-to-End, 실제 사용자의 여정을 그대로 재현하는 테스트
1️⃣ 로그인 버튼 클릭 → 2️⃣ 서버 요청 → 3️⃣ 홈 이동 → 4️⃣ 화면 확인 ✅
E2E 테스트는 바로 이런 사이드 이펙트가 실제 서비스에서 문제를 일으키는지를 직접 검증해줍니다.
API 요청이 실제로 잘 가는지?
로그가 중복으로 전송되는지?
...
E2E 테스트 도구로는
이라고 생각하면 좋을 것 같습니다.
Playwright는 브라우저를 자동으로 조작해 테스트를 수행하는 도구입니다.
즉, 사람이 실제로 클릭하고 입력하는 과정을 코드로 재현합니다.
쉽게 비유하자면, Playwright는 "자동 클릭 로봇" 🤖
우리가 직접 브라우저에서 하는 행동을 대신 반복해주는 로봇같은 존재
👩 로그인 버튼을 클릭
🤖 page.click('#login')
👩 입력창에 이름 입력
🤖 page.fill('#name', '소영')
👩 로그인 성공 확인
🤖 expect(page.getByText('환영합니다')).toBeVisible()
1️⃣ 브라우저를 자동으로 실행
2️⃣ 사용자 행동 재현
3️⃣ 네트워크와 API 감시
4️⃣ 멀티 브라우저·멀티 디바이스 테스트
5️⃣ 시각적·DOM 기반 검증
npm install playwright
import { test, expect } from '@playwright/test';
test('로그인 성공 시 홈 화면으로 이동', async ({ page }) => {
await page.goto('https://dankkumi.com/login');
await page.fill('#id', 'jiyul1004');
await page.fill('#pw', '1234');
await page.click('button[type=submit]');
await expect(page.getByText('오늘의 학습')).toBeVisible();
});
“AI가 테스트를 직접 만든다면?”
즉, "코드가 바뀌면 → AI가 테스트를 만들고 → 테스트를 돌리고 → 결과를 요약해주는" 완전 자동 테스트
실제 예시를 보겠습니다.
// 기존
export function login(id, pw) {
return api.post('/login', { id, pw });
}
// 변경됨
export function login(id, pw) {
const response = api.post('/login', { id, pw });
localStorage.setItem('lastLogin', new Date().toISOString()); // 새 기능
return response;
}
AI(Cursor + MCP) 분석 결과
🔍 감지된 사이드 이펙트:
localStorage에 lastLogin이 추가 저장됨
테스트 필요: “로그인 시 로컬스토리지에 lastLogin이 생성되는지 확인”
# Login Behavior Test (Auto-generated)
## Scenario: User login stores timestamp in localStorage
- Navigate to `/login`
- Fill ID input with `testuser`
- Fill PW input with `1234`
- Click on `로그인` button
- Wait for page navigation to `/home`
- Assert: `localStorage.getItem('lastLogin')` is not null
- Assert: Timestamp format matches ISO string
MCP가 Playwright 포맷으로 바로 실행 가능한 시나리오를 Markdown으로 구성
import { test, expect } from '@playwright/test';
test('로그인 시 lastLogin이 저장되어야 한다', async ({ page }) => {
await page.goto('/login');
await page.fill('#id', 'testuser');
await page.fill('#pw', '1234');
await page.click('button#login');
await expect(page).toHaveURL('/home');
const lastLogin = await page.evaluate(() => localStorage.getItem('lastLogin'));
expect(lastLogin).not.toBeNull();
expect(lastLogin).toMatch(/^\\d{4}-\\d{2}-\\d{2}T/);
});
AI가 Markdown 기반 시나리오를 Playwright 코드로 변환하고 즉시 실행
✅ Test Passed: login page navigation OK
⚠️ Warning: localStorage timestamp value delayed by 400ms
🧠 Suggestion: Add `await page.waitForTimeout(500)` before checking storage
AI MCP가 Playwright 로그를 읽고
테스트 실패 원인을 “비동기 타이밍 이슈”로 분석 후
구체적 수정 제안까지 리포트 💬
🧪 AI 테스트 결과 요약
• 총 12개 테스트 중 11개 성공, 1개 경고 ⚠️
• 원인: localStorage 비동기 반영 지연 (400ms)
• 추천 수정: await page.waitForTimeout(500) 추가
• 관련 커밋: #3f92d1a auth.ts
⏱ 테스트 실행 시간: 38초

사람이 테스트 코드를 쓰는 시대에서, AI가 테스트를 생성/실행/리포트하는 시대로 진화 중!