테스트 구조 및 워크플로 상세 가이드
3.1 기본 구성 - AAA 패턴 심화 이해
AAA 패턴은 1950년대부터 시작된 소프트웨어 테스팅의 핵심 구조로, 2001년 Bill Wake에 의해 체계화되고 2002년 Kent Beck의 “Test Driven Development: By Example”에서 널리 알려진 테스트 작성 방법론입니다12. 이 패턴은 테스트의 가독성과 유지보수성을 크게 향상시키며, BDD(Behavior-Driven Development)의 Given-When-Then 구조와 밀접한 관련이 있습니다13.
[asset:1]
**Arrange (준비)테스트 환경을 설정하는 가장 중요한 단계로, 테스트 대상 유닛과 모든 의존성을 격리하여 통제된 환경을 만드는 과정입니다14. 이 단계에서는 다음과 같은 작업을 수행합니다:
- 객체 인스턴스 생성: 테스트할 클래스나 컴포넌트의 인스턴스 생성
 - Mock/Stub 객체 설정: 외부 의존성을 가짜 객체로 대체
 - 테스트 데이터 준비: 입력값, 예상 결과값 등 테스트에 필요한 데이터 설정
 - 환경 설정: 데이터베이스 상태, 파일 시스템, 네트워크 설정 등
 
// Next.js 컴포넌트 테스트에서의 Arrange 예시
describe('LoginForm 컴포넌트', () => {
  test('로그인 성공 시 대시보드로 이동한다', async () => {
    // Arrange - 복잡한 준비 단계
    const mockRouter = { push: jest.fn() };
    const mockAuthService = {
      login: jest.fn().mockResolvedValue({ 
        token: 'test-token', 
        user: { id: 1, name: 'Test User' } 
      })
    };
    
    // Mock Next.js 라우터
    jest.spyOn(require('next/router'), 'useRouter').mockReturnValue(mockRouter);
    
    // 컴포넌트 렌더링
    render(<LoginForm authService={mockAuthService} />);
    
    // 필요한 DOM 요소 가져오기
    const emailInput = screen.getByLabelText('이메일');
    const passwordInput = screen.getByLabelText('비밀번호');
    const submitButton = screen.getByRole('button', { name: '로그인' });중요한 특징: Arrange 단계는 종종 가장 긴 코드 분량을 차지하며, 반복되는 설정이 많다면 beforeEach나 별도의 헬퍼 함수로 추출하는 것이 좋습니다12.
Act (실행) 단계
테스트하고자 하는 핵심 동작을 수행하는 단계로, 보통 한 줄의 코드로 구성됩니다12. 두 줄 이상의 코드가 필요하다면 API 설계를 재검토해야 할 신호일 수 있습니다2.
    // Act - 실제 사용자 동작 시뮬레이션
    await userEvent.type(emailInput, 'test@example.com');
    await userEvent.type(passwordInput, 'password123');
    await userEvent.click(submitButton);Assert (검증) 단계
Act 단계의 결과가 예상한 대로 동작했는지 확인하는 단계입니다12. 단일 동작이더라도 여러 결과가 나올 수 있기 때문에 여러 개의 검증문을 사용할 수 있습니다:
    // Assert - 다양한 결과 검증
    await waitFor(() => {
      expect(mockAuthService.login).toHaveBeenCalledWith('test@example.com', 'password123');
      expect(mockRouter.push).toHaveBeenCalledWith('/dashboard');
      expect(screen.queryByText('로그인 중...')).not.toBeInTheDocument();
    });
  });
});3.2 테스트 더블(Test Double) 완전 이해
테스트 더블은 영화의 스턴트 더블에서 유래된 용어로, 실제 객체를 대신하는 가짜 객체를 의미합니다56. Gerard Meszaros의 분류에 따라 5가지 유형으로 나뉩니다6.
[asset:3]
Mock (목) - 행위 검증 객체
함수나 메서드의 호출 여부, 호출 횟수, 호출 인자를 검증하는 데 사용됩니다57. Mock은 상호작용 검증에 초점을 맞춘 테스트 더블입니다89.
// Next.js에서 API 호출 Mock 예시
test('게시글 삭제 시 API가 올바르게 호출된다', async () => {
  // Arrange
  const mockApiClient = {
    delete: jest.fn().mockResolvedValue({ success: true })
  };
  
  render(<PostList apiClient={mockApiClient} />);
  
  // Act
  fireEvent.click(screen.getByTestId('delete-post-1'));
  
  // Assert - Mock을 통한 행위 검증
  expect(mockApiClient.delete).toHaveBeenCalledWith('/api/posts/1');
  expect(mockApiClient.delete).toHaveBeenCalledTimes(1);
});Mock의 특징:
Stub (스텁) - 상태 검증 객체
미리 정의된 응답을 제공하여 테스트 대상이 올바른 로직을 수행하는지 확인합니다57. Stub은 상태 검증에 초점을 맞춘 테스트 더블입니다89.
// Next.js API 응답 Stub 예시
test('사용자 목록을 올바르게 표시한다', async () => {
  // Arrange - Stub으로 고정된 응답 제공
  const userServiceStub = {
    getUsers: jest.fn().mockResolvedValue([
      { id: 1, name: '홍길동', email: 'hong@test.com' },
      { id: 2, name: '김철수', email: 'kim@test.com' }
    ])
  };
  
  render(<UserList userService={userServiceStub} />);
  
  // Assert - 상태 검증
  await waitFor(() => {
    expect(screen.getByText('홍길동')).toBeInTheDocument();
    expect(screen.getByText('김철수')).toBeInTheDocument();
  });
});Fake (페이크) - 간소화된 실제 구현체
실제 동작을 모방하지만 더 단순하게 구현된 객체입니다59. 프로덕션에는 적합하지 않지만 테스트에서는 실제와 유사한 동작을 제공합니다6.
// In-Memory Database Fake 예시
class FakeUserRepository {
  constructor() {
    this.users = new Map();
    this.nextId = 1;
  }
  
