리팩토링 vs 개선하기, 그리고 테스트 커버리지
리팩토링과 개선하기의 차이를 정의하고, 코드 커버리지 지표를 활용한 안전한 리팩토링 방법 정리
읽어보면 좋을 글
리팩터링 가이드 - 좋은 코딩 습관을 기르자 | 비브로스 기술 블로그
달리는 기차의 바퀴 교체하기 2. Restructuring
코드와 함께 살펴보는 프론트엔드 단위 테스트 – Part 1. 이론 편 | 우아한형제들 기술블로그
근본적인 요구사항 정의부터
리팩토링 ≠ 성능 개선, 기능 추가
진짜 SW 공학에서 이야기하는 리팩토링을 해볼 지, 아니면 추가적인 요구사항과 더불어 우리가 분석한대로 개선하기를 해볼지 정하는게 좋을 것 같다
리팩토링
- 기능 변화 없이 내부 구조만 개선하는 작업
- 외부 동작은 동일, 내부 품질을 올리는 것에 초점
- 유지보수성, 가독성, 확장성, 테스트 용이성을 위한 작업
입력 = 출력이 그대로여야 하고, 테스트 결과가 완전히 동일해야 한다.
개선하기
- 기능을 추가하거나 안정성을 향상하는 등 로직 자체를 수정하는 작업
- 외부 동작이 바뀔 수 있음
- 품질·성능·기능 측면의 결과를 만들어내는 작업
- 입력 ≠ 출력, 테스트 결과는 달라질 수 있다.
성능 향상, UX 변경, 기능 추가/수정 등등
| 구분 | 리팩토링 | 개선하기 |
|---|---|---|
| 목적 | 코드 품질 향상 (dirty → clean 이랄까) | 기능 추가, 성능 개선, 안정성 향상 |
| 외부 동작 | 절대 바뀌면 안 됨 (정의 상) | 바뀌어도 되고 바뀔수밖에 없다 |
| 초점 | 내부 구조 | 사용자 입장에서의 결과 |
| 예를 들면 | 함수 분리, <br/>변수명/함수명 수정, <br/>해체할당, <br/>매개변수 객체화 등등 | API 응답 속도 120ms → 20ms로 개선, <br/>로직을 더 안전하게 변경, <br/> UI에서 더 빠르게 동작하도록 로직 변경 |
리팩토링을 하려면, 현재 소스에 대한 테스트코드가 잘 작성되어 있어야 한다.
- “리팩토링 전에도 통과”
- “리팩토링 후에도 통과”
테스트코드가 없다면?
“이전과 동일하게 동작하는 코드입니다” 라고 말할 근거가 없는게 아닐까?
그래서 테스트코드를 작성하긴 해야겠는데, 엉망진창 얼렁뚱땅 테스트코드일수도 있지 않나?
신뢰 가능한 테스트코드임을 확인할 지표가 있나?
테스트 코드 측정 가능 지표
코드 커버리지
- 테스트를 돌렸을 때, 내 코드 중 실제로 실행된 부분이 전체 코드에서 몇 %냐
- 테스트를 돌렸을 때, 중요한 분기/예외 처리 부분을 모두 거치며 테스트하고 있는가?
→ 그래서 어느 부분이 테스트되고 있고 어느 부분이 테스트되지 않았는지 를 확인하는 지표가 코드 커버리지다!
- 라인(line) 커버리지 = 구문(Statement) 커버리지
- 실행 가능한 코드 라인 중에서 테스트 실행 중 적어도 한 번이라도 실행된 라인의 비율
- 분기(branch) 커버리지
if,switch,삼항 연산자,&&,||등의 조건 분기에서 각 분기(참/거짓)가 한 번 이상 실행됐는지
- 함수(Funcs) 커버리지
- 정의된 함수들 중, 테스트 중에 한 번이라도 호출된 함수의 비율
- Jest 로 커버리지 확인해보기
"test:coverage": "jest --coverage"
npm run test:coverage

