Showing Posts From

매뉴얼

매뉴얼 QA 후배에게 Selenium 가르치다 깨달은 것

매뉴얼 QA 후배에게 Selenium 가르치다 깨달은 것

매뉴얼 QA 후배에게 Selenium 가르치다 깨달은 것 시작은 HR 전화 "J님, 신입 한 명 들어와요. 자동화 가르쳐주세요." 3년 매뉴얼 QA 경력자. 이름은 민지. 28살. 나는 고민했다. 뭘 먼저 가르치지. Python? HTML? Git? 결론은 "일단 Selenium 돌려보자"였다. 첫날, 내 자동화 프레임워크 보여줬다. 민지 표정이 굳었다. "선배, 이게 다 뭐예요?" Page Object Model. Config 파일. Fixture. Decorator. 설명하는데 1시간. 민지는 계속 끄덕였다. 하지만 눈이 멍했다. 그때 깨달았다. 내 코드가 생각보다 복잡하다. 첫 번째 질문: "왜 find_element 안 써요?" 민지 첫 과제. 로그인 테스트 스크립트 짜기. Selenium 공식 문서 보고 짰다. 코드 리뷰 요청 왔다. driver.find_element(By.ID, "username").send_keys("test") driver.find_element(By.ID, "password").send_keys("1234") driver.find_element(By.XPATH, "//button[@type='submit']").click()내가 짠 코드는 이랬다. self.login_page.enter_username("test") self.login_page.enter_password("1234") self.login_page.click_login_button()민지가 물었다. "선배 코드엔 find_element가 없는데요?" 나는 설명했다. Page Object Model. Locator 추상화. 유지보수성. 민지는 또 끄덕였다. 근데 다음 날 코드는 여전히 find_element 투성이. 화가 나려다 멈췄다. 민지가 이해 못 한 게 아니다. 내가 "왜"를 안 알려줬다.엘리먼트가 안 잡힐 때 민지 두 번째 과제. 검색 기능 테스트. 2시간 뒤 민지가 왔다. "선배, 이거 계속 에러나요." NoSuchElementException 나는 물었다. "wait 넣었어?" "wait요?" WebDriverWait. Explicit Wait. Implicit Wait. 설명했다. 민지는 코드에 time.sleep(3) 박았다. "아니 그게 아니라..." 나는 다시 설명했다. sleep은 나쁜 습관. 테스트 느려짐. Flaky 테스트 원인. 민지가 물었다. "그럼 왜 선배 코드엔 wait이 안 보여요?" 내 Base Page 클래스 열어봤다. 모든 메소드에 wait 내장. def _wait_and_find(self, locator, timeout=10): return WebDriverWait(self.driver, timeout).until( EC.presence_of_element_located(locator) )민지는 내 코드만 봤으니 wait을 몰랐다. 나는 당연하다고 생각한 것들. 민지에겐 보이지 않았다. 프레임워크 안에 숨어있었으니까. 세 번째 질문: "이건 왜 깨져요?" 민지 자동화 스크립트 10개 짰다. CI에 올렸다. 다음 날 아침. Jenkins 빨간불. 민지 테스트 5개 실패. 민지가 당황했다. "제 컴퓨터에선 됐는데요?" Headless 모드. 크롬 버전. 타임아웃 설정. 환경변수. 나는 하나씩 체크했다. 민지는 옆에서 봤다. "선배, 이런 거 어떻게 다 알아요?" "다 겪어봐서." 실패 원인은 타임아웃이었다. CI 서버가 느렸다. 민지 코드는 타임아웃 3초 하드코딩. 내 코드는 환경변수로 관리. TIMEOUT = os.getenv('TEST_TIMEOUT', 10)민지가 물었다. "왜 이렇게 해요?" "CI는 로컬보다 느리거든." "그럼 전부 이렇게 해야 돼요?" "응." 민지 표정이 어두워졌다. "자동화 어렵네요." 나도 그랬다고 말해줬다. 4년 전 나도 헤맸다고.내 코드 다시 보기 민지 질문이 계속됐다. "왜 fixture를 이렇게 써요?" "conftest.py는 뭐예요?" "이 decorator는 왜 만든 거예요?" 질문마다 내 코드 다시 봤다. 4년간 쌓인 코드. 나한테는 당연했다. 근데 민지 눈으로 보니 복잡했다. 주석 없는 함수. 이름만으로는 모호한 변수. 왜 이렇게 짰는지 기억 안 나는 로직. 민지는 내 코드 리뷰어가 됐다. "선배, 이 함수 이름이 뭘 하는 건지 모르겠어요." def _handle_alert(self): ...민지 말이 맞았다. alert 뜨면 accept? dismiss? 코드 봐야 안다. 리팩토링했다. def accept_alert_if_present(self): """Alert이 있으면 accept, 없으면 무시""" ...민지가 물었다. "이 try-except는 왜 있어요?" try: element.click() except ElementClickInterceptedException: self.driver.execute_script("arguments[0].click();", element)"어... 가끔 엘리먼트가 다른 거에 가려져서." "그럼 주석 달면 안 돼요?" 또 맞았다. 주석 추가했다. 민지 덕분에 내 코드가 나아졌다. 온보딩 방법 바꾸기 3주 뒤. 민지는 여전히 헤맸다. 내 접근이 틀렸다. "일단 프레임워크 써봐" 방식. 민지는 프레임워크 구조를 이해 못 했다. 왜 이렇게 짜야 하는지. 방법을 바꿨다. 온보딩 문서 만들기. 1단계: 날것의 Selenium 가장 기본부터. driver 띄우고 find_element. from selenium import webdriverdriver = webdriver.Chrome() driver.get("https://example.com") driver.find_element(By.ID, "username").send_keys("test")"이게 자동화의 시작이야. 이것만 알아도 테스트 짤 수 있어." 민지가 직접 짰다. 로그인. 검색. 장바구니. 코드는 지저분했다. 반복도 많았다. 하지만 돌아갔다. 민지 표정이 밝아졌다. "선배, 이거 재밌어요!" 2단계: 반복의 고통 민지한테 과제 줬다. "로그인 테스트 10개 짜봐." 다음 날 민지가 왔다. "선배, 코드가 너무 길어요." find_element 코드 100번 반복. Copy-paste 지옥. "개발자가 ID 바꾸면?" "다... 다 고쳐야죠." "그래서 함수로 빼는 거야." 민지는 함수를 만들었다. def login(driver, username, password): driver.find_element(By.ID, "username").send_keys(username) driver.find_element(By.ID, "password").send_keys(password) driver.find_element(By.ID, "login-btn").click()"훨씬 낫다. 이게 추상화의 시작이야."3단계: Page Object Model 민지 함수가 늘어났다. 50개. "선배, 이거 어떻게 관리해요?" "Page Object Model." 페이지별로 클래스 만들기. 로케이터 분리. 메소드로 액션 정의. class LoginPage: def __init__(self, driver): self.driver = driver self.username_input = (By.ID, "username") self.password_input = (By.ID, "password") def login(self, username, password): self.driver.find_element(*self.username_input).send_keys(username) ...이제 민지가 내 코드를 이해했다. "선배, 선배 코드가 이렇게 된 거였구나!" "응. 고통 받다보면 자연스럽게 이렇게 돼." 4단계: 프레임워크 pytest. fixture. conftest.py. Base Page. 민지는 이제 질문이 구체적이었다. "왜 fixture를 session scope로 해요?" "Base Page에 wait을 넣는 게 좋은 이유가 뭐예요?" 3주 전 질문과 달랐다. 문맥을 이해했다. 민지는 직접 Base Page 만들었다. 내 것과 비슷했다. "선배, 제가 짠 거 리뷰해주세요." 코드 봤다. 생각보다 괜찮았다. "민지야, 너 이제 자동화 엔지니어 같은데?" 민지가 웃었다. "아직 멀었어요." 민지의 역습 6주 뒤. 민지가 PR 날렸다. "선배, 이거 개선했어요." 내 retry 로직. 민지가 리팩토링했다. 원래 코드: def click_with_retry(self, locator, max_attempts=3): for i in range(max_attempts): try: element = self._wait_and_find(locator) element.click() return except: if i == max_attempts - 1: raise time.sleep(1)민지 코드: def click_with_retry(self, locator, max_attempts=3, retry_delay=1): """엘리먼트 클릭을 재시도. Stale element 대응.""" for attempt in range(max_attempts): try: element = self._wait_and_find(locator) element.click() logger.info(f"Click succeeded on attempt {attempt + 1}") return except (StaleElementReferenceException, ElementClickInterceptedException) as e: if attempt == max_attempts - 1: logger.error(f"Click failed after {max_attempts} attempts") raise logger.warning(f"Click failed, retrying... ({attempt + 1}/{max_attempts})") time.sleep(retry_delay)로깅 추가. Exception 구체화. Docstring. 내 코드보다 나았다. "Merge 할게." 민지가 좋아했다. "제 PR 첫 머지예요!" 깨달은 것들 민지 가르치면서 배웠다. 1. 내 코드는 내가 생각한 것보다 복잡하다 4년간 쌓인 코드. 당연하다고 생각한 패턴들. 초보자 눈엔 복잡한 미로. 2. "왜"를 알려줘야 한다 "이렇게 해" 방식은 안 먹힌다. 왜 Page Object Model? 왜 fixture? 왜 wait? 고통을 먼저 겪게 하고, 해결책을 제시하기. 3. 단계적 학습이 중요하다 처음부터 프레임워크 보여주기 = 실패 날것 Selenium → 반복의 고통 → 추상화 → 프레임워크 순서가 있다. 4. 질문은 코드 리뷰다 민지 질문은 내 코드의 문제점이었다. 주석 없는 함수. 모호한 네이밍. 불필요한 복잡도. 민지 덕분에 리팩토링했다. 5. 가르치면서 성장한다 민지한테 설명하려니 내가 제대로 이해 못 한 게 보였다. "이거 왜 이렇게 짰지?" 기억 안 나는 코드들. 다시 공부했다. Python decorator. Pytest fixture scope. Selenium wait 전략. 민지 때문에 내가 나아졌다. 지금 민지는 민지는 이제 자동화 스크립트 30개 관리한다. CI 빨간불 나면 혼자 고친다. PR 리뷰도 한다. 어제 민지가 말했다. "선배, 저도 후배 가르칠 수 있을 것 같아요." "그래? 뭘 먼저 가르칠 건데?" "당연히 날것 Selenium이죠. 고통부터 겪게 해야죠." 민지가 웃었다. 나도 웃었다. 민지가 진짜 이해했다. 다음 신입한테 다음 달 신입 또 온다. 이번엔 개발자 출신. 준비는 됐다. 온보딩 문서 있다. 민지한테 멘티 맡길 수도 있다. 근데 또 다를 거다. 개발자는 질문이 다를 테니까. "왜 unittest 안 쓰고 pytest 써요?" "이 구조는 왜 이렇게 짰어요?" 또 내 코드 다시 볼 거다. 또 리팩토링할 거다. 가르치는 게 배우는 거다. 민지가 증명했다.민지 질문이 내 코드를 고쳤다. 다음 신입도 기대된다.