Showing Posts From

Vs

XPath vs CSS Selector: 언제 뭘 써야 할까

XPath vs CSS Selector: 언제 뭘 써야 할까

XPath vs CSS Selector: 언제 뭘 써야 할까 오늘도 깨진 테스트 출근했다. 슬랙 알람 17개. "자동화 테스트 전부 실패했는데요?" 어제 개발자가 로그인 폼 디자인 살짝 바꿨다. class 이름 하나 변경. 내 테스트 스크립트 83개가 깨졌다. driver.find_element(By.XPATH, "//div[@class='login-container']/form/input[1]")이런 XPath를 썼었다. 멍청했다. 2시간 동안 로케이터 전부 수정했다. 점심도 못 먹었다. 오늘은 이 얘기를 써야겠다고 생각했다. XPath와 CSS Selector. 둘 다 쓸 줄 알지만 언제 뭘 써야 하는지 정확히 아는 사람은 적다. 나도 3년 차까지는 몰랐다.로케이터가 뭔지부터 자동화 테스트에서 웹 요소를 찾는 방법이다. "이 버튼 클릭해" 하려면 일단 그 버튼을 찾아야 한다. Selenium이나 Playwright한테 "여기 있어" 알려주는 게 로케이터다. 방법은 여러 개다.ID Name Class Name Tag Name Link Text XPath CSS SelectorID가 제일 좋다. 유일하고 빠르고 안 깨진다. 근데 현실은 ID 없는 요소가 태반이다. 그래서 XPath나 CSS Selector를 쓴다. 이 둘이 제일 강력하다. 거의 모든 요소를 찾을 수 있다. 문제는 강력할수록 잘못 쓰기 쉽다는 거다. 칼이 날카로울수록 조심해야 하듯이. 나는 신입 때 XPath만 썼다. 개발자 도구에서 Copy XPath 버튼 눌러서 복붙했다. /html/body/div[1]/div[2]/div[3]/form/div[1]/input이런 게 나온다. 겁나 길다. 그리고 UI 조금만 바뀌면 바로 깨진다. 3년 차 되니까 CSS Selector가 더 편하다는 걸 알았다. 4년 차인 지금은 상황에 따라 섞어 쓴다. XPath의 장점과 단점 XPath는 XML Path Language다. HTML은 XML의 일종이니까 쓸 수 있다. 장점부터. 상위로 올라갈 수 있다. # 버튼 찾고 그 부모의 부모 찾기 driver.find_element(By.XPATH, "//button[@id='submit']/../../")CSS Selector는 이게 안 된다. 부모나 형제를 찾을 수 없다. 오직 자식만. 복잡한 DOM 구조에서 특정 요소 기준으로 위로 올라가야 할 때 XPath만 답이다. 텍스트로 찾을 수 있다. # "로그인" 텍스트를 가진 버튼 driver.find_element(By.XPATH, "//button[text()='로그인']")# "확인"을 포함하는 버튼 driver.find_element(By.XPATH, "//button[contains(text(), '확인')]")진짜 편하다. 디자이너가 class 이름 맘대로 바꿔도 텍스트는 잘 안 바뀐다. 복잡한 조건을 쓸 수 있다. # position으로 찾기 //div[@class='item'][position()>2]# 여러 조건 and/or //input[@type='text' and @name='username']이런 건 CSS Selector로 못 한다. 단점도 명확하다. 느리다. CSS Selector보다 평균 10-20% 느리다. 브라우저는 CSS Selector를 네이티브로 지원한다. XPath는 그렇지 않다. 테스트 케이스 10개면 상관없다. 1000개면 체감된다. 문법이 복잡하다. //, /, @, [], .. 이런 기호들. 헷갈린다. 실수하기 쉽다. 신입 QA한테 가르치기도 어렵다. "이건 왜 슬래시가 2개예요?" 질문 받으면 설명이 길어진다. 브라우저마다 미묘하게 다르다. Chrome에서 되는 XPath가 Firefox에서 안 될 때가 있다. 많지는 않지만 가끔 당한다.CSS Selector의 장점과 단점 CSS Selector는 웹 개발자들이 스타일링할 때 쓰는 그거다. 장점. 빠르다. 브라우저 엔진이 직접 지원한다. 최적화도 잘 돼 있다. XPath보다 확실히 빠르다. 대규모 E2E 테스트 스위트 돌릴 때 차이가 난다. 문법이 직관적이다. # ID로 찾기 driver.find_element(By.CSS_SELECTOR, "#username")# Class로 찾기 driver.find_element(By.CSS_SELECTOR, ".login-button")# 속성으로 찾기 driver.find_element(By.CSS_SELECTOR, "input[type='password']")# 자식 찾기 driver.find_element(By.CSS_SELECTOR, "form > input")깔끔하다. 읽기 쉽다. 후배한테 가르치기도 편하다. 크로스 브라우저 호환성이 좋다. CSS는 표준이다. Chrome이든 Firefox든 Safari든 똑같이 작동한다. 단점. 위로 못 올라간다. 부모 선택자가 없다. :has()가 있긴 한데 Selenium에서 지원 안 하는 경우가 많다. 형제 요소 찾기도 제한적이다. 바로 다음 형제만 +로 찾을 수 있다. 텍스트로 못 찾는다. 이게 제일 아쉽다. 버튼 텍스트로 직접 찾을 방법이 없다. # 이런 거 안 됨 driver.find_element(By.CSS_SELECTOR, "button[text='로그인']") # 틀림XPath 써야 한다. 복잡한 조건에 약하다. position이나 조건 로직 같은 건 표현하기 어렵다. 내가 쓰는 기준 4년 동안 삽질하면서 정리한 내 원칙이다. 기본은 CSS Selector. 웬만하면 CSS Selector 쓴다. 빠르고 읽기 쉽고 안정적이다. # 좋음 driver.find_element(By.CSS_SELECTOR, "button[data-testid='submit']") driver.find_element(By.CSS_SELECTOR, ".modal-content input[name='email']")텍스트로 찾아야 하면 XPath. 버튼 라벨, 링크 텍스트, 에러 메시지. 이런 건 텍스트로 찾는 게 제일 안정적이다. # 텍스트 기반 로케이터 driver.find_element(By.XPATH, "//button[text()='다음']") driver.find_element(By.XPATH, "//a[contains(text(), '비밀번호 찾기')]") driver.find_element(By.XPATH, "//span[@class='error' and contains(text(), '필수')]")UI 디자인 바뀌어도 버튼에 쓰인 "다음" 텍스트는 잘 안 바뀐다. 부모/형제 찾아야 하면 XPath. 체크박스 옆의 라벨 텍스트로 체크박스 찾기. 이런 패턴. # 라벨 텍스트로 체크박스 찾기 checkbox = driver.find_element(By.XPATH, "//label[text()='이용약관 동의']/preceding-sibling::input[@type='checkbox']")CSS Selector로는 불가능하다. 테이블 같은 복잡한 구조는 XPath. 특정 행의 특정 열 찾기. position 필요할 때. # 3번째 행의 2번째 셀 cell = driver.find_element(By.XPATH, "//table[@id='data-table']/tbody/tr[3]/td[2]")절대 경로는 쓰지 않는다. # 이런 거 절대 금지 driver.find_element(By.XPATH, "/html/body/div[1]/div[2]/form/input[3]")Copy XPath 해서 나온 거 그대로 쓰면 이렇다. 개발자가 div 하나만 추가해도 깨진다. 상대 경로를 써야 한다. # 이렇게 driver.find_element(By.XPATH, "//form[@id='login-form']//input[@name='username']")안정적인 로케이터 전략 UI 변경에 강한 로케이터를 만드는 게 핵심이다. 1. data-testid 속성을 쓴다. 개발자한테 요청해서 테스트용 속성을 넣어달라고 한다. <button data-testid="submit-button">제출</button>driver.find_element(By.CSS_SELECTOR, "[data-testid='submit-button']")이게 제일 안정적이다. 디자인 바뀌어도 안 깨진다. 우리 팀은 모든 주요 요소에 data-testid 붙이기로 컨벤션 정했다. 개발자들한테 처음엔 귀찮다는 소리 들었다. 지금은 당연하게 생각한다. 2. 의미 있는 속성을 우선한다. 좋은 순서:ID (있으면) data-testid name type + name 조합 class (여러 개 중 의미 있는 것) 텍스트 구조 기반 (최후의 수단)# 좋음 driver.find_element(By.CSS_SELECTOR, "#username") driver.find_element(By.CSS_SELECTOR, "input[name='email']")# 나쁨 driver.find_element(By.CSS_SELECTOR, "div > div > input:nth-child(2)")3. 동적 class는 피한다. # 나쁨 - 빌드마다 바뀌는 해시 driver.find_element(By.CSS_SELECTOR, ".css-1hw23kj-button")# 좋음 - 안정적인 class driver.find_element(By.CSS_SELECTOR, ".primary-button")Tailwind나 CSS Modules 쓰면 class 이름에 해시가 붙는다. 절대 쓰면 안 된다. 4. nth-child는 조심한다. # 위험 - 순서 바뀌면 깨짐 driver.find_element(By.CSS_SELECTOR, "form input:nth-child(2)")# 나음 - 속성으로 특정 driver.find_element(By.CSS_SELECTOR, "form input[type='password']")리스트나 테이블에서 position이 중요한 경우에만 써야 한다. 5. 로케이터를 변수로 관리한다. Page Object Pattern을 쓴다. class LoginPage: USERNAME_INPUT = (By.CSS_SELECTOR, "input[name='username']") PASSWORD_INPUT = (By.CSS_SELECTOR, "input[name='password']") SUBMIT_BUTTON = (By.XPATH, "//button[text()='로그인']") def login(self, username, password): driver.find_element(*self.USERNAME_INPUT).send_keys(username) driver.find_element(*self.PASSWORD_INPUT).send_keys(password) driver.find_element(*self.SUBMIT_BUTTON).click()로케이터 한 곳에서 관리. UI 바뀌면 여기만 수정하면 된다.성능 비교 실험 궁금해서 직접 측정해봤다. 테스트 환경:페이지: 복잡한 대시보드 (DOM 요소 500+) 반복: 각 로케이터 100회 실행 브라우저: Chrome 120결과: ID: 평균 12ms CSS Selector: 평균 18ms XPath (상대경로): 평균 23ms XPath (절대경로): 평균 35msID가 제일 빠르다. 당연하다. CSS Selector가 XPath보다 약 30% 빠르다. 절대 경로 XPath는 두 배 느리다. 쓰면 안 되는 이유가 하나 더. 100회면 차이가 1초. 전체 테스트 스위트 1000개면 10초다. 무시 못 한다. 근데 성능보다 중요한 게 안정성이다. 0.01초 빨라도 매주 깨지면 소용없다. 실제 사례들 사례 1: 동적 폼 회원가입 폼. 선택 항목에 따라 필드가 추가된다. 처음에 이렇게 짰다: # 나쁨 driver.find_element(By.XPATH, "//form/div[3]/input")"기업 회원" 선택하면 사업자등록번호 필드가 생긴다. 그럼 div 순서가 바뀐다. 깨진다. 수정: # 좋음 driver.find_element(By.CSS_SELECTOR, "input[name='phone']") driver.find_element(By.XPATH, "//label[text()='전화번호']/following-sibling::input")name 속성이나 라벨 텍스트로 찾으니 안정적이다. 사례 2: 모달 창 "정말 삭제하시겠습니까?" 확인 모달. 페이지에 여러 모달이 있다. class 이름이 전부 .modal이다. # 나쁨 - 어떤 모달인지 모름 driver.find_element(By.CSS_SELECTOR, ".modal button")첫 번째 모달의 버튼을 찾는다. 원하는 모달이 아닐 수 있다. 수정: # 좋음 - 텍스트로 특정 modal = driver.find_element(By.XPATH, "//div[contains(@class, 'modal') and contains(., '삭제하시겠습니까')]") confirm_button = modal.find_element(By.XPATH, ".//button[text()='확인']")모달 내용으로 찾고, 그 안에서 버튼을 찾는다. 사례 3: 동적 리스트 상품 목록. 개수가 계속 바뀐다. 특정 상품의 "장바구니" 버튼 클릭해야 한다. # 나쁨 driver.find_element(By.XPATH, "//div[@class='product-list']/div[5]/button")5번째 상품이 뭔지 모른다. 상품 추가되면 순서 바뀐다. 수정: # 좋음 product_name = "무선 키보드" button = driver.find_element( By.XPATH, f"//div[contains(@class, 'product-item') and contains(., '{product_name}')]//button[text()='장바구니']" )상품 이름으로 찾으니 확실하다. 디버깅 팁 로케이터 안 될 때 내가 하는 것들. 1. 개발자 도구에서 직접 테스트 Console에서: // CSS Selector 테스트 document.querySelector("input[name='username']")// XPath 테스트 $x("//button[text()='로그인']")결과가 나오면 로케이터는 맞다. 타이밍 문제다. null 나오면 로케이터가 틀렸다. 2. 암묵적 대기 vs 명시적 대기 # 암묵적 대기 - 전역 설정 driver.implicitly_wait(10)# 명시적 대기 - 특정 요소 from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as ECelement = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='username']")) )동적 로딩이 많으면 명시적 대기가 낫다. 3. Screenshot 찍기 요소 못 찾으면 그 순간 스크린샷 저장. try: driver.find_element(By.CSS_SELECTOR, ".submit-button").click() except NoSuchElementException: driver.save_screenshot("debug.png") raise뭔가 예상과 다르게 렌더링됐는지 바로 알 수 있다. 4. 여러 로케이터 시도 def find_element_flexible(driver, *locators): for locator in locators: try: return driver.find_element(*locator) except NoSuchElementException: continue raise NoSuchElementException(f"None of the locators worked: {locators}")# 사용 button = find_element_flexible( driver, (By.CSS_SELECTOR, "[data-testid='submit']"), (By.XPATH, "//button[text()='제출']"), (By.CSS_SELECTOR, "button.primary") )백업 로케이터를 둔다. 하나 바뀌어도 다른 걸로 찾는다. 팀 컨벤션 우리 팀 규칙. 1. 로케이터 우선순위 1. data-testid (CSS) 2. ID (CSS) 3. name (CSS) 4. 텍스트 (XPath) 5. 복합 속성 (CSS) 6. 구조 + 텍스트 (XPath)이 순서대로 시도. 문서화했다. 2. 절대 경로 금지 코드 리뷰에서 절대 경로 보이면 즉시 reject. # ❌ Rejected "/html/body/div[1]/..."# ✅ Approved "//form[@id='login']//input"3. Page Object 필수 직접 로케이터 쓰지 말고 Page Object 통해서. # ❌ Bad driver.find_element(By.CSS_SELECTOR, "input[name='username']").send_keys("test")# ✅ Good login_page.enter_username("test")4. 로케이터 네이밍 # 명확하게 USERNAME_INPUT = (By.CSS_SELECTOR, "input[name='username']") SUBMIT_BUTTON = (By.XPATH, "//button[text()='로그인']")# 애매하게 말고 INPUT_1 = (By.CSS_SELECTOR, "input:nth-child(1)") # ❌ BTN = (By.XPATH, "//button") # ❌5. 주석으로 이유 설명 특이한 로케이터는 왜 그렇게 했는지 적는다. # XPath 사용 이유: 부모 요소 기준으로 찾아야 함 # 라벨 텍스트로 체크박스 선택 TERMS_CHECKBOX = (By.XPATH, "//label[text()='이용약관 동의']/preceding-sibling::input")실전 체크리스트 로케이터 짤 때 내가 확인하는 것들. 작성 전: 이 요소에 ID나 data-testid 있나? 개발자한테 추가 요청 가능한가? 텍스트 기반으로 찾을 수 있나? 여러 개 있는 요소인가? (리스트, 테이블)작성 중: 절대 경로 안 썼나? 동적 class 안 썼나? nth-child 꼭 필요한가? 더 안정적인 속성 없나?작성 후: 개발자 도구에서 테스트했나? 페이지 새로고침해도 작동하나? 다른 데이터로도 작동하나? (예: 다른 상품명) 백업 로케이터 필요한가?결론 CSS Selector를 기본으로. XPath는 필요할 때만. 빠르고 읽기 쉬운 게 CSS Selector다. 대부분 이걸로 해결된다. 텍스트로 찾거나 부모로 올라가야 하면 XPath 쓴다. 근데 제일 중요한 건 로케이터 종류가 아니다. 얼마나 안정적으로 짜느냐다. ID나 data-testid 쓸 수 있으면 무조건 그거 쓴다. CSS든 XPath든 상관없다. 개발자랑 협업해서 테스트하기 좋은 마크업 만드는 게 제일 중요하다. 로케이터 전략이 없으면 UI 바뀔 때마다 테스트 고친다. 시간 낭비다. 전략 세워두면 테스트가 안정적이다. 자동화가 의미 있어진다. 나도 초반엔 닥치는 대로 Copy XPath 했다. 계속 깨졌다. 지금은 로케이터 짜는 데 시간 쓴다. 그게 나중에 시간 아낀다.오늘 점심 먹으면서 후배한테 이 얘기 했다. "로케이터 전략부터 세워" 라고. 후배는 고개를 끄덕였다. 다음 PR에서 확인할 거다.

