Skip to main content

43장. 테스트를 쉽게 이해하고 적용하기

[8부] 테스트를 쉽게 이해하고 적용하기

[8부] 테스트를 쉽게 이해하고 적용하기

[8부] 테스트를 쉽게 이해하고 적용하기

테스트는 왜 이렇게 무섭게 들릴까

처음 테스트를 접하면 많은 사람이 이렇게 느낀다. "코드도 겨우 돌아가게 만들었는데, 이제 그걸 또 검사하는 코드를 써야 한다고?" 이 반응은 아주 자연스럽다. 테스트는 낯설고, 이름부터 딱딱하고, 뭔가 전문가만 하는 일처럼 보이기 때문이다.

하지만 테스트를 아주 쉬운 말로 바꾸면 생각보다 단순하다. 테스트는 내가 기대한 대로 서비스가 동작하는지 확인하는 과정이다. 빵집에서 새로 만든 식빵을 손님에게 내놓기 전에 한 번 잘라 맛을 보고, 질감과 향을 확인하는 것과 비슷하다. 코드를 썼다면, 그 코드가 정말 의도대로 움직이는지 확인해야 한다.1

이 장에서 꼭 가져가야 할 핵심은 하나다. 테스트는 개발자를 괴롭히는 추가 숙제가 아니라, 안심하고 고칠 수 있게 만드는 안전장치라는 점이다. 한 번 익히면 "이거 바꿨다가 다른 기능 망가지는 거 아니야?"라는 공포가 줄어든다. 바로 그 점이 테스트의 가장 큰 가치다.

테스트의 진짜 목적: 잘 만들었다보다, 망가지지 않았다

초보자는 테스트를 "완벽함을 증명하는 도장"처럼 생각하기 쉽다. 하지만 실제로 테스트의 더 현실적인 목적은 완벽 증명이 아니다. 오히려 이렇게 생각하는 편이 맞다.

"이번 수정 때문에 원래 되던 게 망가지지 않았는지 확인하자."

서비스를 만들다 보면 기능 추가보다 수정이 더 많다. 버튼 문구를 바꾸고, 로그인 흐름을 손보고, 검색 조건을 조금 고친다. 이때 무서운 건 새 기능 자체보다 기존 기능이 조용히 깨지는 일이다. 테스트는 그 조용한 사고를 빨리 발견하게 도와준다.1

특히 웹 서비스는 화면, 서버, 데이터베이스, 로그인, 권한이 얽혀 있다. 눈으로 보기엔 멀쩡한데 실제로는 데이터 저장이 안 되거나, 목록은 뜨는데 수정이 실패하거나, 내 글이 아닌 남의 글도 수정되는 문제가 생길 수 있다. 그래서 테스트는 "보이는 것"만 확인하는 일이 아니라, 서비스의 핵심 흐름이 계속 살아 있는지 점검하는 일이다.2

수동 테스트와 자동 테스트를 먼저 구분하자

테스트는 크게 두 가지로 나눠 이해하면 쉽다.

수동 테스트는 사람이 직접 서비스를 눌러 보며 확인하는 방식이다. 회원가입을 해 보고, 로그인해 보고, 글을 써 보고, 삭제해 보는 식이다. 가장 직관적이고, 초보자에게 가장 먼저 필요한 방식이기도 하다.1

자동 테스트는 이런 확인 과정을 코드로 적어 두고, 컴퓨터가 대신 반복해서 실행하게 만드는 방식이다. 한 번 만들어 두면 커밋 후에도, 배포 전에도, 기능 수정 후에도 빠르게 다시 확인할 수 있다. 반복 검사를 자동화한다는 점에서 큰 힘을 발휘한다.2

입문자에게 중요한 사실은 둘 중 하나만 고르는 게 아니라는 점이다. 처음에는 수동 테스트로 감을 잡고, 자주 확인해야 하는 핵심 흐름부터 자동 테스트로 옮겨 가면 된다. 즉, 수동 테스트는 이해의 시작점이고, 자동 테스트는 반복의 구원자다.

초보자가 알아야 할 테스트 종류는 많지 않다

테스트 세계에 들어가면 용어가 한꺼번에 쏟아진다. 단위 테스트, 통합 테스트, E2E 테스트, 회귀 테스트, 스모크 테스트, 계약 테스트, 성능 테스트, 보안 테스트…. 이름만 보면 벌써 브라우저 창을 닫고 싶어진다.

