리팩토링 전에 Playwright 테스트부터 만든 이유
운영 중인 레거시 서비스를 AI와 함께 점진적으로 리팩토링 하기
- 01운영 중인 서비스를 왜 지금 리팩토링하기로 했나
- 02왜 Spring Boot, Gradle, React Router v7로 방향을 정했나
- 03리팩토링 전에 Playwright 테스트부터 만든 이유← 현재
- 04AI가 잘 이해할 수 있는 구조를 먼저 만들었다
- 05Gradle 전환과 미사용 코드 정리부터 시작한 이유
- 06domain과 infra를 나누며 백엔드 리팩토링을 시작했다
리팩토링 전에 Playwright 테스트부터 만든 이유
리팩토링을 하겠다고 마음먹고 나서 가장 먼저 든 생각은 "어디서부터 손대야 하지?"였다. 자연스럽게 떠오르는 시작점은 백엔드 구조 정리였다. controller에 몰린 로직을 빼고, service를 정리하고, domain과 infra를 나누고, MongoTemplate을 Spring Data MongoDB로 옮기는 일들이 먼저 떠올랐다.
그런데 그 순서는 위험했다. 지금 서비스가 어떤 상태를 정상이라고 보는지조차 명확하지 않은데 구조부터 바꾸기 시작하면, 나중에 깨진 것을 고치는 일과 원인을 추적하는 일이 엉켜 버린다. 무엇이 "원래부터 이상했던 것"이고, 무엇이 "리팩토링하면서 새로 깨진 것"인지 구분하기 어려워진다.
그래서 나는 코드를 바꾸기 전에 먼저 현재 서비스의 동작을 테스트로 고정하기로 했다. 그 시작점이 Playwright였다.
문제는 테스트가 없다는 것보다, "무엇을 테스트해야 하는지"도 불분명했다는 점이었다
이번 프로젝트가 조금 더 까다로웠던 이유는 단순히 E2E 테스트가 없어서가 아니었다. 더 큰 문제는 현재 코드베이스 안에 실제로 쓰이는 코드와 더 이상 쓰이지 않는 코드가 섞여 있었다는 점이었다.
이 프로젝트는 예전에 사내 코드를 fork 떠와서 기반으로 사용했던 부분이 있었고, 그 과정에서 사용하지 않는 controller, template, static file이 꽤 많이 남아 있었다. 당시에는 빠르게 기능을 붙이는 게 더 중요했겠지만, 시간이 지나면서 이런 코드들은 점점 맥락을 흐리게 만들었다.
이 상태에서 무작정 테스트를 만들면 오히려 테스트 범위를 잘못 잡을 수 있다. 예를 들어 페이지가 실제로는 더 이상 사용되지 않는데 템플릿 파일만 남아 있다면, 그걸 굳이 회귀 테스트로 고정하는 건 좋은 선택이 아니다. 반대로 실제 사용자 흐름인데 코드상으로는 파편처럼 흩어져 있다면, 그걸 놓치면 리팩토링 안전망이 부족해진다.
그래서 테스트를 만들기 전에 먼저 해야 했던 일은 "현재 서비스가 실제로 어떤 화면으로 구성되어 있는지"를 다시 파악하는 일이었다.
왜 Playwright였나
테스트 도구 선택도 그 기준에서 결정했다.
이번에 내가 먼저 만들고 싶었던 것은 단위 테스트가 아니라 현재 사용자 흐름이 유지되는지 확인할 수 있는 회귀 테스트였다. 특히 프론트는 나중에 React Router v7 기반으로 다시 만들 계획이 있었기 때문에, 당장은 Thymeleaf 기반 화면이라도 "현재 이 페이지가 이렇게 뜬다"는 사실 자체를 먼저 고정해둘 필요가 있었다.
Playwright를 고른 이유는 명확했다.
첫째, 현재 화면을 기준으로 테스트를 만들기 좋다. 실제 브라우저에서 페이지 이동, 버튼 클릭, 폼 입력, 댓글 작성, 관리자 로그인 같은 흐름을 바로 자동화할 수 있다.
둘째, 리팩토링 전에 서비스 현재 상태를 스냅샷처럼 고정하기 좋다. 이건 단위 테스트가 잘하는 일이 아니다. 지금 필요한 것은 특정 함수의 정밀한 로직 검증보다, 사용자 관점의 흐름이 깨지는 순간을 빠르게 잡아내는 안전망이었다.
셋째, 프론트를 새로 만드는 과정에도 그대로 도움이 된다. 나중에 React Router v7 SSR 프론트를 붙일 때도, 지금 만든 Playwright 시나리오는 그대로 회귀 테스트 자산으로 남길 수 있다.
즉, Playwright는 "테스트를 위한 테스트"가 아니라, 점진적 리팩토링을 위한 기준선을 만드는 도구였다.
처음부터 모든 기능을 테스트하려고 하지는 않았다
중요한 건 범위였다. 리팩토링 전에 회귀 테스트를 만든다고 해서 처음부터 모든 기능을 다 자동화하려고 하면 오히려 시작조차 어렵다. 특히 운영 중인 서비스는 기능이 많고, 그중 일부는 외부 시스템과도 연결돼 있기 때문에, 처음부터 전부 잡으려 하면 테스트가 너무 무거워진다.
그래서 기준을 이렇게 잡았다.
- 먼저 공개 사용자 흐름을 고정한다.
- 그다음 관리자 핵심 화면을 고정한다.
- 그다음 데이터를 실제로 바꾸는 흐름은 별도 mutation 테스트로 분리한다.
- 마지막으로 인증/권한 흐름은 auth 카테고리로 분리한다.
이렇게 나눈 이유는 테스트를 실행할 때 목적이 다르기 때문이다.
- read-only 테스트는 현재 화면이 그대로 뜨는지 확인하는 용도
- mutation 테스트는 실제 저장/삭제/수정이 정상 동작하는지 확인하는 용도
- admin 테스트는 운영성 화면과 기능을 검증하는 용도
- auth 테스트는 접근 제어와 로그인 흐름을 검증하는 용도
나중에 리팩토링이 진행되더라도, 어떤 범주가 깨졌는지 바로 감을 잡을 수 있게 하고 싶었다.
실제로 어떤 흐름부터 고정했나
가장 먼저 잡은 건 공개 화면 쪽이었다.
- 홈 화면 렌더링
- 검색
- 용어 목록
- 첫 번째 게시글 상세 진입
- 새 문서 만들기 진입
- GNB 링크 이동
이 흐름들은 서비스의 기본 동선이기도 하고, 리팩토링 과정에서 가장 먼저 깨질 가능성이 높은 부분이기도 했다. 라우팅이 조금만 바뀌어도, 템플릿이 깨져도, 서버 모델 바인딩이 달라져도 쉽게 영향이 드러나는 영역이었다.
그다음에는 mutation 성격의 흐름을 추가했다.
- 댓글 작성
- 작성 페이지 필수값 검증
- 최소/최대 글자수 검증
- 유형 선택 후 AI 초안 버튼 활성화
여기서는 단순히 "버튼이 보이는가"가 아니라, 실제로 데이터가 저장되거나 에러 메시지가 정확히 뜨는지까지 확인했다. 리팩토링이 진행되면 이런 부분이 가장 조용히 깨지기 쉽기 때문이다.
마지막으로 관리자 테스트를 붙였다.
- 관리자 로그인
- 대시보드
- 사용자 관리
- 유형 관리
- 메인 카피 관리
- JSON 업로드 화면
- 벡터 색인 화면
- 일부 관리자 mutation
여기서도 중요한 건 "어디까지를 read-only로 보고, 어디부터 mutation으로 볼 것인가"를 나누는 일이었다. 예를 들어 메인 카피 저장은 mutation이지만, JSON 업로드 페이지는 화면 렌더링만 먼저 고정했다. 외부 시스템과 대량 데이터 변경이 얽히는 기능은 처음부터 전부 mutation으로 넣지 않았다.
관리자 테스트는 왜 따로 분리했나
관리자 기능은 일반 사용자 흐름과 성격이 많이 다르다. 접근 권한이 필요하고, 데이터를 실제로 바꾸는 기능이 많고, 운영 보조 기능도 섞여 있다. 이걸 공개 스모크에 같이 넣으면 테스트 실행 목적이 흐려진다.
그래서 관리자 기능은 따로 카테고리를 만들고, 나중에는 아예 로그인 세션을 한 번 만든 뒤 재사용하게 구성했다. 관리자 테스트를 돌릴 때마다 매번 로그인부터 다시 하는 것은 느리기도 하고 불필요하다. 결국 Playwright storageState를 활용해서 한 번 로그인한 상태를 재사용하는 쪽이 훨씬 안정적이었다.
이건 테스트 구조를 만드는 과정에서 개인적으로 꽤 만족스러웠던 부분이다. 단순히 "테스트가 있다"가 아니라, 실제로 반복 실행 가능한 테스트 구조를 만든 느낌이었기 때문이다.
Playwright를 만들면서 오히려 서비스 구조를 더 잘 보게 됐다
재미있었던 점은, 테스트를 만들면서 코드 구조가 더 잘 보이기 시작했다는 점이다.
어떤 버튼이 실제로 어떤 endpoint를 호출하는지, 어떤 템플릿이 아직 살아 있는지, 어떤 화면은 페이지 자체는 있지만 실제 사용 흐름에서는 거의 빠져 있는지, 이런 것들이 Playwright 시나리오를 만들면서 훨씬 또렷하게 보였다.
특히 "미사용 코드가 많다"는 막연한 느낌이, 테스트를 만들다 보니 더 구체적인 판단으로 바뀌었다. 예를 들어 실제로 접근되는 경로, 템플릿에서 연결되는 버튼, JS에서 호출하는 endpoint를 기준으로 보면, 지금 정리해도 되는 코드와 아직 남겨야 하는 코드가 조금씩 구분되기 시작했다.
결국 Playwright 테스트를 먼저 만든 일은 단순한 안전망 확보를 넘어서, 현재 서비스의 살아 있는 범위를 다시 이해하는 과정이기도 했다.
테스트를 먼저 만든 것이 결과적으로 리팩토링 순서를 정해줬다
이 시점에서 가장 크게 느낀 점은, 테스트를 먼저 만들면 리팩토링 순서가 자연스럽게 드러난다는 것이다.
예를 들어 공개 스모크가 먼저 잡혀 있으면, 홈·검색·상세·작성 흐름을 깨뜨리지 않는 범위 안에서 백엔드를 건드리게 된다. 관리자 테스트가 있으면 운영 페이지를 함부로 정리하지 않게 된다. mutation 테스트가 있으면 저장/수정 로직을 바꿀 때 더 신중해진다.
즉 테스트는 단순히 나중에 깨졌는지 확인하는 도구가 아니라, 어떤 순서로 바꿔야 안전한지 알려주는 장치이기도 했다.
그리고 이건 내가 이번 리팩토링에서 초반에 내린 가장 중요한 판단 중 하나였다고 생각한다. 코드를 먼저 바꾸지 않고, 현재 동작을 먼저 고정한 것. 결과적으로 이 덕분에 이후의 구조 정리도 훨씬 덜 불안하게 진행할 수 있었다.
마무리
리팩토링 전에 Playwright 테스트부터 만든 이유는 단순하다. 지금 서비스가 무엇을 정상이라고 보는지 먼저 알아야 했기 때문이다. 운영 중인 서비스에서는 구조 개선보다 먼저, "현재 이 동작이 살아 있어야 한다"는 기준을 만들어두는 일이 중요했다.
특히 이번 프로젝트처럼 fork 떠온 코드 안에 미사용 코드가 많이 남아 있는 경우에는 더 그랬다. 테스트를 만들기 위해 현재 화면과 흐름을 다시 추적하는 과정 자체가, 이미 리팩토링의 일부였다.
다음 글에서는 왜 AI가 잘 이해할 수 있는 구조를 먼저 만들기로 했는지, 그리고 왜 /backend, /frontend와 멀티모듈 구조를 초기부터 잡아두는 것이 중요했는지를 정리해보려고 한다.