테스트 코드 살펴보기(미리보기 마지막)

Chapter 7. 코드베이스 적응

by 고코더

"테스트 코드라고? 뭘 테스트 하는 거지?"

123123.png

코드 저장소를 클론받았다. 프로젝트 구조를 파악했다. 이제 첫 작업을 받을 준비가 됐다고 생각했다.

그런데 선배가 슬랙으로 메시지를 보냈다. "내일 작업 시작하기 전에 테스트 코드부터 한번 읽어보세요. 코드 이해하는 데 도움 될 거예요."


'테스트 코드?'

프로젝트 폴더를 열어봤다. test, tests, __tests__ 같은 폴더가 보인다. 클릭했다. 파일이 수십 개다. 하나를 열어봤다.


test_user_login_success()

test_user_login_invalid_password()

test_user_login_nonexistent_user()


'이게 뭐지? 로그인 기능을 테스트하는 건가? 그런데 왜 코드가 두 번 있는 거지?'

머릿속이 복잡하다. 실제 코드도 있고, 테스트 코드도 있다. 둘의 차이가 뭔지 잘 모르겠다. 테스트 코드는 왜 있는 걸까? 꼭 읽어야 하나? 실제 코드만 보면 안 되나?


걱정하지 마라. 테스트 코드는 생각보다 무섭지 않다. 오히려 신입 개발자에게는 최고의 가이드다. 여기서는 테스트 코드가 무엇인지, 왜 중요한지, 그리고 어떻게 읽고 실행하는지 차근차근 알아보자.


테스트 코드가 뭔가요?


테스트 코드란 내가 작성한 코드가 제대로 작동하는지 자동으로 확인해주는 코드다.

예를 들어, 로그인 기능을 만들었다고 해보자. 직접 브라우저를 열고, 아이디를 입력하고, 비밀번호를 입력하고, 로그인 버튼을 눌러서 확인할 수 있다. 하지만 매번 이렇게 하면 시간이 너무 오래 걸린다.


테스트 코드는 이 과정을 자동화한다. 코드를 실행하면, 로그인이 성공하는지, 잘못된 비밀번호를 입력했을 때 에러가 나는지, 존재하지 않는 사용자로 로그인했을 때 어떻게 되는지를 자동으로 확인해준다.


테스트 코드의 구조는 보통 이렇다.

function test_login_success() {

// 준비: 테스트용 사용자 만들기

user = create_test_user()

// 실행: 로그인 시도

result = login(user.email, user.password)

// 검증: 성공했는지 확인

assert result.success == true

}


크게 세 부분으로 나뉜다.

준비(Arrange): 테스트에 필요한 데이터를 준비한다

실행(Act): 실제로 기능을 실행한다

검증(Assert): 결과가 예상과 맞는지 확인한다


이 패턴은 모든 테스트 코드에서 반복된다. 언어나 프레임워크가 달라도 본질은 같다.

테스트가 없는 코드를 수정하는 건 줄타기와 같다. 한 군데 고쳤는데 다른 곳이 망가질 수 있다. 그래서 더 조심스럽게 작업해야 한다. 가능하면 수정하기 전에 테스트부터 만들자. 기존 동작을 검증하는 테스트를 먼저 쓰고, 그다음에 코드를 수정한다. 그러면 훨씬 안전하다.


왜 테스트 코드를 읽어야 할까?

"테스트 코드요? 저는 실제 코드만 보면 되는 거 아닌가요?" 라는 생각을 했다. 하지만 테스트 코드는 실제 코드를 작성을 돕는 보물창고와 같다.


1. 함수 사용법이 명확하게 나와 있다

실제 코드에는 함수 정의만 있다. 하지만 테스트 코드에는 그 함수를 어떻게 사용하는지 예시가 나와 있다.


예를 들어,

test_10_percent_discount() {

result = calculate_discount(10000, "SAVE10")

assert result == 9000

}


test_invalid_coupon() {

result = calculate_discount(10000, "INVALID")

assert result == 10000

}


'아하, calculate_discount 는 가격과 쿠폰 코드를 받아서, 할인된 가격을 리턴하는구나. 유효하지 않은 쿠폰이면 원래 가격 그대로 리턴하고.' 이렇게 테스트 코드로 함수의 사용 방법을 익힐 수 있다.


2. 예외 상황까지 알 수 있다

실제 코드를 읽으면 정상적인 흐름은 이해할 수 있다. 하지만 예외 상황은? 잘못된 입력이 들어오면? 네트워크가 끊기면?


테스트 코드에는 이런 예외 케이스들이 다 나와 있다.