하지만 지금 우리에게 꼭 필요한 것은 아래 네 가지 감각이다.

  • 단위 테스트: 작은 함수 하나가 제대로 동작하는지 확인한다.

  • 통합 테스트: 여러 부분이 연결됐을 때 잘 맞물리는지 확인한다.

  • E2E 테스트: 실제 사용자처럼 처음부터 끝까지 흐름을 확인한다.

  • 스모크 테스트: 서비스가 최소한 살아 있는지 빠르게 확인한다.34

여기서 가장 먼저 익히기 좋은 것은 단위 테스트와 스모크 테스트다. 단위 테스트는 작고 간단해서 시작이 쉽고, 스모크 테스트는 서비스 전체를 아주 얕게라도 빠르게 확인할 수 있어서 실전 효용이 높다.

단위 테스트: 작은 부품 하나를 확인하는 습관

단위 테스트는 "이 작은 함수가 제대로 계산하나?"를 보는 테스트다. 예를 들어 제목이 1자 이상인지 검사하는 함수, 날짜를 보기 좋게 바꾸는 함수, 검색어를 정리하는 함수처럼 작고 명확한 규칙을 가진 코드에 잘 어울린다.

단위 테스트가 좋은 이유는 실패 원인을 찾기 쉽기 때문이다. 로그인 전체 흐름이 깨졌다면 원인이 화면인지, 서버인지, 권한인지 헷갈릴 수 있다. 하지만 validateTitle() 같은 함수가 실패했다면 문제 범위가 아주 좁다. 덕분에 디버깅이 훨씬 편해진다.

예를 들어 글 작성 폼에서 제목과 내용을 검사하는 함수를 만들었다고 해 보자.

export function validatePostInput(title: string, content: string) {
  if (!title.trim()) return '제목을 입력하세요.'
  if (title.length > 100) return '제목은 100자 이하로 입력하세요.'
  if (!content.trim()) return '내용을 입력하세요.'
  return null
}

이 함수는 규칙이 분명하다. 그러니 테스트도 쉽게 쓸 수 있다.

import { describe, it, expect } from 'vitest'
import { validatePostInput } from './validatePostInput'

describe('validatePostInput', () => {
  it('제목이 비어 있으면 에러 메시지를 반환한다', () => {
    expect(validatePostInput('', '본문')).toBe('제목을 입력하세요.')
  })

  it('내용이 비어 있으면 에러 메시지를 반환한다', () => {
    expect(validatePostInput('제목', '')).toBe('내용을 입력하세요.')
  })

  it('제목과 내용이 모두 있으면 null을 반환한다', () => {
    expect(validatePostInput('제목', '본문')).toBe(null)
  })
})

이 정도만 해도 이미 큰 진전이다. 초보자에게 테스트의 첫 성공 경험은 "서비스 전체를 검증했다"가 아니라, 작은 규칙 하나를 코드로 확실히 고정했다는 데 있다.

통합 테스트: 연결부에서 생기는 문제를 잡아내기

서비스는 부품 하나만 잘 돌아간다고 끝나지 않는다. 화면에서 입력한 값이 서버로 가고, 서버가 데이터베이스에 저장하고, 다시 목록에 반영되는 흐름이 맞아야 한다. 이 연결 구간을 확인하는 것이 통합 테스트의 감각이다.

웹 서비스나 API 테스트가 중요한 이유도 여기에 있다. UI를 통하지 않아도 서버의 요청과 응답, 상태 코드, 데이터 형식을 빠르게 검증할 수 있기 때문이다. 보통 UI 테스트보다 빠르고, 문제 지점을 더 직접적으로 확인하기 좋다.23

예를 들어 아래 같은 질문은 통합 테스트 감각에 가깝다.

  • 로그인 요청을 보내면 정말 200 응답이 오는가

  • 글 작성 요청 뒤에 목록 조회를 하면 새 글이 보이는가

  • 삭제 요청 뒤에 상세 페이지를 열면 404 또는 적절한 오류가 나는가

이 단계에서는 꼭 거창한 자동화가 아니어도 된다. Postman이나 Thunder Client 같은 도구로 요청을 보내 보고, 응답이 예상과 맞는지 확인하는 것만으로도 훌륭한 시작이다.2

E2E 테스트: 실제 사용자처럼 한 바퀴 돌아보기

E2E는 End-to-End의 줄임말이다. 말 그대로 시작부터 끝까지 확인하는 테스트다. 사용자가 브라우저에서 로그인하고, 글을 작성하고, 목록에서 방금 쓴 글을 보고, 상세로 들어가고, 수정하고, 삭제하는 흐름 전체를 점검한다.

