Showing Posts From

돌린

밤새 돌린 테스트가 새벽 3시에 깨졌을 때

밤새 돌린 테스트가 새벽 3시에 깨졌을 때

새벽 3시 알람, 그리고 깨진 파이프라인 어제 저녁 8시에 큰 기능 PR을 머지했다. 전체 테스트 스위트를 도는 건 보통 4시간 걸린다. 자동화 엔지니어라면 알 거다. 밤샘 CI는 숙명이다. 침대에 누웠는데 핸드폰이 울렸다. Jenkins 알람이다. 새벽 3시 정각. 빌드는 FAILED. 화면을 봤을 때 느낌이 왔다. 이건 단순한 실패가 아니다. 뭔가 깊은 거다. 일어나 앉았다. 잠은 포기했다. 아무도 지금 연락할 수 없다. 개발팀은 자고 있고, 나만 깨어 있다. 자동화의 밤은 혼자 버티는 거다.노트북을 켰다. 모니터 세 개를 돌렸다. 한쪽에는 Jenkins 로그, 한쪽에는 테스트 리포트, 한쪽에는 IDE를 띄웠다. 새벽 3시의 나는 이미 자동화 엔지니어가 아니라 범죄 수사관이다. 파이프라인 사건의 현장 FAILED: test_checkout_with_coupon FAILED: test_user_profile_update FAILED: test_notification_badge_count ERROR: Connection timeout at step_wait_for_element보자. 셀레늄 테스트 세 개가 떴다. 패턴이 있다. 모두 사용자 액션이 들어가는 테스트들이다. 그리고 마지막 줄. Connection timeout. 이게 핵심이다. CI 환경을 봤다. 새벽에도 파이프라인은 도는데, 리소스 사용량이 이상했다. 다른 배치 작업이랑 겹쳤나보다. 메모리 70%, CPU 50%. 정상은 아니다. 여기서 대부분의 자동화 엔지니어가 실수한다. "아, 환경 문제구나" 하고 재실행한다. 그리고 다음날 또 깨진다. 반복한다. 나는 다르게 생각했다. 환경 문제가 맞으면, 이 테스트는 프로덕션에서도 깨질 가능성이 높다. 즉 내 테스트가 너무 민감하다는 뜻이다.그래서 코드를 뜯어봤다. wait_for_element(locator, timeout=10) 10초다. 10초 wait는 신뢰할 수 없다. CI 환경이 느릴 땐 평정심이 필요하다. 내가 쓴 코드를 봤다. def wait_for_element(self, locator, timeout=10): try: WebDriverWait(self.driver, timeout).until( EC.presence_of_element_located(locator) ) except TimeoutException: raise AssertionError(f"Element not found: {locator}")이 코드의 문제는 뭔가? Retry 로직이 없다. 한 번 깨지면 끝이다. Flaky 테스트의 정의다. 새벽 3시, 나는 이 코드를 고쳤다. def wait_for_element(self, locator, timeout=10, retries=3): for attempt in range(retries): try: WebDriverWait(self.driver, timeout).until( EC.presence_of_element_located(locator) ) return True except TimeoutException: if attempt == retries - 1: raise AssertionError(f"Element not found after {retries} attempts: {locator}") time.sleep(2) # 2초 대기 후 재시도재시도 로직을 넣었다. 3번까지 시도한다. 2초 간격으로. 그 다음은 뭔가? 테스트 순서다. 새벽 파이프라인에선 병렬 실행도 있다. 리소스가 줄어드니까 어떤 테스트가 먼저 끝날지 모른다. 그럼 상태 관리가 문제가 된다. @pytest.fixture(autouse=True) def setup_and_teardown(): # 각 테스트마다 클린 상태에서 시작 driver.delete_all_cookies() driver.execute_script("window.sessionStorage.clear();") yield # 테스트 후 정리 if driver: driver.quit()이건 이미 있었다. 좋다. 그럼 다음은? 왜 3시에 깨졌는가 파이프라인을 분석했다. 같은 시간대에 돌고 있던 게 뭐였나?01:00 - 앱 빌드 (30분) 01:30 - 데이터베이스 마이그레이션 테스트 (90분) 02:10 - 내 자동화 테스트 (4시간)아. DB 마이그레이션이 끝나는 게 2시간 40분 즈음이다. 거기서 뭔가 리소스를 남기고 있었나? 로그를 더 들었다. [02:47] Database migration completed [02:48] Automation tests started [02:49] Chrome driver spawned (port 4444) [03:02] Connection pool exhausted아하. 크롬 인스턴스가 정상 종료가 안 되고 있었다. 이전 빌드에서 고아 프로세스가 남아있었다. 메모리를 점점 먹고 있었고, 결국 3시쯤에 터진 거다.이건 내 코드 문제가 아니다. CI 환경 설정 문제다. 하지만 내 책임이다. 왜냐면 내가 테스트를 견고하게 만들지 못했으니까. 새벽 4시, 나는 두 가지를 했다. 1. Dockerfile 수정 # 이전 RUN apt-get install -y chromium-browser# 이후 RUN apt-get install -y chromium-browser RUN echo "pkill -f chrome || true" > /cleanup.shCI 시작 전에 고아 프로세스를 모두 정리하는 스크립트를 넣었다. 2. 테스트 타임아웃 늘림 @pytest.mark.timeout(300) # 5분으로 늘림 def test_checkout_with_coupon(): ...느린 CI 환경을 고려했다. 10초는 너무 짧다. 3. 로그 레벨 상향 logging.basicConfig(level=logging.DEBUG) logger.debug(f"Waiting for {locator}, timeout={timeout}") logger.debug(f"Attempt {attempt+1}/{retries}")다음에 같은 일이 생기면 더 빨리 디버깅할 수 있게. 아침 6시, 결론 다시 실행했다. 이번엔 통과했다. 모든 테스트. 파이프라인이 초록색이 됐다. 아침 9시, 팀 미팅에서 뭐라고 할까 고민했다. 아무것도 안 하기로 했다. 개발팀에게 "야간 파이프라인에 환경 문제가 있었어요"라고 하면 뭐 하냐. 나 혼자 밤새 고칠 수 있는 거다. 대신 테스트 리포트에는 이렇게 적었다. [FIX] Improved wait_for_element stability - Added retry mechanism (3 attempts, 2s interval) - Extended timeout for CI environment - Added debug logging for failed attempts[INFRA] Cleaned up Docker initialization - Added pre-cleanup for orphaned Chrome processes - Improved resource allocation개발팀은 읽지 않을 거다. 하지만 다음 누군가 밤샘할 때 필요한 정보다. 자동화 엔지니어라는 게 이런 거다. 야간 파이프라인의 짐을 혼자 지는 거. 그 대신 그 파이프라인이 믿을 수 있는 파이프라인이 되는 거. 새벽 3시 깨진 테스트는 나한테 뭘 줬나? 2시간의 수면 부채? 아니다. 안정적인 자동화 프레임워크의 법칙 하나를 줬다. 테스트는 최악의 환경을 고려해서 만들어야 한다. 왜냐면 CI 환경은 항상 최악이니까. 오늘따라 커피가 여섯 잔이다.밤샘은 버티는 거지만, 버티는 방법을 배우는 게 진짜 자동화 엔지니어다.[IMAGE_1] [IMAGE_2] [IMAGE_3]