티스토리 뷰

[9주차 과제]

1. TDD란 (내 포폴에 TDD 적용해보기)

2. CI/CD란 (Github Actions 통해서 배포 후 디스코드 채널로 알림 오도록 설정)


1. TDD(Test Driven Development, 테스트 주도 개발)

정의 : 소프트웨어 개발에 앞서 테스트를 먼저 작성하는 접근 방식

(프로덕션 코드를 작성하기 전에 테스트 코드를 먼저 작성하는 개발 방법론)

->  테스트가 코드의 설계와 개발을 이끌어가는 방식

 

< 핵심 사이클: Red - Green - Refactor >

 

🔴 Red: 실패하는 테스트를 먼저 작성 (구현이 없으니 당연히 실패)

🟢 Green: 테스트를 통과시키는 최소한의 코드만 작성

🔵 Refactor: 테스트가 통과하는 상태에서 코드 구조 개선

 

-> 이 사이클을 짧은 호흡(보통 몇 분 단위)으로 반복

 

왜 하는가

- 버그 조기 발견

- 회귀(regression) 방지 → 테스트 스위트가 안전망 역할

- "테스트하기 쉬운 코드" = 자연스럽게 결합도 낮은 좋은 설계

- 테스트 코드가 곧 살아있는 명세(문서) 역할

 

// 회귀(Regression) : 원래 잘 되던 기능이 다시 망가지는 현상

// 테스트 스위트(Test Suite) : 여러 개의 테스트를 묶어놓은 집합(테스트 모음집)

 

TDD에서 회귀 방지가 가능한 이유:

  • 기능을 만들 때마다 그 기능에 대한 테스트가 함께 작성됨
  • 시간이 지나면 → 프로젝트에 수백 개의 테스트가 쌓임
  • 새 코드를 추가한 후 모든 테스트를 한 번 돌려보면

만약 기존 기능 B가 깨졌다면 → B에 대한 테스트가 실패함 → 즉시 발견!


포트폴리오 적용

작은 기능 하나 잡아서, 테스트부터 쓰고, 통과시키고,

정리하는 사이클을 반복한 흔적을 코드와 커밋 히스토리에 남기기

 

#환경 세팅

Next.js에서 Jest가 안정적.

// Jest = JavaScript용 테스트 프레임워크

(Meta에서 만든 오픈소스 테스트 도구)

-> JavaScript/TypeScript로 짠 코드가 의도대로 동작하는지 자동으로 검증해주는 프로그램

 

테스트 대상: route.js (chat API) 입력 검증

 

1) 패키지 설치

npm install -D jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event

 

// -D 플래그는 devDependencies(개발 의존성)에 설치한다는 뜻

(개발의존성은 개발할 때만 필요하고, 실제 서비스에는 필요 없는 패키지)

 

2) 설치 확인

-> package.json에서 5개 패키지 체크

 

3) Jest 설정 파일 만들기

루트(최상위)에 "jest.config.js"랑 "jest.setup.js" 파일 만들기

 

jest.config.js 

-> Jest가 어떻게 동작할지 알려주는 설정 파일

 

const nextJest = require('next/jest');

const createJestConfig = nextJest({
  dir: './',
});

const customJestConfig = {
  setupFilesAfterEach: ['<rootDir>/jest.setup.js'],
  testEnvironment: 'jest-environment-jsdom',
};

module.exports = createJestConfig(customJestConfig);

 

jest.setup.js

-> 각 테스트 실행 전에 공통으로 불러올 코드 적는 파일

 

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

 

이 한줄로 같은 DOM 전용 매처를 모든 테스트에서 자동으로 사용 가능

// Matcher(매처) = 값을 어떻게 검증할지 정하는 함수

-> expect (실제값))매처 (기대값);

 

4) package.json에 테스트 스크립트 추가

package.json 파일 열고 Scripts에 아래 두 줄 추가(콤마 잘 붙여줘야함)

"test": "jest",