- 그래서 커버리지 라는 지표를 어떻게 활용하냐?
- 리팩토링 전
- 모든 테스트코드를 실행한 결과가 전부 PASS 여야 한다.
- 커버리지 도구(Jest + coverage, JaCoCo, Istanbul, ...)로 현재 커버리지가 몇 %인지 기록해둔다.
- 리팩토링 후
- 일단 모든 테스트코드를 실행한 결과가 이전과 동일하게 전부 PASS 여야 하고,
- 커버리지가 유지 or 상승했는지 결과를 측정한다.
- 리팩토링 전
결론!
- 커버리지 수치가 70%정도 나오도록 테스트코드를 작성하고 리팩토링을 시작한다.
- 리팩토링을 진행한다.
- 리팩토링 완료 후 동일한 테스트코드를 실행해보고, 이전과 동일하게 통과됨을 확인한다.
- 커버리지 수치는 유지될수도, 상승했을수도 있는데, 떨어지지만 않으면 될 것 같다.
그래서 어떻게 리팩토링하면 좋을까?
크롱님이 제시한 리팩토링 코드에서, 기존 코드 대비 로직이 너무 깔끔해져서 감탄했다…. 구조 분해 할당이랑 validations 배열이 진짜 킥이다
if (registerForm && errorContainer) {
const { value: userId } = registerForm.querySelector("#userId") || {};
const { value: password } = registerForm.querySelector("#password") || {};
const { value: nickname } = registerForm.querySelector("#nickname") || {};
const errorMessages = {
userId: "아이디를 입력해주세요.",
password: "비밀번호를 입력해주세요.",
nickname: "닉네임을 입력해주세요.",
passwordShort: "비밀번호는 8자리 이상이어야 합니다.",
};
const validations = [
[!userId, "userId"],
[!password, "password"],
[!nickname, "nickname"],
[password && password.length < 8, "passwordShort"],
];
for (const [condition, key] of validations) {
if (condition) {
errorContainer.textContent = errorMessages[key];
return;
}
}
errorContainer.textContent = "";
registerForm.submit();
}
AI로 작성하는 테스트코드는?
지금까지는 테스트 코드를 직접 작성해본 경험이 거의 없고, AI에게 ‘이거 테스트코드 작성해줘’ 라고 해왔음
근데 마스터님들이 항상 “할 줄 아는건 AI 시키고, 할 줄 모르는건 직접 해보는게 좋아요” 라고 하심
그럼 난 테스트코드를 제대로 작성할 줄 모르고, 좋은 테스트코드가 뭔지도 잘 모르는데 무작정 AI에게 시키는건 ? → 전혀 좋지 않은 행동같다
🍀이번주에 다함께 해보면서 학습/성장 해보면 좋을 것 같은 포인트!🍀
- 테스트코드 작성 방법 학습하기 (만만한 jest 기준으로)
- 커버리지에 대해 학습하기 + 다른 좋은 학습거리가 있다면 얼마든지 좋아요!
- 직접 테스트코드 몇개 작성해보고 커버리지 측정해보기
- 기존 로직을 정말 간단한 부분이더라도 일부 리팩토링 해보고 테스트 결과 다시 측정해보기
- 리팩토링 전/후를 비교분석해보기
- 어떤 파일을 어떻게 리팩토링할지는 중요하지 않은 것 같다.
- 다른 사람이 작성한 코드에 대해 테스트코드를 스스로 짜보는 경험 + 다른 사람이 작성한 코드를 리팩토링해보는 경험 이 중요한 것 같다.
- 그리고 커버리지같은 지표를 서로 공유하며, 잘 된 리팩토링인지 이야기해보는 경험
- 지금까지 3주간 백로그/태스크 도출 + 협업 + 기능 구현을 경험했으니, 이번에도 새로운 기능을 개발하기 위해 같은 프로세스의 작업을 하기보다는 이전에는 해보지 못했던 새로운 것들을 경험해보면 좋을 것 같다.