본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 19. 21:05

Unit Test AI 가이드 — 환각 제로, 크로스 스택 표준

요약

AI를 활용하여 환각 없이 일관된 단위 테스트를 생성하기 위한 크로스 스택 가이드입니다. 스택별 최적의 라이브러리 선정과 Cursor IDE의 규칙 시스템을 활용한 테스트 자동화 전략을 다룹니다.

핵심 포인트

  • 스택별 단일 라이브러리(Jest, Vitest, pytest 등) 사용으로 일관성 유지
  • Cursor IDE의 .cursor/rules를 활용한 프로젝트 수준의 AI 지침 주입
  • Arrange-Act-Assert 패턴 준수를 통한 테스트 품질 확보
  • AI 환각 방지를 위한 명확한 명명 규칙 및 컨텍스트 설정

집중 분야: 단위 테스트 (Unit Tests) 전용 — 통합 테스트 (integration), E2E 제외

스택 (Stacks): Node.js (NestJS/Express) · React.js · Python · Angular · Laravel

목표: AI가 환각 (hallucination) 없이 일관되고 결정론적으로 (deterministically) 단위 테스트를 생성함

IDE: Cursor (주요 도구) + Claude (보조 도구)

파트 1 — 스택별 최적의 단일 라이브러리 (최종 결정)

라이브러리를 혼용하지 마세요. 스택당 하나만 선택하고, 완전히 설정하며, 절대 벗어나지 마세요.

스택라이브러리선택 이유
Node.js / NestJS / ExpressJest네이티브 DI 모킹 (DI mocking), @nestjs/testing이 이를 기반으로 구축됨, 가장 넓은 생태계
React.jsVitest + @testing-library/react네이티브 Vite/ESM 지원, Jest 호환 API, 3~10배 더 빠름
Pythonpytest사실상의 표준 (de facto standard), 피스처 (fixture) 시스템으로 보일러플레이트 (boilerplate) 제거, 최고의 플러그인 생태계
AngularJest (Karma 대체)Karma는 Angular 17+에서 지원 중단됨; Jest는 공식 마이그레이션 대상임
LaravelPest현대적인 문법, PHPUnit 기반, 더 높은 신호 대 잡음비 (signal-to-noise ratio)

규칙: 만약 누군가 동일한 스택에 대해 두 번째 라이브러리를 제안한다면, 거절하세요. 스택당 하나의 라이브러리, 한 번의 설정, 그리고 항상 준수할 것.

파트 2 — IDE: Cursor (이 목표를 위한 유일한 선택)

왜 VS Code / WebStorm이 아닌 Cursor인가

기능CursorVS Code + CopilotWebStorm
프로젝트 수준의 AI 규칙.cursor/rules/
코드베이스 인식 컨텍스트 (Codebase-aware context)@codebase부분적부분적
터미널 실행 + 출력 읽기✅ Composer
다중 파일 생성✅ Agent mode제한적
파일 유형별 사용자 정의 지침 (Custom instructions)
MCP 서버 통합

Cursor의 .cursor/rules/ 시스템은 모든 AI 상호작용에 지속적이고 프로젝트 범위 내의 지침을 주입하는 유일한 IDE 네이티브 메커니즘입니다. 이것이 근본적으로 환각 (hallucination)을 방지하는 핵심입니다.

이 프로젝트를 위한 Cursor 설정

project-root/
...

파트 3 — Cursor 규칙 파일 (환각 방지의 핵심)

이 파일들은 일치하는 파일에서 작업할 때 모든 AI 프롬프트에 자동으로 주입됩니다.

3.1 전역 단위 테스트 규칙 (Global Unit Test Rule)

파일: .cursor/rules/unit-test-global.mdc

---
...

// Arrange — 입력값, 모의 객체 (mocks), 예상값 설정

// Act — 테스트 대상인 단일 함수/메서드 호출

// Assert — 정확히 하나의 결과 검증


## 명명 규칙 (Naming Convention) — 필수

...

3.2 NestJS 규칙 (NestJS Rule)

파일: .cursor/rules/unit-test-nestjs.mdc

