Showing Posts From
야근하며
- 25 Dec, 2025
야근하며 수정한 테스트: 내일 아침 더 꼬여 있다
오후 6시, 급한 불 배포가 내일이다. PM이 슬랙에 멘션을 날렸다. "J님, 새 결제 기능 자동화 테스트 내일 아침까지 가능할까요?" 가능하냐고? 불가능하다. 하지만 대답은 정해져 있다. "네, 해보겠습니다." 정상적으로 하면 이틀 걸린다. API 스펙 확인하고, 테스트 시나리오 짜고, Page Object 설계하고, 스크립트 작성하고, 리뷰받고. 그게 정석이다. 지금 시간은 6시 반. 퇴근 시간 지났다. 옆자리 개발자들 하나둘 나간다. "먼저 갈게요." "네, 수고하세요." 모니터 3개를 켰다. 왼쪽엔 API 문서, 가운데엔 IDE, 오른쪽엔 테스트 실행 화면. 커피 한 잔 더 탔다. 네 번째다.급하게 짜는 코드의 특징 8시다. 일단 돌아가는 걸 만들었다. def test_payment(): driver.get("https://...") driver.find_element_by_id("btn_pay").click() time.sleep(5) # 일단 5초 기다림 assert "success" in driver.page_source보기 싫다. 하드코딩 범벅이다. sleep으로 때웠다. Page Object도 없다. 그냥 element 직접 찾는다. 하지만 돌아간다. 지금은 그게 중요하다. 다음 시나리오. 카드 결제 실패 케이스. def test_payment_fail(): driver.get("https://...") driver.find_element_by_id("card_num").send_keys("1234") # 잘못된 카드 driver.find_element_by_id("btn_pay").click() time.sleep(5) assert "실패" in driver.page_source복붙했다. 앞에 코드 거의 똑같다. URL도 하드코딩, 셀렉터도 하드코딩. fixture도 안 만들었다. "나중에 리팩토링하면 되지." 이 말을 몇 번 했는지 모른다. 경력 7년차가 이러면 안 되는데. 9시 반. 테스트 케이스 5개 짰다. 전부 이런 식이다. 코드 중복 천지. 하드코딩 천국. sleep 축제. 돌려봤다. 3개 성공, 2개 실패. 실패한 이유를 봤다. 타이밍 이슈다. sleep(5)가 부족했다. 10으로 늘렸다. 다시 돌렸다. 4개 성공, 1개 실패. 또 타이밍이다. sleep(15)로 늘렸다. 전부 통과했다. "좋아, 일단 된다." 커밋했다. 메시지는 "add payment test cases". 상세 설명 없다. 급하니까.다음날 아침의 충격 출근했다. 10시 10분. 늦었다. 어젯밤 12시에 퇴근했다. 슬랙을 켰다. 알림 7개. 전부 Jenkins 실패 알림이다. "어? 어제 다 통과했는데?" CI 로그를 봤다. 전부 타임아웃이다. selenium.common.exceptions.TimeoutException Element not found: btn_pay아, 맞다. 하드코딩했지. 개발자가 어젯밤에 버튼 ID를 바꿨다. btn_pay에서 button_payment로. 슬랙에 개발자 메시지가 있다. "결제 버튼 컴포넌트 리팩토링했습니다. UI 변경 없어요." UI는 안 바뀌었다. 하지만 ID는 바뀌었다. 그걸 하드코딩했으니 깨진다. 수정했다. 5군데. 다시 푸시. 다시 실행. 또 실패. 이번엔 다른 에러. AssertionError: 'success' not found in page source페이지 구조가 바뀌었다. 성공 메시지가 모달로 뜬다. page_source엔 안 보인다. 수정했다. explicit wait 추가. 모달 찾는 로직 추가. 그런데 다른 테스트도 깨졌다. 전부 같은 패턴으로 짰으니까. 10시 반. 벌써 30분 날렸다. PM이 슬랙에 물었다. "J님 테스트 통과했나요? 배포 11시예요." "조금만요, 거의 다 됐습니다." 거짓말이다. 아직 멀었다.기술 부채의 이자 점심시간이다. 배포는 연기됐다. 내 때문이다. 테스트는 겨우 통과시켰다. 하지만 더 꼬였다. 어젯밤 코드 위에 오늘 아침 땜질을 했다. 나쁜 코드 위에 더 나쁜 코드. def test_payment(): driver.get("https://...") time.sleep(3) # 페이지 로딩 btn = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "button_payment")) ) btn.click() time.sleep(5) # 결제 처리 try: modal = driver.find_element_by_class_name("modal_success") assert "success" in modal.text except: assert "success" in driver.page_source # fallbacksleep이랑 explicit wait이 섞였다. try-except로 땜질했다. fallback 로직도 이상하다. 이게 5개 테스트에 복붙되어 있다. 팀장이 코드 리뷰를 달았다. "J님, 이거 Page Object 패턴 안 쓴 이유가 있나요?" "급해서 일단 돌아가게 만들었습니다. 리팩토링할게요." "배포 끝나고 꼭 해주세요. 이렇게 가면 유지보수 힘듭니다." 안다. 나도 안다. 하지만 리팩토링할 시간이 있을까? 다음주에 또 급한 기능이 온다. 또 급하게 짤 것이다. 또 부채가 쌓일 것이다. 악순환의 시작 2주 지났다. 결제 테스트는 그대로다. 리팩토링 할 시간이 없었다. 새 기능 테스트 짜느라. 그것도 급하게 짰다. 같은 패턴으로. 이제 급한 코드가 10개 파일이다. CI 실행시간이 20분에서 35분으로 늘었다. sleep 때문이다. Flaky 테스트가 생겼다. 타이밍 이슈로 가끔 실패한다. 개발자들이 불평한다. "테스트 왜 이렇게 느려요?" "가끔 이유 없이 실패하던데, 테스트 문제 아닌가요?" 변명할 수 없다. 내 코드가 문제다. 새로 온 QA 후배가 내 코드를 봤다. "선배님, 이 테스트 왜 이렇게 짜셨어요? Page Object 안 쓰셨네요?" "응, 급해서... 나중에 고칠 거야." "그럼 제가 새로 짤 테스트도 이렇게 짜면 돼요?" "아니, 너는 제대로 짜. 이건... 나쁜 예시야." 후배한테 나쁜 예시를 보여주고 있다. 부채의 이자는 복리다 한 달 지났다. 급한 코드가 20개 파일이 됐다. 이제 신규 기능 하나 추가하려면 기존 코드를 봐야 한다. 복붙할 게 많으니까. 그러다 나쁜 패턴도 같이 복붙된다. API가 바뀌면 20군데를 고쳐야 한다. 하드코딩했으니까. UI 컴포넌트가 바뀌면 테스트 10개가 깨진다. 셀렉터를 직접 박았으니까. CI 실행시간은 50분이다. sleep 총합이 5분이 넘는다. 테스트 커버리지는 올랐다. 65%. 좋아 보인다. 하지만 품질은 떨어졌다. 테스트를 믿을 수 없다. 개발자들이 테스트 실패를 무시한다. "저 테스트 원래 잘 깨지잖아요. 그냥 재실행하면 돼요." 틀린 말이 아니다. PM이 물었다. "테스트 자동화 많이 했는데, 버그는 왜 여전히 나와요?" 대답할 수 없었다. 리팩토링의 두려움 주말이다. 집에서 쉬고 있다. 리팩토링을 해야 한다는 생각이 머리를 떠나지 않는다. 계획을 세웠다.Page Object 패턴 적용 공통 fixture 만들기 sleep 전부 explicit wait로 교체 하드코딩된 셀렉터 전부 상수로 분리 중복 코드 제거최소 3일은 걸린다. 풀타임으로. 하지만 3일을 쓸 수 있을까? 그 사이에 버그가 나면? 긴급 패치가 들어가면? 새 기능이 또 급하게 오면? 리팩토링 도중에 기존 테스트는 돌아가지 않는다. CI가 깨진다. 팀장한테 말해야 한다. "3일간 다른 일 못 합니다. 리팩토링합니다." 승인이 날까? 더 큰 두려움이 있다. 리팩토링 하다가 기존 테스트를 깨뜨리면 어떡하지? 지금 코드가 나쁘지만, 어쨌든 돌아간다. 고치다가 더 망가뜨리면? "그냥 새로 짜는 게 나을까?" 하지만 새로 짜는 것도 3일이다. 똑같다. 결국 아무것도 안 했다. 월요일이 됐다. 작은 리팩토링의 시작 월요일 오전. 회의가 없다. 결심했다. 전체는 못 해도 일부는 한다. 가장 자주 깨지는 테스트 파일 하나만. test_payment.py. 2시간 걸렸다. Page Object 만들었다. class PaymentPage: BTN_PAYMENT = (By.ID, "button_payment") MODAL_SUCCESS = (By.CLASS_NAME, "modal_success") def __init__(self, driver): self.driver = driver def click_payment(self): element = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable(self.BTN_PAYMENT) ) element.click() def get_result_message(self): modal = WebDriverWait(self.driver, 10).until( EC.visibility_of_element_located(self.MODAL_SUCCESS) ) return modal.text테스트 코드가 깔끔해졌다. def test_payment_success(payment_page): payment_page.click_payment() result = payment_page.get_result_message() assert "success" in resultsleep이 사라졌다. 중복도 사라졌다. 읽기 쉽다. 돌려봤다. 통과했다. CI에도 통과했다. 실행 시간이 3분 줄었다. 기분이 좋다. 오후에 또 다른 파일 하나. test_cart.py. 같은 방식으로. 2시간 반 걸렸다. 이번엔 조금 빨랐다. 패턴을 알았으니까. 하루에 파일 2개씩 리팩토링한다. 10일이면 20개 끝난다. 점진적 개선 2주 지났다. 급한 코드의 절반을 고쳤다. 완벽하지 않다. 아직 개선할 부분이 많다. 하지만 확실히 나아졌다. CI 실행시간이 35분으로 줄었다. Flaky 테스트가 5개에서 2개로 줄었다. 코드 리뷰에서 지적받는 횟수가 줄었다. 후배가 말했다. "선배님, 이번 코드는 이해하기 쉬워요. 이렇게 짜면 되는 거죠?" "응, 이게 맞아." 개발자가 슬랙에 썼다. "테스트 빨라졌네요. 뭐 하셨어요?" "리팩토링 좀 했습니다." "좋네요. ㅎㅎ" 작은 칭찬이지만 기분 좋다. 팀장이 1on1에서 말했다. "테스트 코드 품질 올라간 거 보여요. 꾸준히 하고 있죠?" "네, 조금씩 하고 있습니다." "급하다고 대충 짜는 거, 나도 이해해요. 하지만 결국 돌아오더라고요." "맞아요. 배웠습니다." "이번 분기 평가에 반영할게요. 수고하고 있어요." 배운 것들 급한 코드는 빚이다. 언젠가 갚아야 한다. 이자는 복리다. 급한 코드 위에 급한 코드가 쌓인다. 한 번에 갚을 순 없다. 파산한다. 조금씩 갚는 수밖에 없다. "나중에 리팩토링하면 돼"는 거짓말이다. 나중은 안 온다. 더 급한 일만 온다. 그래서 매일 조금씩 해야 한다. 출근해서 1시간. 파일 하나. 함수 몇 개. 그럼 부채가 줄어든다. 천천히지만 확실하게. 완벽한 코드를 짤 순 없다. 급할 때도 있다. 하지만 부채를 인정하고 관리해야 한다. 급하게 짰으면 주석을 남긴다. "TODO: refactor to Page Object". 급하게 짰으면 이슈를 만든다. "기술 부채: test_payment 리팩토링". 급하게 짰으면 다음날 1시간 투자한다. 조금이라도 개선한다. 그게 쌓이면 달라진다. 지금 내 코드는 한 달 전보다 낫다. 완벽하지 않다. 아직 급한 코드가 10개 남았다. 하지만 매일 조금씩 나아진다. 그걸로 충분하다.어젯밤 야근해서 짠 코드. 오늘 아침엔 더 꼬여 있다. 그게 부채의 이자다. 하지만 매일 조금씩 갚으면, 언젠가 0이 된다. 그걸 믿고 오늘도 리팩토링 한다.