이 테스트는 현실감이 강하다. 실제 사용자 경험과 가장 비슷하기 때문이다. 대신 느리고, 설정이 조금 번거롭고, 화면 구조가 자주 바뀌면 깨지기 쉽다. 그래서 초보자는 E2E를 많이 쓰기보다 정말 중요한 흐름 1~2개만 먼저 고정하는 것이 좋다.

우리 서비스에서는 이런 흐름이 가장 중요하다.

  1. 회원가입 또는 로그인에 성공한다.

  2. 로그인한 사용자가 글을 작성한다.

  3. 목록에서 방금 작성한 글이 보인다.

  4. 상세 페이지에서 내용을 확인한다.

  5. 작성자가 자기 글을 수정하거나 삭제할 수 있다.

이 다섯 단계는 서비스의 심장과 같다. 이 흐름이 살아 있으면 서비스는 일단 숨을 쉬고 있다고 볼 수 있다.

스모크 테스트: 연기만 안 나면 일단 통과

스모크 테스트는 이름이 재미있다. 기계에 전원을 넣었을 때 연기가 나지 않는지 먼저 보는 데서 나온 표현이다. 소프트웨어에서는 새 빌드나 새 배포가 최소한 실행 가능하고, 핵심 경로가 살아 있는지 빠르게 확인하는 테스트를 뜻한다.4

중요한 점은 스모크 테스트가 모든 기능을 깊게 검사하는 테스트가 아니라는 것이다. 오히려 반대다. 아주 짧고 얕게, 대신 빠르게 확인한다. 목적은 하나다. "이 버전, 다음 단계로 넘어가도 되나?"4

우리 프로젝트 기준으로 스모크 테스트는 이런 식으로 잡으면 충분하다.

  • 홈 화면이 열린다.

  • 로그인 페이지가 열린다.

  • 테스트 계정으로 로그인된다.

  • 글 목록이 불러와진다.

  • 글 작성이 된다.

여기서 하나라도 막히면 깊은 테스트로 들어가기 전에 멈춰야 한다. 마치 집 문이 안 열리는데 거실 인테리어를 논의할 수 없는 것과 같다.

우리 서비스에 꼭 필요한 최소 테스트 세트

이 책의 목표는 거대한 테스트 체계를 만드는 것이 아니다. 초보자가 실제 서비스를 끝까지 완성하는 것이다. 그래서 테스트도 욕심내기보다 작고 강한 세트로 시작하는 편이 좋다.

추천하는 최소 세트는 이렇다.

1단계: 수동 체크리스트 만들기

배포 전이나 큰 수정 후에 아래 항목을 직접 해 본다.

  • 회원가입이 된다.

  • 로그인이 된다.

  • 로그인 후 글 작성이 된다.

  • 내가 쓴 글이 목록과 상세에 보인다.

  • 수정이 된다.

  • 삭제가 된다.

  • 로그아웃이 된다.

  • 비로그인 상태에서 글 작성 페이지 접근이 막힌다.

이 체크리스트만 있어도 "뭘 확인해야 하지?"라는 혼란이 크게 줄어든다.

2단계: 단위 테스트 2~3개 추가하기

다음처럼 규칙이 분명한 함수부터 테스트한다.

  • 입력값 검증 함수

  • 날짜 또는 문자열 포맷 함수

  • 검색어 정리 함수

화려하지 않아 보여도 괜찮다. 이런 작은 함수는 자주 바뀌고, 깨졌을 때 은근히 불편하다.

3단계: 핵심 E2E 또는 스모크 테스트 1개 만들기

가장 추천하는 것은 로그인 → 글 작성 → 목록 확인 흐름 하나다. 이 한 줄기만 자동화해도 서비스 전체가 연결되어 있다는 사실을 강하게 검증할 수 있다.

Playwright 같은 도구로 핵심 흐름 하나만 자동화해 보기

브라우저에서 실제 사용자처럼 클릭하고 입력하는 테스트 도구로는 Playwright가 많이 쓰인다. 처음부터 복잡하게 갈 필요는 없다. 한 개의 시나리오만 자동화해도 충분하다.

예를 들어 개념은 이런 식이다.