---
...
const module = await Test.createTestingModule({
  providers: [
    SubjectService,
    { provide: DependencyService, useValue: mockDependency }
  ]
}).compile();

모의 객체 패턴 (Mock Pattern) — jest-mock-extended

import { createMock } from '@golevelup/ts-jest';
// 또는 (OR)
import { mock } from 'jest-mock-extended';

const mockUserRepo = mock();

금지 사항 (Never)

const mockRepo = {
  findOne: jest.fn(),
  save: jest.fn(),
  delete: jest.fn(),
  findAll: jest.fn(),
  update: jest.fn(),
};

모든 NestJS spec 파일에 필요한 필수 임포트 (Required imports for every NestJS spec file)

import { Test, TestingModule } from '@nestjs/testing';
import { NotFoundException, BadRequestException } from '@nestjs/common';

3.3 React 규칙 (React Rule)

파일: .cursor/rules/unit-test-react.mdc

---
...
export default defineConfig({
  test: {
    environment: 'jsdom',
    globals: true,
    ...
  }
});

setup.ts

import '@testing-library/jest-dom';

컴포넌트 테스트 구조 (Component test structure)

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi } from 'vitest';

React를 위한 모킹 규칙 (Mocking rules for React)

...

const user = userEvent.setup();
await user.click(button);
await user.type(input, 'value');

커스텀 훅 테스트 (Custom hook testing)

import { renderHook, act } from '@testing-library/react';

const { result } = renderHook(() => useMyHook());

act(() => result.current.doSomething());

expect(result.current.value).toBe('expected');

typescript

3.4 Python Rule

File: .cursor/rules/unit-test-python.mdc


---

...

python

GOOD — injectable dependency

class UserService:

def __init__(self, repo: UserRepository):

    self.repo = repo

BAD — untestable

class UserService:

def __init__(self):

    self.repo = UserRepository()  # can't mock this

## Mock pattern — pytest-mock (mocker fixture)

python
def test_raises_when_user_not_found(mocker):

mock_repo = mocker.Mock()

mock_repo.find_by_id.return_value = None
...

## Async tests — pytest-asyncio

python
import pytest

@pytest.mark.asyncio

async def test_async_service(mocker):

mock_repo = mocker.AsyncMock()

...

## Fixture pattern — for shared setup

python
@pytest.fixture

def user_service(mocker):

repo = mocker.Mock()

return UserService(repo), repo

def test_get_user_happy_path(user_service):

service, repo = user_service

repo.find_by_id.return_value = User(id="1", email="a@b.com")
...

## Naming

...

darkmode

3.5 Angular Rule

File: .cursor/rules/unit-test-angular.mdc


---

...

bash

g add @angular-builders/jest


## TestBed configuration — always minimal

typescript
await TestBed.configureTestingModule({
imports: [ComponentUnderTest], // standalone components

providers: [

{ provide: UserService, useValue: mockUserService }

]
}).compileComponents();


## Service mock pattern

typescript
const mockUserService = {
getUser: jest.fn(),
createUser: jest.fn(),
};


## Component test — check DOM behavior, not internal state

typescript
fixture.detectChanges(); // trigger ngOnInit

const button = fixture.debugElement.query(By.css('[data-testid="submit"]'));

button.nativeElement.click();

fixture.detectChanges();

expect(fixture.debugElement.query(By.css('.error-msg'))).toBeTruthy();


## Pipe 테스트 — 순수 함수, TestBed 불필요

typescript

it('should transform date correctly', () => {

const pipe = new DateFormatPipe();

expect(pipe.transform(new Date('2024-01-01'))).toBe('Jan 1, 2024');
});


## Guard/resolver 테스트 — 주입 후 직접 호출

typescript

const guard = TestBed.inject(AuthGuard);

const result = await guard.canActivate(mockRoute, mockState);

expect(result).toBe(false);

  
python

### 3.6 Laravel Rule

**파일: `.cursor/rules/unit-test-laravel.mdc`**  


...

  
php

