inflearn logo
강의

講義

知識共有

自分だけの Claude Code ハーネス (SPEC・TDD・CIで築くAI開発ワークフロー)

10-3 実習 失敗するテストコード スキル作成

tdd-red 스킬에 허용 에러 범위에 대한 질문

8

MinWoo Kim

投稿した質問数 1

0

미션2.png.webp

 

수업노트로 요청한 tdd-red 스킬의 초안은 다음과 같습니다.

아래 내용으로 진행하면 import error, 파일 없음 에러도 정상적인 red 로 인식하고 다음 단계로 진행됩니다.

---
name: tdd-red
description: 승인된 테스트 시나리오를 실패하는 테스트 코드로 작성한다. TDD Red 단계 시작 시 사용한다.
argument-hint: <feature> <이슈 번호>
allowed-tools: Read Write Bash
---

`$ARGUMENTS`에서 feature 이름과 이슈 번호를 파싱해 해당 이슈의 승인된 시나리오를 실패하는 테스트 코드로 작성한다.

## 시작 전

### 인자 파싱

`$ARGUMENTS`를 공백으로 분리해 첫 번째 토큰을 `{feature}`, 두 번째 토큰을 `{issue}` 로 사용한다.

| 입력 예시          | feature  | issue |
| ------------------ | -------- | ----- |
| `/tdd-red tag 2`   | `tag`    | `2`   |
| `/tdd-red search 1`| `search` | `1`   |

누락된 값이 있으면 실행 전에 사용자에게 질문한다.

- `{feature}`가 없으면: "어떤 기능의 이슈인가요? (예: tag, search, ...)"
- `{issue}`가 없으면: "이슈 번호를 알려주세요."

두 값이 확정된 뒤 `docs/features/{feature}/issue-{issue}.md` 를 읽어 시그니처와 테스트 시나리오 목록을 파악한다.
- 시그니처 섹션: 테스트 대상 파일 경로 및 함수·컴포넌트명 확인
- 테스트 시나리오 섹션: 작성할 시나리오 목록 전체 수집

파일이 없으면 즉시 멈추고 사용자에게 알린다 (`/test-scenarios {feature} {issue}` 를 먼저 실행해야 한다는 안내 포함).

---

## 단계 1: 테스트 파일 준비

시그니처에서 테스트 대상 파일 경로를 추출하고 테스트 파일 경로를 결정한다.

### 파일 위치 규칙

| 구현 파일                          | 테스트 파일                              |
| ---------------------------------- | ---------------------------------------- |
| `src/api/tags.ts`                  | `src/api/tags.test.ts`                   |
| `src/components/TagInput.tsx`      | `src/components/TagInput.test.tsx`       |
| `src/context/NotesContext.tsx`     | `src/context/NotesContext.test.tsx`      |

- 테스트 파일이 이미 존재하면 기존 내용을 읽어 중복 `describe` 블록을 피한다.
- 테스트 파일이 없으면 새로 생성한다.

### 파일 헤더 (신규 생성 시)

```ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
// 컴포넌트 파일인 경우에만 추가:
// import { render, screen } from '@testing-library/react';
// import userEvent from '@testing-library/user-event';
```

---

## 단계 2: 시나리오를 테스트 코드로 작성

시나리오를 하나씩 `it()` 블록으로 변환한다. 한 번에 모두 작성하지 않고 **시나리오 하나 작성 → 실행 → 실패 확인 → 다음 시나리오** 순서를 반복한다.

### 테스트 이름 형식

```
should [기대 동작] when [조건]
```

### describe 블록 구조

함수·컴포넌트 단위로 묶는다.

```ts
describe('함수명 또는 컴포넌트명', () => {
  it('should [기대 동작] when [조건]', () => {
    // Red 단계: 구현이 없으므로 실패하는 코드만 작성
  });
});
```

### Red 테스트 작성 원칙

- 구현이 존재하지 않으므로 import가 실패하거나 호출이 오류를 던져야 한다.
- 억지로 통과시키려 하지 않는다. 실패 자체가 목표다.
- `expect`는 실제 기대 동작을 명확하게 표현한다. `expect(true).toBe(false)` 같은 더미 assertion은 사용하지 않는다.

**API 함수 예시**

```ts
import { addTag } from './tags';

describe('addTag', () => {
  it('should return updated note when valid tag is added', async () => {
    const result = await addTag('note-1', 'work');
    expect(result.tags).toContain('work');
  });

  it('should throw when tag is empty string', async () => {
    await expect(addTag('note-1', '')).rejects.toThrow();
  });
});
```

**컴포넌트 예시**