"test:watch": "jest --watch"

 

5) 환경 세팅 체크(Jest 정상 동작 체크)

프로젝트 루트에 __tests__ 폴더 만들고, 그 안에 sanity.test.js 파일 생성 후 아래 내용 입력

 

test('Jest가 정상 동작한다', () => {
  expect(1 + 1).toBe(2);
});

 

// sanity test는 정상인지 체크하는 가장 기초적인 테스트(환경 동작 검증)

-> 터미널에서 npm test 

PASS, 1 passed => 환경 세팅 완료


# TDD 사이클 시작 (Red → Green → Refactor 한 사이클)

 

시나리오

/api/chat 라우트에 잘못된 요청이 왔을 때 적절히 응답하는지 검증

-> messages 배열의 마지막 메시지 content가 빈 문자열이면 400 상태코드를 반환해야 한다

 

// ChatWidget.jsx에서 서버로 요청 보낼 때
fetch('/api/chat', {
  method: 'POST',
  body: JSON.stringify({
    messages: [{ role: 'user', content: '' }]  // ← content가 비어있음
  })
});

🔴 Red 단계

  1. __tests__/api/chat.test.js 파일 생성
  2. "message content가 빈 문자열이면 400" 테스트 코드 작성
  3. npm test 실행 → 실패 확인

🟢 Green 단계

  1. route.js에 검증 로직 추가
  2. npm test 실행 → 통과 확인

🔵 Refactor 단계

  1. 검증 로직 분리해서 깔끔하게
  2. npm test 다시 실행 → 여전히 통과

Red 단계)

chat.test.js

 

실패 확인

 

Red에서 "기능 코드는 한 줄도 안 짰음"

route.js(실제 기능 파일)는 손도 안 대고, 테스트 파일만 새로 만듦

"기능 명세서를 코드로 미리 적어둠"

-> Red 단계는 본질적으로 "이런 기능을 만들 거야"라고 코드로 약속하는 단계

 

*비유

🔴 Red = 시험 문제를 출제 (정답은 모르는 상태)

🟢 Green = 그 시험 문제를 풀어서 정답 맞히기

 

 

Green 단계)

테스트 통과시키기.

 

1. API 라우트는 브라우저 환경(jsdom)이 아닌 Node.js 환경에서 돌아야함

테스트 파일 상단에 환경 지시자 추가

(node 환경에서 돌려달라고 Jest에게 파일별로 지시하는 주석)

 

/**
 * @jest-environment node
 */

 

2. route.js에 아래 코드 추가

const lastMessage = messages[messages.length - 1] if (!lastMessage?.content?.trim()) { return Response.json({ error: 'Message content cannot be empty' }, { status: 400 }) }

 

통과 확인

 

Refactor 단계)

테스트가 통과하는 상태에서 코드를 더 깔끔하게 정리하는 단계(리팩토링)

-> 기능은 그대로 코드만 개선, 테스트는 계속 통과

 

원칙: 리팩토링은 Green 상태에서 시작하고 한 번에 하나씩만 바꾸기

리팩토링 후 반드시 테스트 다시 돌리기. 기능 추가하지 말기

 

const lastMessage = messages[messages.length - 1]
if (!lastMessage?.content?.trim()) {
  return Response.json({ error: 'Message content cannot be empty' }, { status: 400 })
}

const history = messages.slice(0, -1).map(m => ({
  role: m.role === 'assistant' ? 'model' : 'user',
  parts: [{ text: m.content }],
}))

const lastInput = messages[messages.length - 1].content  // ← 중복


마지막 한줄만 수정

->  const lastInput = lastMessage.content

// 위에서 만든 변수 재사용

 

이후 테스트, 여전히 패스하면 한 사이클 성공! githup push함.

// 코드의 일부를 바꾸더라도 테스트를 바로 하기 때문에

변경의 안정성을 바로 검증할 수 있음(안전망)

 

간단한 리팩토링 성공!

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함