test('로그인 후 글을 작성할 수 있다', async ({ page }) => {
  await page.goto('/login')
  await page.getByLabel('이메일').fill('[email protected]')
  await page.getByLabel('비밀번호').fill('password1234')
  await page.getByRole('button', { name: '로그인' }).click()

  await page.goto('/posts/new')
  await page.getByLabel('제목').fill('테스트 글')
  await page.getByLabel('내용').fill('테스트 본문입니다.')
  await page.getByRole('button', { name: '등록' }).click()

  await expect(page.getByText('테스트 글')).toBeVisible()
})

이 코드는 프로젝트 구조에 따라 달라질 수 있지만, 중요한 것은 문법보다 흐름이다. 이 테스트가 통과한다면 화면, 인증, 서버, 저장, 목록 반영까지 한 번에 확인할 수 있다. 한마디로 서비스의 등줄기를 한 번 쭉 눌러 보는 테스트다.

API 테스트는 UI 테스트보다 먼저 배워도 좋다

브라우저에서 화면을 클릭하는 테스트가 부담스럽다면, API 테스트부터 시작하는 것도 좋다. 서버는 보통 요청을 받고 응답을 돌려주는 구조라서, 화면보다 문제를 단순하게 보기 쉽다.3

예를 들어 이런 흐름이다.

  1. 로그인 API에 이메일과 비밀번호를 보낸다.

  2. 200 응답과 토큰 또는 세션이 오는지 확인한다.

  3. 글 작성 API에 제목과 내용을 보낸다.

  4. 201 응답이 오는지 확인한다.

  5. 글 목록 API에서 방금 만든 글이 보이는지 확인한다.

이 방식의 장점은 화면 스타일이나 버튼 위치와 상관없이 서비스의 뼈대를 먼저 확인할 수 있다는 점이다. 프론트엔드가 조금 흔들려도 백엔드 핵심 흐름은 별도로 검증할 수 있다.

AI와 함께 테스트를 만드는 법

여기서 Claude Code나 Codex가 아주 큰 도움을 준다. 다만 "테스트 다 만들어 줘"라고 던지는 것보다, 무엇을 확인해야 하는지 명확하게 정의해 주는 편이 훨씬 좋다. Codex도 명확한 맥락과 완료 조건이 있을수록 더 잘 동작한다.5

좋은 요청은 대체로 이런 요소를 포함한다.

  • 어떤 기능을 테스트할지

  • 어떤 도구를 쓸지

  • 어떤 시나리오를 먼저 다룰지

  • 성공 기준이 무엇인지

  • 테스트 데이터는 무엇인지

예를 들어 Claude Code에 이렇게 요청할 수 있다.

Next.js + Supabase 프로젝트다. 초보자용으로 가장 중요한 테스트만 추가하고 싶다. Vitest로 입력 검증 함수 단위 테스트 3개를 만들고, Playwright로 로그인 후 글 작성이 되는 스모크 테스트 1개를 작성해 줘. 각 테스트가 왜 필요한지도 주석으로 짧게 설명해 줘.

Codex에는 이렇게 더 구체적으로 줄 수 있다.

현재 프로젝트 구조를 읽고, posts 작성 흐름의 핵심 경로를 기준으로 최소 테스트 세트를 제안해 줘. 새로운 테스트 파일만 만들고 기존 기능 코드는 건드리지 마. 실행 명령어와 실패 시 점검 포인트도 README 형식으로 정리해 줘.

핵심은 AI가 코드를 많이 쓰게 하는 것이 아니라, 내가 무엇을 안심하고 싶은지 분명히 말하는 것이다.

AI가 만든 테스트를 그대로 믿으면 안 되는 이유

테스트 코드라고 해서 항상 맞는 것은 아니다. 오히려 AI가 만든 테스트에는 자주 등장하는 함정이 있다.

첫째, 실제로 존재하지 않는 버튼 이름이나 라벨을 가정할 수 있다. 테스트가 실행조차 안 되면 대부분 여기서 막힌다.

둘째, 너무 구현 세부사항에 묶인 테스트를 만들 수 있다. 예를 들어 특정 CSS 클래스나 깊은 DOM 구조에 매달리면, 디자인만 바꿔도 테스트가 깨진다.

셋째, 성공처럼 보이는 허술한 테스트를 만들 수 있다. 단순히 페이지가 열렸다는 이유만으로 기능 성공이라고 판단하면 진짜 중요한 저장이나 권한 문제를 놓칠 수 있다.