```ts
import { render, screen } from '@testing-library/react';
import { TagInput } from './TagInput';

describe('TagInput', () => {
  it('should render input when component is mounted', () => {
    render(<TagInput tags={[]} onAdd={vi.fn()} onRemove={vi.fn()} />);
    expect(screen.getByRole('textbox')).toBeInTheDocument();
  });
});
```

---

## 단계 3: 시나리오별 실행 루프

각 시나리오를 작성한 직후 아래 명령으로 해당 테스트 파일만 실행한다.

```bash
npx vitest run <테스트-파일-경로> --reporter=verbose
```

- **실패 확인**: `FAIL` 또는 import 오류가 나오면 정상. 다음 시나리오로 이동한다.
- **통과**: 테스트가 의도치 않게 통과하면 즉시 멈추고 사용자에게 알린다. 구현 파일이 이미 존재하는지 확인한다.

---

## 단계 4: 전체 확인

모든 시나리오 작성이 끝나면 전체 테스트를 실행한다.

```bash
npm test
```

- 작성한 테스트 파일의 모든 케이스가 실패하는지 확인한다.
- 기존에 통과하던 테스트가 새로 실패하면 즉시 사용자에게 보고한다.

---

## 제약

- 테스트 파일(`*.test.ts`, `*.test.tsx`)만 생성하거나 수정한다.
- `src/` 안의 구현 코드(`*.ts`, `*.tsx`, `*.css` 등 테스트 파일 제외)는 절대 수정하지 않는다.
- 테스트가 통과하도록 mock이나 stub으로 우회하지 않는다.

---

## 산출물

- 작성된 테스트 파일 목록 및 경로
- 시나리오별 실패 메시지 요약
- `npm test` 결과: 실패한 테스트 수 / 전체 테스트 수

 

 

관련해서, import 에러 및 파일 에러는 없어야한다.(stub 이 있어야 함. Collect 는 모두 성공하고 Test 단계에서 실패해야한다.) 는 점을 고려해서 아래와 같이 스킬을 수정했는데, 피드백 부탁드립니다.

 

---
name: tdd-red
description: 승인된 테스트 시나리오를 실패하는 테스트 코드로 작성한다. TDD Red 단계 시작 시 사용한다.
argument-hint: <feature> <이슈 번호>
allowed-tools: Read Write Bash
---

`$ARGUMENTS`에서 feature 이름과 이슈 번호를 파싱해 해당 이슈의 승인된 시나리오를 실패하는 테스트 코드로 작성한다.

## 시작 전

### 인자 파싱

`$ARGUMENTS`를 공백으로 분리해 첫 번째 토큰을 `{feature}`, 두 번째 토큰을 `{issue}` 로 사용한다.

| 입력 예시           | feature  | issue |
| ------------------- | -------- | ----- |
| `/tdd-red tag 2`    | `tag`    | `2`   |
| `/tdd-red search 1` | `search` | `1`   |

누락된 값이 있으면 실행 전에 사용자에게 질문한다.

- `{feature}`가 없으면: "어떤 기능의 이슈인가요? (예: tag, search, ...)"
- `{issue}`가 없으면: "이슈 번호를 알려주세요."

두 값이 확정된 뒤 `docs/features/{feature}/issue-{issue}.md` 를 읽어 시그니처와 테스트 시나리오 목록을 파악한다.
- 시그니처 섹션: 테스트 대상 파일 경로 및 함수·컴포넌트명 확인
- 테스트 시나리오 섹션: 작성할 시나리오 목록 전체 수집

파일이 없으면 즉시 멈추고 사용자에게 알린다 (`/test-scenarios {feature} {issue}` 를 먼저 실행해야 한다는 안내 포함).

---

## 단계 0: 스텁 생성

테스트 파일을 작성하기 전에, 이슈의 시그니처를 읽어 **아직 존재하지 않는 구현 파일**에 대해서만 최소 스텁을 생성한다.

### 목적

Import Error 때문에 테스트가 실행조차 되지 않으면, 테스트가 무엇을 거부하는지 알 수 없다. 스텁은 빌드를 통과시켜 테스트가 **Assertion Failure로 실패**하도록 만드는 것이 전부다.

### 스텁 작성 규칙

- **기존 파일이 있으면 건드리지 않는다.** 신규 파일만 생성한다.
- 시그니처의 파라미터·반환 타입을 그대로 선언한다. 구현 로직은 쓰지 않는다.
- 반환값은 타입을 만족하는 최솟값으로 고정한다.