test_empty_email() {

result = login("", "password123")

assert result.error == "이메일을 입력해주세요"

}


test_network_timeout() {

result = fetch_user_data(timeout=0.001)

assert result.error == "네트워크 타임아웃"

}


'그렇구나, 이메일이 비어있으면 이런 에러 메시지가 나오고, 네트워크 타임아웃이 발생하면 이렇게 처리하는구나.'


신입 개발자의 가장 큰 두려움 중 하나는 "내가 코드를 수정했다가 다른 부분이 망가지면 어떡하지?"다.

테스트 코드가 있으면 이런 걱정이 줄어든다. 코드를 수정하고 테스트를 돌렸을 때 모두 통과하면, 기존 기능이 잘 작동한다는 뜻이다. 만약 뭔가 망가졌다면? 테스트가 빨간불을 켜준다.


테스트 코드 읽는 순서

테스트 코드를 처음 볼 때는 어디서부터 읽어야 할지 막막하다. 파일이 수십 개, 테스트 함수가 수백 개다. 전부 읽어야 하나? 아니다. 전략적으로 읽어 보자


1단계: 내가 작업할 기능부터

팀장님이 "로그인 버그 수정해보세요" 라고 했다면, 로그인 관련 테스트 코드부터 찾아보자.

보통 테스트 파일 이름은 실제 파일 이름과 비슷하다.


실제 코드: user_auth.py -> 테스트 코드: test_user_auth.py

실제 코드: LoginService.java -> 테스트 코드: LoginServiceTest.java

실제 코드: payment.js -> 테스트 코드: payment.test.js


파일을 열면 테스트 함수들이 쭉 나열되어 있다. 함수 이름만 봐도 대충 무슨 테스트인지 알 수 있다.

test_login_success()

test_login_wrong_password()

test_login_empty_email()

test_login_nonexistent_user()


'로그인 성공 케이스, 틀린 비밀번호 케이스, 빈 이메일 케이스, 존재하지 않는 사용자 케이스...'

이름만 읽어도 어떤 경우의 수가 있는지 파악된다.


2단계: 정상 케이스 먼저 읽기

test_login_success() 같은 정상 케이스부터 읽자. 가장 기본적인 사용 방법을 알 수 있다.


function test_login_success() {

// 테스트용 사용자 생성

user = create_user("test@example.com", "password123")

// 로그인 시도

result = login("test@example.com", "password123")

// 성공 확인

assert result.success == true

assert result.user_id == user.id

}


'아, 로그인 함수는 이메일이랑 비밀번호를 받고, success와 user_id를 리턴하는구나.'


3단계: 예외 케이스 읽기

정상 케이스를 이해했으면, 예외 케이스들을 읽어보자.


function test_login_wrong_password() {

user = create_user("test@example.com", "password123")

result = login("test@example.com", "wrong_password")

assert result.success == false

assert result.error == "비밀번호가 일치하지 않습니다"

}


'틀린 비밀번호를 입력하면 success가 false가 되고, error 필드에 메시지가 담기는구나.'


읽어도 이해가 안 되면? 직접 실행해보자. 테스트 코드는 실행 가능한 코드다. 브레이크포인트를 찍고 디버거를 돌려보면 정확히 어떤 값이 들어가고 나오는지 볼 수 있다.


테스트 실행 방법

테스트 코드를 읽는 것만으로도 많은 도움이 되지만, 직접 실행해보면 훨씬 더 잘 이해할 수 있다.


테스트 실행 명령어 찾기

회사마다, 프로젝트마다 테스트 실행 방법이 다르다. 하지만 대부분 README 파일에 나와 있다.

README에서 "Testing", "테스트", "Tests" 같은 단어를 검색해보자. 보통 이런 식으로 나와 있다.


# 테스트 실행

npm test # JavaScript/TypeScript

pytest # Python

mvn test # Java (Maven)

gradle test # Java (Gradle)

cargo test # Rust

go test ./... # Go


언어와 프레임워크마다 명령어가 다르지만, 본질은 같다. "테스트 코드를 실행해서 모든 검증이 통과하는지 확인한다."


코드를 수정했는데 테스트가 빨간불이 켜졌다. 당황스럽다. '내가 뭘 잘못했지?' 괜찮다. 테스트가 실패하는 건 정상이다. 오히려 테스트가 제 역할을 하고 있다는 뜻이다. 에러 메시지를 읽어보자. 어떤 테스트가 실패했는지, 무엇을 기대했는데 무엇이 나왔는지 나와 있다. 코드를 고치고 다시 테스트를 돌리면 초록불을 볼 수 있다.


처음 테스트를 실행할 때