  async create(userData) {
    const user = { id: this.nextId++, ...userData };
    this.users.set(user.id, user);
    return user;
  }
  
  async findById(id) {
    return this.users.get(id) || null;
  }
  
  async update(id, updates) {
    const user = this.users.get(id);
    if (user) {
      Object.assign(user, updates);
      return user;
    }
    return null;
  }
}Fake 객체의 장점:
- 복잡한 외부 의존성 완전 대체 가능
 - 실제와 유사한 동작 제공으로 더 현실적인 테스트
 - 빠른 실행 속도와 안정적인 테스트 환경
 
3.3 파라미터라이즈드 테스트 심화
파라미터라이즈드 테스트는 동일한 테스트 로직을 여러 입력값으로 반복 실행하여 코드 중복을 줄이고 테스트 커버리지를 높이는 강력한 기법입니다1011. 이는 Data-Driven Testing이라고도 불리며, 1997년 JUnit에서 처음 도입된 이후 모든 주요 테스팅 프레임워크에서 지원하고 있습니다11.
[asset:5]
Jest에서의 파라미터라이즈드 테스트 구현
Jest는 test.each()와 describe.each() 함수를 통해 파라미터라이즈드 테스트를 지원합니다1213.
1. 기본 배열 방식
// 계산기 함수 테스트
describe('사칙연산 테스트', () => {
  test.each([
    [2, 3, 5],      // 2 + 3 = 5
    [10, -5, 5],    // 10 + (-5) = 5
    [0, 0, 0],      // 0 + 0 = 0
    [-1, 1, 0]      // -1 + 1 = 0
  ])('add(%i, %i)는 %i를 반환한다', (a, b, expected) => {
    expect(add(a, b)).toBe(expected);
  });
});2. 테이블 형식 (템플릿 리터럴)
// 입력 유효성 검증 테스트
describe('이메일 유효성 검사', () => {
  test.each`
    email                    | expected | description
    ${'user@example.com'}   | ${true}  | ${'정상적인 이메일'}
    ${'invalid-email'}      | ${false} | ${'@가 없는 이메일'}
    ${'test@'}              | ${false} | ${'도메인이 없는 이메일'}
    ${'@domain.com'}        | ${false} | ${'사용자명이 없는 이메일'}
    ${''}                   | ${false} | ${'빈 문자열'}
  `('$description: validateEmail("$email") → $expected', 
    ({ email, expected, description }) => {
      expect(validateEmail(email)).toBe(expected);
    }
  );
});3. 외부 데이터 소스 활용
// testData.json 파일에서 데이터 로드
const testData = require('./testData.json');
 