자동화 엔지니어 vs 개발자: 내 정체성은 뭘까

자동화 엔지니어 vs 개발자: 내 정체성은 뭘까

자동화 엔지니어 vs 개발자: 내 정체성은 뭘까 7년 차, 여전히 모르겠다 아침 10시. 출근해서 테스트 결과 확인했다. 밤새 돌린 E2E 테스트 327개 중 12개 실패. 로그 열어봤다. 8개는 타임아웃, 3개는 셀렉터 변경, 1개는 진짜 버그. 이 과정이 개발인지 QA인지 모르겠다. 7년 전 신입 때는 단순했다. 매뉴얼 QA. 클릭하고 확인하고 버그 리포트. 내 역할이 명확했다. QA는 QA였다. 4년 전 자동화로 넘어오면서 모호해졌다. 코드 짜고, 아키텍처 고민하고, 리팩토링하고. 이게 개발 아닌가? 어제 신입 개발자가 물었다. "J님은 개발자세요?" 잠깐 멈췄다. 답을 못 했다.매뉴얼 3년, 명확했던 시절 2018년. 첫 회사. QA팀 막내. 테스트 케이스 엑셀로 관리했다. 손으로 하나하나 클릭. 당시엔 단순했다. 기획서 보고 → 테스트 케이스 작성 → 실행 → 버그 리포트 → 회귀 테스트. 개발팀과 경계가 분명했다. 그들은 코드를 만들고, 우리는 검증했다. "QA는 품질의 파수꾼"이라는 말에 자부심 느꼈다. 버그 찾으면 뿌듯했다. 내 역할이 명확했다. 물론 힘들었다. 반복 작업. 야근. 회귀 테스트 지옥. 한 스프린트에 300개 테스트 케이스 손으로 돌렸다. 2년 차 되던 해, 팀장이 말했다. "자동화 배워볼래?" 그때는 몰랐다. 내 정체성이 흔들리기 시작할 줄. 자동화 시작, 코드와의 첫 만남 2021년. 새 회사로 이직. 자동화 포지션. 첫날 Selenium 설치했다. Python 기초 강의 들었다. 처음엔 간단했다. driver.find_element(By.ID, "login").click()"이거면 되네?" 싶었다. 3개월 후, 현실 직면했다.Flaky 테스트: 랜덤하게 실패하는 놈들 타임아웃 문제: Wait 조건 잡기 셀렉터 깨짐: UI 조금만 바뀌면 전부 수정 테스트 데이터 관리: DB 초기화는 어떻게?"이거 개발 아냐?" 생각했다. Page Object Model 배웠다. 디자인 패턴 공부했다. pytest fixture, conftest.py, 로그 관리, 리포트 생성. 6개월 후엔 프레임워크 설계했다. base_page.py 만들고, 공통 메서드 추출하고, config 관리하고. 동료 개발자가 코드 리뷰 달았다. "여기 중복 제거할 수 있어요." 그 순간 깨달았다. 나도 개발자처럼 일하고 있다는 걸.개발자인 듯 개발자 아닌 작년 봄. 개발팀 회의에 참석했다. 마이크로서비스 아키텍처 전환 논의. 프론트엔드 개발자: "API 스펙 바뀌면 통신 다시 짜야죠." 백엔드 개발자: "DB 마이그레이션 스크립트 필요해요." 나: "테스트 환경 구성은 어떻게 하죠?" 다들 고개 끄덕였다. 나도 의견 냈다. "서비스 간 통합 테스트가 복잡해질 텐데, 모킹 전략 필요합니다." 그 자리에선 동료였다. 개발자처럼. 회의 끝나고 슬랙 메시지 왔다. "J님, 회원가입 시나리오 손으로 한 번 테스트 부탁드려요." 순간 멈칫했다. 매뉴얼 테스트. 자동화했는데 왜 또 손으로? 물어봤다. "자동화 테스트로는 안 될까요?" "프로덕션 환경이라 자동화는 좀..." 아, 맞다. 나는 개발자가 아니구나. 급여는 개발자, 취급은... 연봉 협상 때. "자동화 엔지니어는 개발자급이니까 6500 드릴게요." 좋았다. 매뉴얼 QA 평균보다 1500 높았다. 근데 조직도를 보면 QA팀 소속. 팀명: "품질관리팀" 개발자 워크샵 있을 때. 초대 안 받았다. "개발 조직만 가는 거라서..." 컨퍼런스 지원 신청했다. "코드 짜시잖아요. 개발 컨퍼런스 가세요." 가서 발표했다. "E2E 테스트 자동화 프레임워크 구축기" 청중 질문: "근데 왜 개발팀에 안 계세요?" 답 못 했다. 사내 개발자 커뮤니티 있다. 가입 신청했다. "QA팀은... 음... 관심사가 다를 것 같아서..." 거절당했다. 급여는 개발자, 소속은 QA, 일은 둘 다. 나는 뭘까.SDET라는 새로운 선택지 6개월 전. 링크드인 헤드헌터 메시지. "SDET 포지션 관심 있으세요?" SDET. Software Development Engineer in Test. 처음 들어봤다. 찾아봤다.테스트 코드도 프로덕션 코드만큼 중요 개발팀 소속, 품질 책임 TDD, CI/CD 파이프라인 관리 테스트 인프라 개발"이거 나잖아?" JD 더 봤다.코딩 테스트 필수 자료구조, 알고리즘 능력 시스템 디자인 면접 프로덕션 코드 리뷰 참여긴장됐다. 내가 개발자 코딩 테스트를 통과할 수 있을까? LeetCode 시작했다. Easy 문제부터. Two Sum 풀었다. 30분 걸렸다. 개발자는 5분 만에 푸는 문제. "나는 아직 멀었구나." 코드는 짤 줄 알지만 내 GitHub 저장소.test-automation-framework: 스타 23개 api-testing-utils: 스타 8개 selenium-helper: 스타 15개전부 테스트 관련. 프로덕션 코드는? 없다. 기여한 오픈소스는? 테스트 툴만. 이력서 technical skills:Python, JavaScript (테스트용) Selenium, Appium, Pytest Jenkins, Docker (CI/CD) API Testing, E2E Testing개발자 이력서랑 비교했다.Python, JavaScript (프로덕션) Django, React AWS, Kubernetes RESTful API 설계, 마이크로서비스방향이 다르다. 작년에 프로덕션 코드 한 번 짰다. 테스트 환경 초기화 스크립트. 200줄. 시니어 개발자가 리뷰했다. "여기 에러 핸들링 약하네요." "로깅 레벨 잘못 잡았어요." "이건 유틸로 빼는 게 좋겠어요." 수정하는데 3일 걸렸다. 개발자들은 하루에 이런 코드 수백 줄 짠다. 나는 200줄에 3일. "나는 개발자가 아니구나." 다시 깨달았다. 정체성 혼란의 순간들 순간 1: 채용 공고 "QA 자동화 엔지니어 채용" 요구사항: 3년 이상 개발 경험 개발 경험? 나는 QA 경험 7년인데. 순간 2: 이직 면접 면접관: "본인은 QA 출신인가요, 개발 출신인가요?" 나: "QA로 시작했지만 지금은..." 면접관: "아, QA 출신이시네요." 탈락했다. 순간 3: 팀 회식 개발팀장: "J님은 뭐 하세요?" 나: "자동화 엔지니어요." 개발팀장: "아, 테스터?" 아니라고 하고 싶었다. 근데 맞는 말 같기도 하고. 순간 4: 연봉 협상 "QA는 올해 3% 인상입니다." "근데 저 코드 짜잖아요." "그래도 QA팀이니까요." 억울했다. 순간 5: 프로젝트 회고 PM: "개발은 잘 끝났고, QA는..." 나: "저도 개발했는데요. 테스트 인프라." PM: "아 네, QA 자동화 잘하셨어요." 개발으로 인정 안 받는 느낌. 양쪽에서 다 어중간한 QA 관점에서 보면: "J님은 매뉴얼 감각이 떨어져요." "요즘 손으로 안 해봐서 그래요." 손으로 안 하는 이유? 자동화했으니까. 근데 그게 단점이 된다. 후배 QA가 찾은 UI 버그. "이거 자동화 테스트에서 왜 안 잡았어요?" 시각적 요소. 픽셀 단위 레이아웃. 자동화로 잡기 어렵다. "자동화가 만능은 아니거든." "그럼 뭐 하러 자동화해요?" 할 말 없었다. 개발 관점에서 보면: "테스트 코드 품질이 낮아요." "프로덕션 코드처럼 관리해야죠." 노력했다. 리팩토링했다. 커버리지 올렸다. 근데 개발자가 보면 여전히 부족하다. "이런 건 디자인 패턴 쓰면 좋은데..." "성능 테스트는 k6가 낫지 않을까요?" 알고는 있다. 근데 시간이 없다. 테스트 케이스 늘리는 게 우선이니까. 양쪽에서 다 중간이다. QA 중에선 제일 코드 잘 짜는 사람. 개발자 중에선 제일 테스트만 하는 사람. 자동화의 함정 자동화 시작할 때 생각했다. "이거 다 자동화하면 나는 뭐 하지?" 4년 차. 답 나왔다. 자동화 유지보수. 개발팀이 UI 리뉴얼했다. 테스트 스크립트 380개 깨졌다. 2주 동안 고쳤다. 셀렉터 전부 수정. 끝나자마자 또 깨졌다. API 스펙 변경. Flaky 테스트. 랜덤 실패하는 놈들. 원인 찾는데 3일. 고치는데 1시간. "자동화하면 편할 줄 알았는데..." 유지보수가 개발보다 어렵다. 내가 짠 코드지만 6개월 후엔 낯설다. 주석 없으면 이해 못 한다. "이거 왜 이렇게 짰지?" 테스트 커버리지 80%. "나머지 20%는요?" "자동화 어려운 케이스예요." "그럼 손으로 해야죠." 결국 매뉴얼도 병행. 자동화 엔지니어인데 손으로 테스트. 이게 맞나 싶다. 커리어 고민, SDET로 갈까 링크드인 봤다. SDET 채용 공고.구글: SDET, L4, $180K 페이스북: Software Engineer, Testing, E5 넷플릭스: Senior SDET공통점: 개발팀 소속. 요구사항 봤다.코딩 테스트 (LeetCode Medium 이상) 시스템 디자인 테스트 전략 설계 프로덕션 코드 기여마지막이 관건이다. 프로덕션 코드. 내 경험:테스트 코드 4년 프로덕션 코드 0년JD에 "테스트 코드도 프로덕션 코드"라고 써있다. 위안 삼았다. 지원했다. 스타트업 SDET. 1차 코딩 테스트. Medium 2문제. 첫 문제: Binary Tree Level Order Traversal 45분 걸렸다. 제한 시간 30분. 탈락. "아직 멀었구나." LeetCode 매일 풀기 시작했다. 퇴근하고 2시간씩. 한 달 후 다시 지원. 다른 회사. 코딩 테스트 통과. 2차 기술 면접. "테스트 인프라 어떻게 설계하셨나요?" 대답했다. 내 경험 기반. 면접관이 고개 끄덕였다. "근데 프로덕션 API는 개발해보셨어요?" "...아니요." "SDET는 기능 개발도 하거든요." 또 탈락. 내가 원하는 건 뭘까 깊이 생각해봤다. 개발자가 되고 싶은가? 100% 아니다. QA로 남고 싶은가? 100% 아니다. 그럼 뭘 원하는가?코드로 문제 해결하고 싶다 품질에 대한 책임감을 유지하고 싶다 개발자와 동등하게 대우받고 싶다 테스트만 하는 사람은 되기 싫다 기능 개발만 하는 사람도 되기 싫다모순이다. SDET가 답일까? 어쩌면 맞다. 어쩌면 아니다. SDET 된다고 정체성 혼란 사라질까? 모르겠다. 결국 라벨 문제가 아닐 수도. "나는 무엇을 하는 사람인가"가 중요한 거지. 테스트 코드 짜는 개발자? 개발하는 QA? 둘 다 맞는 것 같다. 둘 다 틀린 것 같기도. 3개월 후, 작은 변화 포지션 타이틀 바꿨다. "QA 자동화 엔지니어" → "품질 엔지니어(Quality Engineer)" QA 빼니까 기분이 다르다. 팀 회의 때 말투 바꿨다. "이거 테스트해볼게요" → "이거 검증 로직 구현할게요" 사소하지만 차이 있다. 개발팀 코드 리뷰 참여 시작했다. "테스트 가능한 코드인가" 관점으로. "여기 의존성 주입하면 목킹 쉬울 것 같아요." 개발자: "오, 좋은데요?" PR 머지됐다. 내 리뷰로. 기여한 느낌. 처음이다. 사이드 프로젝트 시작했다. 간단한 웹앱. Todo 리스트. 프로덕션 코드 짜봤다. React, Node.js, MongoDB. 테스트 코드도 짰다. 당연히. 2주 만에 완성. 배포했다. "나도 개발할 수 있구나." 물론 프로 개발자 수준은 아니다. 근데 할 수 있다는 게 중요하다. 이력서 업데이트했다. "Full-stack 경험 있음 (사이드 프로젝트)" 거짓말은 아니다. 여전히 답은 모른다 지금도 모른다. 내가 뭔지. 출근해서 코드 짠다. 퇴근하고도 코드 짠다. 주말엔 LeetCode 푼다. 월요일엔 테스트 리포트 쓴다. 어떤 날은 개발자 같다. 어떤 날은 QA 같다. 근데 요즘은 괜찮다. 굳이 하나일 필요 있나? 하이브리드면 어때? 개발도 하고 테스트도 하는 사람. 품질도 책임지고 코드도 짜는 사람. 라벨이 뭐든 상관없다. 내가 하는 일이 중요하다. "자동화 엔지니어세요?" "네, 품질 엔지니어이기도 하고, 때론 개발자이기도 해요." 이제 이렇게 답한다. 더 이상 멈칫하지 않는다. SDET로 갈지 모른다. 안 갈 수도 있다. 중요한 건 계속 성장하는 것. 코드도, 테스트도, 품질도. 7년 차. 여전히 모르지만. 괜찮다. 계속 찾아가는 중이다.정체성은 명함이 아니라 내가 하는 일로 정의되는 거다.