| 반환 타입          | 스텁 반환값   |
| ------------------ | ------------- |
| `string[]`         | `[]`          |
| `string`           | `''`          |
| `boolean`          | `false`       |
| `Promise<T>`       | `Promise.resolve(/* 최솟값 */)` |
| React 컴포넌트     | `return <div />;` |
| `void`             | _(반환 없음)_ |

### 스텁 예시

**훅 (`src/hooks/useTagInput.ts`)**

```ts
import { useState } from 'react';

export function useTagInput(_initialTags: string[]) {
  const [tags] = useState<string[]>([]);
  const [inputValue, setInputValue] = useState('');
  return {
    tags,
    inputValue,
    setInputValue,
    addTag: (_value: string) => {},
    removeTag: (_tag: string) => {},
    handleKeyDown: (_e: React.KeyboardEvent<HTMLInputElement>) => {},
  };
}
```

**컴포넌트 (`src/components/TagInput.tsx`)**

```tsx
import { TagInputProps } from '../types'; // 필요 시 인라인 정의

export function TagInput(_props: TagInputProps) {
  return <div />;
}
```

**API 함수 (`src/api/tags.ts`)**

```ts
import { Note } from '../types/note';

export async function addTag(_noteId: string, _tag: string): Promise<Note> {
  return Promise.resolve({} as Note);
}
```

---

## 단계 1: 테스트 파일 준비

시그니처에서 테스트 파일 경로를 결정한다.

| 구현 파일                        | 테스트 파일                         |
| -------------------------------- | ----------------------------------- |
| `src/api/tags.ts`                | `src/api/tags.test.ts`              |
| `src/components/TagInput.tsx`    | `src/components/TagInput.test.tsx`  |
| `src/context/NotesContext.tsx`   | `src/context/NotesContext.test.tsx` |

- 테스트 파일이 이미 존재하면 기존 내용을 읽어 중복 `describe` 블록을 피한다.
- 테스트 파일이 없으면 새로 생성한다.

### 파일 헤더 (신규 생성 시)

```ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
// 컴포넌트 파일인 경우에만 추가:
// import { render, screen } from '@testing-library/react';
// import userEvent from '@testing-library/user-event';
```

---

## 단계 2: 시나리오를 테스트 코드로 작성

이슈의 시나리오 전체를 한 번에 `it()` 블록으로 변환한다.

### 테스트 이름 형식

```
should [기대 동작] when [조건]
```

### describe 블록 구조

함수·컴포넌트 단위로 묶는다.

```ts
describe('함수명 또는 컴포넌트명', () => {
  it('should [기대 동작] when [조건]', () => {
    // 스텁이 있으므로 import는 통과하고, assertion이 실패한다
  });
});
```

### Red 테스트 작성 원칙

- `expect`는 실제 기대 동작을 명확하게 표현한다. `expect(true).toBe(false)` 같은 더미 assertion은 사용하지 않는다.
- 스텁의 반환값(빈 배열, `<div />` 등)을 기준으로 assertion이 자연스럽게 실패하도록 작성한다.
- 테스트가 통과하도록 mock이나 stub으로 우회하지 않는다.

**API 함수 예시**

```ts
import { addTag } from './tags';

describe('addTag', () => {
  it('should return updated note when valid tag is added', async () => {
    const result = await addTag('note-1', 'work');
    expect(result.tags).toContain('work'); // 스텁은 {} 반환 → tags 없음 → Assertion Failure
  });

  it('should throw when tag is empty string', async () => {
    await expect(addTag('note-1', '')).rejects.toThrow(); // 스텁은 throw 안 함 → Assertion Failure
  });
});
```

**컴포넌트 예시**

```ts
import { render, screen } from '@testing-library/react';
import { TagInput } from './TagInput';

describe('TagInput', () => {
  it('should render input when component is mounted', () => {
    render(<TagInput tags={[]} onAdd={vi.fn()} onRemove={vi.fn()} />);
    expect(screen.getByRole('textbox')).toBeInTheDocument(); // 스텁은 <div /> → textbox 없음 → Assertion Failure
  });
});
```

---

## 단계 3: 실행 및 Red 품질 확인

모든 테스트 파일을 작성한 뒤 파일별로 실행한다.

```bash
npx vitest run <테스트-파일-경로> --reporter=verbose
```

### Red 품질 기준

| 실패 유형          | 판정          | 조치                                     |
| ------------------ | ------------- | ---------------------------------------- |
| Assertion Failure  | ✅ 올바른 Red | 다음으로 진행                            |
| Runtime Error      | ✅ 허용       | 다음으로 진행                            |
| Import Error       | ❌ 스텁 누락  | 단계 0으로 돌아가 해당 파일 스텁 보완    |
| Syntax Error       | ❌ 테스트 오류 | 테스트 코드 수정                         |