class UserService {

public function __construct(

private readonly UserRepositoryInterface $repo

...


}
  

Mock 패턴 — Mockery (Pest/PHPUnit 포함)


  
php

it('throws exception when user not found', function () {

// Arrange

$repo = Mockery::mock(UserRepositoryInterface::class);
...


});  

Data provider 패턴 (Pest datasets)


  
php
it('validates email format', function (string $email, bool $valid) {

expect(validateEmail($email))->toBe($valid);


})->with([

['valid@email.com', true],

['notanemail', false],
...


]);  

단위 테스트에서 하지 말아야 할 것들

...

  
markdown

## 파트 4 — CLAUDE.md (Claude 프로젝트 메모)

프로젝트 루트에 배치하세요. Claude는 매 세션마다 자동으로 이를 읽습니다.

**파일: `CLAUDE.md`**  

Project: [당신의 프로젝트 이름]

...


## 파트 5 — Cursor + Claude에서 단위 테스트를 위한 MCP 서버

MCP (Model Context Protocol) 서버는 AI 기능을 확장합니다. 단위 테스트의 경우, 다음이 관련됩니다:

### 5.1 파일 시스템 MCP (내장된 Cursor — 직접 사용)

Cursor는 이미 파일 시스템 접근 권한을 가지고 있습니다. 소스 파일을 읽고 테스트를 생성하기 위해 추가적인 MCP가 필요하지 않습니다. `.cursor/rules/` 시스템이 이를 처리합니다.

### 5.2 테스트 워크플로우를 위한 사용 가능한 MCP 서버

**1. `@executeautomation/playwright-mcp-server`**

- Scope: E2E 전용 — 단위 테스트 (unit tests)에는 해당되지 않음
    
- 이 유스케이스에서는 건너뜀
    
**2. `@modelcontextprotocol/server-filesystem`**

- Claude에게 프로젝트 파일에 대한 접근 권한을 부여함
    
- Claude Desktop에서 소스 코드를 읽어 테스트를 생성할 때 사용
    
- 설치: `claude_desktop_config.json`에서 설정
    
**3. 커스텀 테스트 MCP (직접 구축 — 가장 높은 가치)**

다음 기능을 수행하는 경량 MCP 서버를 구축하세요:

- 소스 파일 읽기
    
- 함수 시그니처 (function signatures) + 타입 (types) 추출
    
- Claude에게 구조화된 JSON 반환
    
- Claude가 추측이 아닌 구조를 바탕으로 테스트 생성
    

// mcp-test-generator/src/index.ts

...


**이것이 중요한 이유:** Claude가 가공되지 않은 소스 코드 대신 타입이 지정된 시그니처를 받게 되면, 환각 (hallucination)을 일으킬 수 없습니다. Claude는 추측이 아닌 구체적인 타입을 바탕으로 테스트를 생성합니다.

### 5.3 Claude Desktop MCP 설정

// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)

...


### 5.4 Cursor MCP 설정

// .cursor/mcp.json (프로젝트 레벨)

...


## Part 6 — 환각 방지 프롬프트 템플릿

테스트에서 AI 환각이 발생하는 근본 원인: AI가 메서드 이름을 지어내거나, 잘못된 모의 객체 (mock) 구조를 만들거나, 존재하지 않는 임포트 (imports)를 생성하는 것입니다. 이 프롬프트들은 이를 제거합니다.

### 6.1 Cursor Cmd+K 프롬프트 (정확히 이대로 사용하세요)

이 [NestJS service / React component / Python class / Angular service / Laravel service]에 대한 단위 테스트 파일을 생성하세요.

...


### 6.2 Claude 프롬프트 (CLAUDE.md를 인지하는 세션용)

[filename]을 읽으세요.

...


### 6.3 일괄 테스트 생성 프롬프트

[directory] 내의 각 파일에 대해 해당하는 .spec.ts 파일을 생성하세요.

...


## Part 7 — 프로젝트 구조 컨벤션

모든 스택은 동일한 근접성 원칙을 따릅니다: **테스트 파일은 소스 파일 바로 옆에 위치합니다**.

### Node.js / NestJS / Angular

src/

...


### React

src/

components/

UserCard/

  UserCard.tsx

  UserCard.test.tsx            ← 단위 테스트 (unit test)

  UserCard.stories.tsx

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0