테스트를 완료하고 발견된 이슈를 모두 해결했더라도 결함이 없다는 것을 완전히 증명할 수는 없습니다. 이슈가 발견되지 않았다고 해서 결함이 없다고 단정할 수도 없습니다. 테스트는 프로그램에서 결함이 없음을 보장하기보다는 결함이 존재함을 밝히는 활동입니다.
매우 단순한 소프트웨어가 아닌 이상 내부 조건, 입력값, 타이밍에 대한 모든 조합을 확인하는 것은 불가능합니다. 따라서 테스트 대상을 분석한 후 가장 중요한 부분에 중점을 두어 테스트 리소스를 투입해야 합니다.
요구 사항 분석과 설계 단계부터 테스트를 진행하면 문서상의 결함을 발견할 수 있으며, 이러한 결함은 코딩 이후에 발견된 결함보다 쉽게 해결할 수 있습니다. 또한 조기에 테스트 설계를 완료하면 코딩이 끝나자마자 테스트를 실행할 수 있어 테스트 기간을 단축할 수 있습니다.
대다수의 결함은 특정 모듈에 집중되며, 이 모듈에서 장애가 발생할 가능성이 큽니다.
동일한 테스트 케이스를 반복해서 수행하면 더 이상 새로운 결함을 찾기 어려워집니다. 이를 극복하려면 새로운 테스트 기법이나 다른 시각에서 테스트 케이스를 정기적으로 변경해야 합니다.
소프트웨어의 종류와 목표에 따라 적합한 테스트 방식이 적용되어야 합니다. 예산, 일정, 리스크, 환경 등을 고려하여 적절한 테스트 전략을 수립해야 합니다.
모든 결함을 수정했더라도 사용자의 요구나 비즈니스 목적을 충족하지 못한다면 높은 품질의 소프트웨어라고 할 수 없습니다.

React 컴포넌트 테스트
import React from 'react';
const Button = ({ onClick, children }) => {
return <button onClick={onClick}>{children}</button>;
};
export default Button;
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import Button from './Button';
test('버튼 클릭 시 콜백 함수가 호출되어야 함', () => {
const handleClick = jest.fn();
const { getByText } = render(<Button onClick={handleClick}>클릭</Button>);
fireEvent.click(getByText('클릭'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
유틸리티 함수 테스트
export function formatDate(date) {
return date.toISOString().split("T")[0];
}
import { formatDate } from './formatDate';
test('날짜를 YYYY-MM-DD 형식의 문자열로 변환해야 함', () => {
const date = new Date('2023-10-11T00:00:00Z');
expect(formatDate(date)).toBe('2023-10-11');
}
컴포넌트 간 상호작용 테스트
import React, { useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>증가</button>
</div>
);
};
export default Counter;
// Counter.test.jsx
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import Counter from "./Counter";
test("버튼 클릭 시 카운트가 증가해야 함", () => {
const { getByText } = render(<Counter />);
const counterText = getByText("현재 카운트: 0");
const button = getByText("증가");
fireEvent.click(button);
expect(counterText.textContent).toBe("현재 카운트: 1");
});
API 호출 및 상태 관리 테스트
import React, { useEffect, useState } from "react";
const UserList = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("/api/users")
.then((res) => res.json())
.then((data) => setUsers(data));
}, []);
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
export default UserList;
import React from "react";
import { render, waitFor } from "@testing-library/react";
import UserList from "./UserList";
beforeEach(() => {
global.fetch = jest.fn(() =>
Promise.resolve({
json: () =>
Promise.resolve([
{ id: 1, name: "홍길동" },
{ id: 2, name: "김철수" },
]),
})
);
});
test("사용자 목록을 렌더링해야 함", async () => {
const { getByText } = render(<UserList />);
await waitFor(() => {
expect(getByText("홍길동")).toBeInTheDocument();
expect(getByText("김철수")).toBeInTheDocument();
});
});
회원 가입 및 로그인 흐름 테스트 (Cypress)
describe("회원 가입 및 로그인 E2E 테스트", () => {
it("사용자가 회원 가입하고 로그인할 수 있어야 함", () => {
// 회원 가입 페이지로 이동
cy.visit("http://localhost:3000/signup");
// 회원 가입 폼 입력 및 제출
cy.get('input[name="username"]').type("testuser");
cy.get('input[name="email"]').type("testuser@example.com");
cy.get('input[name="password"]').type("password123");
cy.get('button[type="submit"]').click();
// 회원 가입 성공 메시지 확인
cy.contains("회원 가입이 완료되었습니다").should("be.visible");
// 로그인 페이지로 이동
cy.visit("http://localhost:3000/login");
// 로그인 폼 입력 및 제출
cy.get('input[name="email"]').type("testuser@example.com");
cy.get('input[name="password"]').type("password123");
cy.get('button[type="submit"]').click();
// 로그인 성공 후 대시보드 확인
cy.url().should("include", "/dashboard");
cy.contains("testuser님 환영합니다").should("be.visible");
});
});
상품 구매 흐름 테스트 (Playwright)
const { test, expect } = require("@playwright/test");
test("상품 검색부터 구매까지의 E2E 테스트", async ({ page }) => {
// 메인 페이지로 이동
await page.goto("http://localhost:3000");
// 상품 검색
await page.fill('input[placeholder="검색"]', "헤드폰");
await page.press('input[placeholder="검색"]', "Enter");
// 검색 결과에서 상품 선택
await page.click('.product-item:has-text("헤드폰")');
// 상품 상세 페이지에서 '구매하기' 클릭
await page.click("text=구매하기");
// 결제 페이지에서 정보 입력
await page.fill('input[name="cardNumber"]', "1234 5678 9012 3456");
await page.fill('input[name="expiryDate"]', "12/25");
await page.fill('input[name="cvv"]', "123");
// 구매 완료
await page.click('button[type="submit"]');
// 구매 완료 확인
await expect(page).toHaveURL(/.*\/order\/confirmation/);
await expect(page.locator("text=구매가 완료되었습니다")).toBeVisible();
});
early return과 같은 안전한 반환 처리가 되어 있는지 확인합니다.