그래서 AI가 만든 테스트는 꼭 세 가지를 다시 봐야 한다.

  • 이 테스트가 정말 사용자에게 중요한 흐름을 확인하는가

  • 이 테스트가 너무 사소한 구조에 의존하지 않는가

  • 이 테스트가 실패했을 때 원인을 이해할 수 있는가

테스트는 많다고 좋은 것이 아니다. 의미 있는 테스트가 좋은 테스트다.

초보자가 자주 하는 테스트 실수

처음에는 누구나 비슷한 함정에 빠진다.

가장 흔한 실수는 모든 걸 다 테스트하려는 것이다. 그러면 시작도 전에 지친다. 테스트는 작은 승리부터 쌓아야 한다.

또 다른 실수는 눈으로 한 번 확인한 것을 영원히 믿는 것이다. "아까 됐으니까 괜찮겠지"는 개발 세계에서 꽤 위험한 문장이다. 조금만 수정해도 다른 곳이 쉽게 흔들린다.

마지막 실수는 실패한 테스트를 귀찮은 존재로 보는 것이다. 사실 실패한 테스트는 아주 친절한 경고다. 서비스가 실제 사용자 앞에서 망가지기 전에, 개발자에게 먼저 조용히 알려주는 셈이기 때문이다.

오늘 바로 적용하는 가장 현실적인 순서

처음 테스트를 붙일 때는 아래 순서를 추천한다.

  1. 핵심 사용자 흐름을 글로 적는다.

  2. 배포 전에 직접 해 볼 수동 체크리스트를 만든다.

  3. 규칙이 분명한 함수 하나에 단위 테스트를 붙인다.

  4. 로그인 또는 글 작성처럼 중요한 흐름 하나를 스모크 테스트로 자동화한다.

  5. 커밋이나 배포 전에 이 테스트를 다시 실행한다.

이 순서가 좋은 이유는 부담이 적기 때문이다. 처음부터 거대한 테스트 프레임워크를 이해하려고 하면 책을 덮고 싶어진다. 하지만 작은 함수 테스트 하나와 핵심 흐름 테스트 하나는 충분히 해볼 만하다. 그리고 그 둘만 있어도 "고쳐도 괜찮다"는 감각이 생긴다.

테스트는 개발자를 느리게 만드는가, 빠르게 만드는가

처음에는 분명 조금 느려진다. 테스트를 생각하고, 케이스를 쓰고, 실패를 고쳐야 하기 때문이다. 하지만 시간이 지나면 오히려 빨라진다.

왜냐하면 테스트가 있으면 수정할 때 덜 불안하고, 버그를 더 빨리 찾고, 배포 전에 자신감이 생기기 때문이다. 특히 AI와 협업할 때는 더 그렇다. Claude Code나 Codex가 빠르게 코드를 만들어 줄수록, 그 결과를 검증할 장치가 필요하다. 테스트는 그 속도를 안전하게 만들어 주는 브레이크이자 안전벨트다.5

개발에서 진짜 무서운 것은 느리게 가는 것이 아니라, 어디가 깨졌는지 모른 채 빠르게 달리는 것이다. 테스트는 그 불안을 줄여 준다.

이 장의 핵심 정리

테스트는 어려운 이론보다 먼저, 예상한 대로 동작하는지 확인하는 습관으로 이해하면 된다. 수동 테스트로 시작해도 괜찮고, 가장 작은 단위 테스트 하나부터 붙여도 충분하다. 중요한 것은 서비스의 핵심 흐름을 정하고, 그 흐름을 반복 가능하게 확인하는 것이다.

이 책의 실습 프로젝트에서는 모든 기능을 촘촘히 테스트할 필요가 없다. 대신 회원가입과 로그인, 글 작성, 목록 반영, 수정과 삭제처럼 서비스의 중심 줄기를 먼저 확인해야 한다. 그리고 그중 하나를 자동화해 두면, 이후 기능을 확장하거나 리팩터링할 때 훨씬 덜 불안해진다.

테스트는 처음엔 낯설지만, 한 번 손에 익으면 놀라울 만큼 든든하다. 코드를 잘 쓰는 힘이 엔진이라면, 테스트는 안심하고 방향을 틀 수 있게 해 주는 핸들이다.

참고

1A Beginner's Guide to Testing Your Code

2A Beginner's Guide On Webservices & API Testing - QATouch

3Web Service Testing Tutorial: Definition, Types, and Best Practices  ZetCode

4Smoke Testing

5Workflows - Codex | OpenAI Developers

책 목록

바이브 코딩으로 완성하는 첫 풀스택 웹 서비스

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기