describe('사용자 권한 검증', () => {
  test.each(testData.userPermissions)(
    '$role 역할의 사용자는 $resource에 $access 권한을 가진다',
    ({ role, resource, access, expected }) => {
      const user = createUser(role);
      expect(user.canAccess(resource, access)).toBe(expected);
    }
  );
});Next.js 프로젝트에서의 실제 활용 사례
[asset:4]
API 라우트 테스트
// 다양한 HTTP 메서드 테스트
describe('/api/users API 엔드포인트', () => {
  test.each([
    ['GET', {}, 200, '사용자 목록 조회'],
    ['POST', { name: 'Test User', email: 'test@example.com' }, 201, '사용자 생성'],
    ['PUT', { id: 1, name: 'Updated User' }, 200, '사용자 정보 수정'],
    ['DELETE', { id: 1 }, 204, '사용자 삭제']
  ])('%s 요청: %s', async (method, body, expectedStatus, description) => {
    const { req, res } = createMocks({
      method,
      body: JSON.stringify(body)
    });
    
    await handler(req, res);
    
    expect(res._getStatusCode()).toBe(expectedStatus);
  });
});컴포넌트 props 검증
// 버튼 컴포넌트의 다양한 상태 테스트
describe('Button 컴포넌트', () => {
  test.each([
    ['primary', 'bg-blue-500', 'text-white'],
    ['secondary', 'bg-gray-500', 'text-white'],
    ['danger', 'bg-red-500', 'text-white'],
    ['success', 'bg-green-500', 'text-white']
  ])('%s 타입 버튼은 올바른 스타일을 적용한다', (variant, bgClass, textClass) => {
    render(<Button variant={variant}>테스트</Button>);
    
    const button = screen.getByRole('button');
    expect(button).toHaveClass(bgClass, textClass);
  });
});파라미터라이즈드 테스트의 핵심 가치
- 코드 중복 제거: 동일한 테스트 로직을 반복 작성할 필요 없음1014
 - 테스트 커버리지 향상: 다양한 입력값으로 광범위한 시나리오 테스트1514
 - 유지보수 효율성: 테스트 로직 변경 시 한 곳만 수정하면 됨1416
 - 가독성 향상: 테스트 데이터와 로직이 명확히 분리됨1015
 - 경계값 테스트: 다양한 edge case를 체계적으로 검증 가능1718
 
사용 시기:
- 동일한 로직을 여러 입력값으로 테스트해야 할 때1015
 - 폼 유효성 검증, API 응답 처리, 계산 로직 등을 테스트할 때1718
 - Cross-browser, 다양한 설정값 테스트가 필요할 때14
 - 대량의 테스트 데이터를 효율적으로 관리해야 할 때1920
 
파라미터라이즈드 테스트는 테스트의 품질과 효율성을 동시에 향상시키는 강력한 도구로, 특히 Next.js와 같은 복잡한 웹 애플리케이션에서 다양한 시나리오를 체계적으로 검증하는 데 필수적인 기법입니다1112.
Footnotes
- 
https://semaphore.io/blog/aaa-pattern-test-automation ↩ ↩2 ↩3 ↩4 ↩5 ↩6
 - 
https://docs.telerik.com/devtools/justmock/basic-usage/arrange-act-assert ↩ ↩2 ↩3 ↩4 ↩5
 - 
https://automationpanda.com/2020/07/07/arrange-act-assert-a-pattern-for-writing-good-tests/ ↩
 - 
https://circleci.com/blog/how-to-test-software-part-i-mocking-stubbing-and-contract-testing/ ↩ ↩2
 - 
https://flambeeyoga.tistory.com/entry/Test-Double과-Stub-Mock-그리고-Fake ↩ ↩2 ↩3 ↩4
 - 
https://www.getxray.app/blog/maximize-your-coverage-with-test-paramtererization-and-data-driven-testing ↩ ↩2 ↩3 ↩4
 - 
https://blog.codeleak.pl/2021/12/parameterized-tests-with-jest.html ↩ ↩2
 - 
https://www.browserstack.com/guide/jest-parameterized-test ↩
 - 
https://www.getxray.app/blog/test-parameterization-techniques ↩ ↩2 ↩3 ↩4
 - 
https://www.linkedin.com/pulse/data-driven-testing-parameterization-tools-elise-lowry ↩ ↩2 ↩3
 - 
https://www.aiotests.com/blog/what-is-parameterization-in-testing ↩
 - 
https://docs.qase.io/general/get-started-with-the-qase-platform/test-cases/test-case-parameters ↩ ↩2
 - 
https://www.zoho.com/qengine/know/data-driven-testing.html ↩ ↩2
 - 
https://jazzteam.org/technical-articles/data-driven-testing/ ↩
 - 
https://smartbear.com/blog/your-guide-to-data-driven-testing/ ↩