왜 현대 테스트 전략이 탄탄한 웹 애플리케이션 구축에 필수적인가
Source: Dev.to
소개
테스트는 이제 코드를 배포하기 전의 마지막 관문이 아니라, 자신감을 가지고 빠르게 개발하면서 모든 것을 깨뜨리지 않도록 해주는 기반입니다. 현대적인 테스트는 웹 애플리케이션의 각 부분을 개발하면서 자동으로 그리고 지속적으로 검증할 수 있는 도구를 제공합니다.
컴포넌트 테스트
React 애플리케이션에서 가장 작은 단위는 컴포넌트—버튼, 폼 입력, 카드 등입니다. 컴포넌트 테스트는 이러한 조각들을 격리된 상태에서 검증하며, 내부 구현 세부 사항보다 사용자가 어떻게 상호작용하는지에 초점을 맞춥니다.
예시: Button 컴포넌트
// Button.jsx
export const Button = ({ onClick, children, disabled = false }) => {
return (
{children}
);
};
테스트
// Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button Component', () => {
it('calls the onClick handler when clicked', () => {
const mockClickHandler = jest.fn();
render(Save);
const buttonElement = screen.getByText('Save');
fireEvent.click(buttonElement);
expect(mockClickHandler).toHaveBeenCalledTimes(1);
});
it('is disabled and has proper aria attribute when disabled prop is true', () => {
render( {}} disabled={true}>Submit);
const buttonElement = screen.getByText('Submit');
expect(buttonElement).toBeDisabled();
expect(buttonElement).toHaveAttribute('aria-disabled', 'true');
});
});
이 테스트들은 컴포넌트의 내부 로직이 바뀌더라도 외부 동작이 동일하게 유지되는 한 신뢰성을 유지합니다.
Storybook을 활용한 시각적 테스트
컴포넌트는 전달받는 데이터에 따라 여러 시각적 상태를 가질 수 있습니다. Storybook은 각 상태를 표시하고 테스트할 수 있는 살아있는 스타일 가이드를 제공합니다.
예시: ProfileCard 스토리
// ProfileCard.stories.jsx
import ProfileCard from './ProfileCard';
export default {
title: 'Components/ProfileCard',
component: ProfileCard,
};
export const Default = {
args: {
userName: 'Jane Doe',
userAvatar: 'https://example.com/avatar.jpg',
isLoading: false,
},
};
export const Loading = {
args: {
isLoading: true,
},
};
export const LongName = {
args: {
userName: 'Dr. Alexander Theophilius Montgomery III',
userAvatar: 'https://example.com/avatar2.jpg',
isLoading: false,
},
};
Storybook을 자동화된 시각적 스냅샷 테스트와 결합하면, 각 스토리의 스크린샷을 기준선과 비교합니다. CSS 변경으로 인해 오버플로우나 레이아웃 깨짐이 발생하면 테스트가 실패하고 픽셀 차이를 강조해 줍니다—순수 단위 테스트만으로는 잡히지 않을 수 있는 버그를 발견하게 됩니다.
엔드‑투‑엔드 테스트
컴포넌트를 페이지와 사용자 흐름으로 엮어가는 단계가 바로 엔드‑투‑엔드(E2E) 테스트가 빛을 발하는 영역입니다. 이 테스트들은 실제 사용자가 실제 브라우저와 상호작용하는 모습을 시뮬레이션하여, 네비게이션, 폼 처리, 상태 유지 등을 포괄합니다. 실행 속도가 느리고 깨지기 쉬워서 보통 중요한 여정에만 사용합니다.
예시: Playwright를 이용한 결제 흐름
// tests/checkout.spec.js
const { test, expect } = require('@playwright/test');
test('Complete user checkout flow', async ({ page }) => {
// 1. Go to the product page
await page.goto('https://myshop.example.com/products');
// 2. Click on the first product
await page.locator('[data-testid="product-card"]').first().click();
// 3. Add it to the cart from the product detail page
await page.locator('[data-testid="add-to-cart-button"]').click();
// 4. Verify the cart counter updates
const cartCount = page.locator('[data-testid="cart-count"]');
await expect(cartCount).toHaveText('1');
// 5. Go to the cart page
await page.locator('[data-testid="cart-icon"]').click();
// 6. Click the checkout button
await page.locator('button:has-text("Proceed to Checkout")').click();
// 7. Fill out the shipping form
await page.fill('[data-testid="shipping-name"]', 'Alex Johnson');
await page.fill('[data-testid="shipping-address"]', '123 Main St');
// ... fill other fields
// 8. Submit the form and confirm we reach the order summary
await page.locator('[data-testid="submit-shipping"]').click();
await expect(page.locator('[data-testid="order-summary"]')).toBeVisible();
await expect(page).toHaveURL(/order-confirmation/);
});
이 테스트가 통과하면 핵심 구매 흐름이 정상적으로 동작한다는 높은 확신을 가질 수 있습니다. CI 파이프라인에서 이러한 테스트를 실행하면 어떤 병합도 중요한 사용자 여정을 깨뜨릴 수 없게 됩니다.
성능 테스트
성능은 현대 웹 앱에서 협상할 수 없는 요소입니다. 느리게 로드되는 기능적인 애플리케이션은 그 목적을 상실합니다. 주요 페이지에 대해 Lighthouse를 프로그래밍 방식으로 실행하는 등 성능 검사를 초기에 통합하면 팀이 예산을 강제할 수 있습니다(예: “홈페이지는 Lighthouse 성능 점수 90점 이상이어야 함”). 성능을 일급 테스트 항목으로 다루면 회귀가 사용자에게 도달하기 전에 잡히게 됩니다.