외주 SI 프로젝트에서 하루에 세 번 같은 일을 겪었습니다. 테스트가 잘못된 코드를 지키고 있었습니다.
1. 없어진 컬럼을 지키는 테스트
테이블 정의가 바뀌면서 컬럼 하나가 사라졌습니다. 그 컬럼을 쓰던 SQL은 실행하면 바로 죽습니다.
ORA-00904: "REG_IP": invalid identifier
그런데 테스트는 초록불이었습니다. 단언이 이렇게 돼 있었기 때문입니다.
String sql = mapper.buildInsertSql();
assertTrue(sql.contains("REG_IP"));
이 테스트는 DB에 가지 않습니다. 문자열에 낱말이 들어 있는지만 봅니다. 컬럼이 없어져도, 오히려 없어졌기 때문에 더 확실하게 통과합니다.
즉 이 테스트는 정확히 고장난 상태를 지키는 자물쇠였습니다.
2. 고치는 길을 막고 선 테스트
느린 쿼리를 IN 절에서 EXISTS 로 바꾸려 했습니다. 성능 차이가 큰 변경이었습니다.
바꿨더니 테스트가 빨간불이 됐습니다.
assertTrue(sql.contains(" IN ("));
결과가 같은지를 보는 게 아니라 IN 이라는 낱말이 남아 있는지를 보고 있었습니다.
이런 테스트가 있으면 사람은 대체로 이렇게 판단합니다. "테스트가 깨지네, 위험한 변경인가 보다." 그래서 안 고칩니다. 테스트가 개선을 막은 겁니다.
3. 정상 화면을 빨간불로 만드는 테스트
이건 제가 쓴 테스트입니다. 업로드 화면을 검증하면서 이렇게 단언했습니다.
assertEquals("2026-01-05", result.getStartDate());
실패했습니다. 실제 값은 2026-01-01 이었습니다.
버그를 찾은 줄 알고 한참 파다가, 서버가 월 단위 업무라 시작일을 그 달 1일로 보정하고 있다는 걸 알았습니다. 화면은 정상이었고 제 테스트만 틀렸습니다.
고칠 뻔했습니다. 정상 동작을요.
공통점
세 개가 겉보기엔 다르지만 원인이 같습니다.
단언이 "무엇을 뜻하는지"가 아니라 "어떻게 생겼는지"를 박아뒀습니다.
SQL 문자열, 낱말 하나, 화면에 뜨는 글자 — 전부 모양입니다. 모양을 박아두면 모양이 바뀔 때마다 깨지고, 뜻이 깨질 때는 안 깨집니다. 정확히 거꾸로입니다.
그래서 이렇게 바꿨습니다
모양이 아니라 결과를 본다
// 전 — SQL 문자열에 낱말이 있는지
assertTrue(sql.contains("REG_IP"));
// 후 — 실제로 넣고 실제로 꺼내 본다
repository.insert(row);
assertEquals("10.0.0.1", repository.findById(id).getRegIp());
이러면 컬럼이 없어지는 순간 테스트가 먼저 죽습니다. 그게 테스트가 할 일입니다.
단언은 서버가 받아들이는 범위로
서버가 값을 보정한다면, 그 보정 후 값이 정답입니다. 내가 넣은 값이 그대로 나올 거라고 가정하면 안 됩니다.
왜 이 단언인지를 적는다
// 월 단위 집계라 서버가 시작일을 그 달 1일로 맞춘다.
// 이 규칙이 없어지면 이 단언도 같이 없애야 한다.
assertEquals("2026-01-01", result.getStartDate());
테스트를 다음에 보는 사람은 왜 이 값인지를 모릅니다. 그걸 안 적으면, 실패했을 때 그냥 기대값을 바꿔서 초록불로 만들어 버립니다.
가져갈 것
- 문자열
contains단언은 테스트가 아닙니다. 코드 검색입니다. - 초록불이 많다고 안전한 게 아닙니다. 초록불이 무엇을 지키고 있는지가 전부입니다.
- 리팩터링을 막는 테스트는 버그입니다. 고치세요.
- 테스트가 실패하면 코드부터 의심하기 전에 테스트를 한 번 읽으세요. 셋 중 하나는 테스트가 틀립니다.
테스트를 늘리는 건 쉽습니다. 어려운 건 틀린 테스트를 지우는 것입니다. 지우면 커버리지가 떨어지니까요. 그래도 지우는 게 맞습니다.
만든 것들
'Coding, Testing, Challenge' 카테고리의 다른 글
| 토큰을 숨기는 명령어로 토큰을 유출했습니다 (0) | 2026.09.08 |
|---|