- **Import Error가 발생하면 올바른 Red로 인정하지 않는다.** 스텁을 보완한 뒤 다시 실행한다.
- 테스트가 의도치 않게 통과하면 즉시 멈추고 사용자에게 알린다.

---

## 단계 4: 전체 확인

모든 파일의 Red 품질이 확인된 뒤 전체 테스트를 실행한다.

```bash
npm test
```

- 이슈에서 작성한 테스트 파일의 케이스가 모두 Assertion Failure로 실패하는지 확인한다.
- 기존에 통과하던 테스트가 새로 실패하면 즉시 사용자에게 보고한다.

---

## 제약

- 테스트 파일(`*.test.ts`, `*.test.tsx`)은 자유롭게 생성·수정한다.
- 단계 0의 스텁은 **존재하지 않는 파일**에 한해서만 신규 생성한다.
- **기존 구현 파일은 수정하지 않는다.**

---

## 산출물

- 생성된 스텁 파일 목록 및 최솟값 반환 내용
- 작성된 테스트 파일 목록 및 경로
- 파일별 실패 유형 요약 (Assertion Failure / Runtime Error)
- `npm test` 결과: 실패한 테스트 수 / 전체 테스트 수

react tdd ai-agent claude 하네스

回答 1

0

communityai8509

안녕하세요, 인프런 AI 인턴입니다. MinWoo Kim님.

TDD Red 단계에서의 테스트 작성과 관련하여 허용 에러 범위에 대한 개선된 스킬 초안을 제안하셨습니다. 주신 내용에 따르면, Import 오류와 파일 오류를 허용하지 않음으로써 테스트가 Assertion Failure로 확실히 실패하도록 개념을 재구성하셨군요. 이를 통해 스텁 파일을 활용하여 테스트 실행 단계에서 필요한 시그니처와 정의가 준비된 상태를 보장하고자 하셨습니다.

Import Error와 파일 없는 오류 처리 개선:

  • 스텁을 생성: 새로 생성된 구현 파일에 대한 최소 스텁을 만들어 Import Error를 방지하고, 테스트가 Assertion Failure 단계까지 진행될 수 있도록 했습니다.
  • 테스트 준비 단계에서의 명확성: 시그니처 단계에서 확인된 정보에 기반하여 필요한 스텁을 명확히 작성하여 오류를 최소화하고자 합니다.

테스트 실행 및 품질 확인:

  • Red 품질 기준 강화: Assertion Failure가 목표이며, Import Error가 발생하면 이는 스텁을 보강해야 함을 의미한다고 명시하셨습니다.
  • 테스트 실행 흐름 명료화: 단계별 테스트 실행 순서를 통해 실수 없이 테스트가 진행될 수 있도록 체계화하셨습니다.

이러한 수정이 TDD의 의도에 더 부합하고, 테스트 작성자의 작업 효율을 높일 것으로 보입니다.

유사한 주제를 다룬 스텁 및 목(Mock)을 활용한 테스트 정확도 향상테스트 파일 관리 및 GitHub 활용 주제의 게시물을 참고하실 수 있습니다. 추가적인 도움이나 개선 사항에 대해 의견을 받으실 수 있을 것입니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

섹션2- 6강의

0

1

1

git사용자 정보 설정 확인 관련

0

2

1

claude code 실행시간

0

7

1

플랜모드에 대해 업데이트 내용이 있었던 것 같습니다!

0

6

1

21강 수업자료 다운로드 오류, 3:38초 붙여넣기하신 것 어디있나요?

0

5

0

서브에이전트 질문입니다.

0

8

1

자동으로 계속 돌게하려면

1

10

2

PRD 파일에 대해서

0

8

1

2차시에서 진도가 안나고 있습니다

0

8

1

새로운 종목 후보군 추가시 확인해야할 사항들

1

14

2

글씨크기

0

15

2

코드 구현을 파이썬으로 하라고 명시했나요?

0

14

1

user memory 들어가면 이상하게 나와요

0

13

1

세션2부터 수업자료 부탁드립니다~

0

11

1

run.py시 42501 에러

1

18

3

ghossty질문

0

11

0

MCP사용시 보안 문제

0

11

0

기존 pro 요금제 구독 필요

1

14

2

24강 전체 파이프라인

1

15

1

claude는 반드시 api형식이어야 하나요?

1

16

2

요구사항인터뷰 강의자료 안보임

0

66

2

실습에 사용된 MD파일 공유 가능한가요?

1

92

3

codex로 적용해도 큰 문제 없을까요?

0

86

1

강의 자료는 어디서 다운 받나요?

1

78

2