터미널을 연다. 프로젝트 루트 디렉토리로 이동한다. 테스트 명령어를 입력한다.


test_login_success (2ms) (O)

test_login_wrong_password (1ms) (O)

test_login_empty_email (1ms) (O)

test_login_nonexistent_user (5ms) (X)

Tests: 1 failed, 3 passed, 4 total


'어? 하나 실패했네?'

당황하지 마라. 신입이 처음 테스트를 돌렸을 때 몇 개 실패하는 건 흔한 일이다. 환경 설정이 안 맞거나, 테스트 데이터가 없거나, 네트워크 연결이 필요한 테스트일 수 있다.


특정 테스트만 실행하기

전체 테스트를 돌리면 시간이 오래 걸릴 수 있다. 내가 작업 중인 부분만 테스트하고 싶다면?

대부분의 테스트 프레임워크는 특정 파일이나 특정 함수만 실행하는 옵션이 있다.


# 특정 파일만 실행

npm test login.test.js

pytest test_login.py


# 특정 함수만 실행

npm test -t "login success"

pytest test_login.py::test_login_success


이렇게 하면 전체 테스트를 기다릴 필요 없이, 내가 수정한 부분만 빠르게 확인할 수 있다.


신입 시절, 결제 금액 계산 로직을 수정하는 작업을 맡았다. 간단해 보였다. 코드 몇 줄만 고치면 되는 작업이었다. 코드를 수정했다. 로컬에서 돌려봤다. 잘 된다. PR을 올렸다. 그런데 CI에서 테스트가 실패했다. '어? 무료배송 쿠폰이랑 같이 쓰면 왜 배송비까지 빠지지?'


코드를 다시 봤다. 내가 수정한 부분에서 무료배송 쿠폰도 할인 금액에 포함되도록 잘못 작성했던 거다.

만약 테스트가 없었다면? 그대로 배포됐을 것이다. 실제 사용자가 무료배송 쿠폰을 썼을 때 이상한 금액이 나왔을 것이다. 고객 불만이 접수됐을 것이다. 긴급 패치를 했을 것이다.


하지만 테스트가 있었기 때문에, 배포 전에 문제를 발견했다. 코드를 고쳤다. 테스트가 통과했다. 안심하고 배포했다. 그날 깨달았다. '테스트는 내 편이구나. 내가 실수할 때 알려주는 친구구나.'


체크리스트: 테스트 코드 파악 완료

테스트 코드 읽기

테스트 폴더 위치 파악했는가?

내가 작업할 기능의 테스트 파일 찾았는가?

정상 케이스 테스트 읽었는가?

예외 케이스 테스트 읽었는가?


테스트 실행

README에서 테스트 실행 방법 찾았는가?

전체 테스트 실행해봤는가?

실패하는 테스트가 있는지 확인했는가?

실패 원인 파악했는가?



테스트 코드, 내 동료가 되어라


처음 테스트 코드를 봤을 때는 낯설었다. '왜 코드가 두 배로 있지? 이걸 다 읽어야 해?'

하지만 지금은 다르다. 테스트 코드를 먼저 읽는다. 함수를 어떻게 쓰는지, 어떤 예외가 있는지 한눈에 파악할 수 있다.


코드를 수정할 때도 테스트부터 돌린다. 초록불이 켜지면 안심이 된다. '내가 뭔가 망가뜨리지 않았구나.'

테스트가 실패하면? 당황하지 않는다. 침착하게 에러 메시지를 읽는다. 코드를 고친다. 다시 돌린다. 통과한다.


선배가 슬랙으로 메시지를 보냈다.

"테스트 코드 읽어봤어요? 이해됐나요?"

"네! 생각보다 읽기 쉽더라고요. 그리고 정말 도움이 많이 됐습니다."


선배가 답한다.

"좋아요. 그럼 내일 첫 작업 시작할게요. 기능 만들 때 테스트도 같이 작성해보세요."

'테스트도 같이 작성한다고?'


조금 떨리지만, 동시에 설렌다. 테스트 코드를 읽는 것에서 끝나지 않는다. 이제는 직접 작성하는 개발자가 될 차례다.


미리보기가 끝났습니다.


안녕하세요. 고코더 입니다.

이 책은 약 20개 챕터로 이루어져 있고, 출판사와 협의된 7챕터까지만 미리보기로 공개했습니다. 신입 개발자가 입사 첫날 이 책 한 권만 받으면, 1년 동안 스스로 성장할 수 있도록 만든 책입니다. 더 깊고 실전적인 내용은 이후 챕터에 담겨 있습니다. 나머지는 서점에서 만나길 바랍니다!



이전 29화주석과 문서 활용하기