불안정 테스트
자동화 테스트를 하다 보면 이런 경험, 한 번쯤 해보셨을 겁니다. 똑같은 코드와 환경에서 테스트를 돌렸는데, 어떤 날은 성공하고 어떤 날은 실패하는 경우 말이죠. 이런 테스트를 바로 Flaky 테스트라고 부릅니다. Flaky 테스트는 말 그대로 "불안정한" 테스트입니다. 테스트 코드나 로직에 큰 문제가 없어 보이는데도, 성공과 실패를 왔다 갔다 하며 개발자들의 신뢰를 무너뜨리곤 하죠. 처음엔 사소해 보일 수 있지만, 이런 테스트가 누적되면 팀의 개발 흐름 전체를 방해하는 주요 원인이 될 수 있습니다. 포괄적인 개념에 대해서는 Martin Fowler의 Eradicating Non-Determinism in Tests를 읽어보는 것도 도움이 됩니다.
Flaky 테스트가 계속해서 발생하면, 테스트 자동화의 본질적인 목적이 흔들리게 됩니다. 특히 아래 두 가지 문제가 크게 드러납니다.
테스트 신뢰도 하락
"이 테스트는 원래 좀 이상해. 무시하자." 이런 말이 팀 내에서 나오기 시작하면 경고등이 켜진 겁니다. Flaky 테스트가 많아지면, 개발자들이 더 이상 테스트 결과를 믿지 않게 됩니다. 결국 테스트가 실패하더라도 그것이 코드의 진짜 문제인지, 아니면 환경적 이슈인지 헷갈리게 되고, 디버깅과 확인 작업에 많은 시간이 낭비됩니다.
개발 생산성 저하
CI/CD 파이프라인이 Flaky 테스트로 인해 자꾸 멈추거나 재실행이 필요해진다면? 당연히 팀의 생산성은 떨어질 수밖에 없습니다. 특히, 배포 과정에서 테스트가 불안정하다면 중요한 업데이트나 릴리스 일정이 지연될 가능성도 커지죠.
Flaky 테스트는 다양한 요인에서 발생할 수 있습니다. 첫 번째는 비동기 처리 문제입니다. 특히 UI 테스트에서 자주 나타나는데, 예를 들어 특정 버튼이 화면에 로드되기도 전에 테스트가 해당 버튼을 클릭하려고 하면 실패할 가능성이 높습니다.
두 번째는 환경 의존성입니다. 로컬 환경에서는 테스트가 잘 작동했지만, CI 환경에서는 실패하는 경우가 이에 해당합니다. 네트워크 지연이나 외부 API 의존성 같은 환경 차이로 인해 테스트 결과가 일관되지 않게 나타날 수 있습니다.
세 번째는 테스트 데이터의 불안정성입니다. 테스트 실행 간에 데이터베이스나 외부 리소스의 상태가 달라질 경우, 테스트 결과가 영향을 받을 수 있습니다. 이처럼 데이터의 변동성이 클수록 Flaky 테스트가 발생할 가능성도 높아집니다.
마지막으로 코드 실행 타이밍 문제도 Flaky 테스트의 주요 원인입니다. 특정 조건에서 테스트 코드가 실행되는 순서나 타이밍이 일관되지 않을 경우, 예상치 못한 오류가 발생할 수 있습니다.
Flaky 테스트는 팀 내에서 "어쩔 수 없는 문제"로 방치되기 쉽습니다.
하지만 이를 그대로 두면 테스트가 제공해야 할 신뢰성과 효율성을 잃게 됩니다. 무엇보다 자동화 테스트의 핵심 목적은 코드 품질을 빠르게 검증하고, 개발자에게 신속한 피드백을 제공하는 데 있습니다.
에이슬립 웹·모바일 서비스에서 발생한 Flaky 테스트의 원인을 깊이 살펴보고, 이를 제거하기 위해 다양한 방법을 시도했습니다. 제가 겪었던 문제와 이를 해결하기 위한 과정을 공유해 보려 합니다. 이 글이 Flaky 테스트로 고민하고 계신 분들께 조금이라도 도움이 되었으면 합니다.
문제 상황
웹 UI 자동화 테스트를 진행하다 보면, 테스트가 비정상적으로 실패하거나 성공이 불안정한 Flaky 테스트 문제가 자주 발생합니다. 특히, 비동기 네트워크 요청과 UI 렌더링 간의 시차로 인해 이러한 문제가 발생하게 되는데요.
테스트 환경에서 환자 목록을 보여주는 페이지는 특정 API 요청의 응답 결과를 기반으로 렌더링 됩니다. 하지만 해당 API 요청이 완료되지 않았거나 네트워크 지연이 발생하면, UI가 업데이트되기 전에 테스트가 진행되어 실패하는 문제가 빈번하게 발생했습니다.
기존 테스트 시나리오는 사용자가 로그인 후 환자 목록 페이지로 이동하고, 환자 등록 기능을 수행한 뒤 등록된 환자가 목록에 나타나는지 확인하는 과정으로 구성되었습니다. 로그인 후 네트워크 요청과 UI 렌더링 간 시차로 인해 명시적 대기인 waitForDisplayed를 사용해 DOM 요소가 나타날 때까지 기다려도 네트워크 요청이 완료되지 않아 빈 상태의 요소가 먼저 렌더링 되는 문제가 있었습니다. 환자 등록 후에도 페이지를 새로고침했지만 목록이 UI에 반영되기 전에 테스트가 진행되어 실패하는 상황이 반복되었습니다. 또한 테스트 데이터가 쌓이면서 요청 시간이 처음 테스트 코드를 통과시킨 시간보다 점진적으로 늘어나는 현상이 발견되어, 일관된 테스트 환경을 확보하기가 어려웠습니다.
기존 접근법의 한계
WebDriverIO의 명시적 대기(waitForDisplayed, waitUntil)는 요소의 표시 상태나 특정 조건을 만족할 때까지 기다리는 데 효과적이지만, 네트워크 요청 상태를 확인하거나 UI가 최종적으로 갱신되는 시점을 보장하지는 못했습니다. 대기 시간을 충분히 길게 설정하면 특정 요소가 나타날 때까지 기다릴 수 있어 Flaky 테스트 문제를 어느 정도 완화할 수는 있습니다. 예를 들어, 대기 시간을 60초로 설정하면 요청 지연이 있어도 성공할 가능성이 높습니다. 그 정도 시간이면 사실상 요청이 완료되어야 하기 때문이죠. 그러나 이 접근 방식은 DOM 요소가 표시되는 상태만 확인할 뿐, 표시되지 않는 이유가 네트워크 요청 실패인지 데이터 로드 문제인지 명확히 알 수 없습니다. 또한, 문제가 발생했을 때 원인을 파악하기 어려워 디버깅이 어렵고, 테스트 신뢰성 역시 충분히 확보되지 않습니다.
performance API 활용
이를 해결하기 위해 브라우저의 네트워크 요청 상태를 직접 추적할 수 있는 performance API를 활용했습니다. performance API는 특정 네트워크 요청이 완료된 이후에만 테스트를 진행하도록 보장하며, UI 렌더링 완료 시점과 테스트 흐름을 정확히 동기화할 수 있었습니다.
이 과정에서 performance.getEntriesByType("resource")를 사용해 브라우저에서 발생한 모든 네트워크 요청을 추적하고, 특정 API 요청이 성공적으로 완료되었는지 확인했습니다. 이후, 해당 API 요청이 완료된 것을 확인한 뒤 DOM 요소 상태를 waitForDisplayed로 다시 검증했습니다.
performance API를 활용한 접근 방식은 네트워크 요청과 UI 상태 간의 불일치로 발생하는 Flaky 테스트 문제를 해결할 수 있었습니다. performance API는 본래 브라우저의 성능 측정 및 최적화를 위한 도구로 설계되었지만, 네트워크 요청 상태를 추적하는 데 유용하게 활용될 수 있습니다. 비록 원래의 목적은 아니지만, 특정 요청의 완료 여부를 확인하고 이를 기반으로 테스트 흐름을 조정함으로써 Flaky 테스트 문제를 안정적으로 해결할 수 있었습니다. 네트워크 요청 상태를 추적하여 요청 지연이나 실패로 인한 테스트 불안정을 방지했으며, 네트워크 요청 완료 이후에만 UI 상태를 검증했기 때문에 UI가 완전히 업데이트되었음을 보장할 수 있었습니다. 또한, 네트워크 요청 정보를 로그로 남겨 디버깅 과정을 크게 개선했습니다. 요청 실패나 지연 상황을 명확히 파악할 수 있었고, 문제의 원인이 네트워크 요청인지, UI 렌더링 지연인지 명확히 구분할 수 있었습니다. (WebDriverIO v9에서 관련 기능의 안정성이 충분히 확보되면, WebDriverIO에서 권장하는 방식으로 전환하여 Performance API 기반 코드를 리팩터링 할 계획입니다.)
Flaky 테스트 문제는 단순히 대기 시간을 늘리는 방식으로는 근본적으로 해결되지 않습니다. 대기 시간을 길게 설정하는 방식은 문제가 발생했을 때 원인을 파악하기 어렵고, 비효율적인 테스트 실행으로 이어질 수 있습니다. 명시적 대기를 도입하더라도 버그 발생 시 어느 영역에서 문제가 발생한 건지 다소 명확하지 않을 수 있습니다.
대신, 네트워크 요청 상태를 추적하여 요청 완료 여부를 명확히 확인하고, 이후 UI 상태를 명시적 대기로 검증하는 방식을 도입하면 네트워크 요청 기반으로 동작하는 UI에서 테스트 안정성을 크게 개선할 수 있으며 더욱 신뢰할 수 있는 테스트 환경을 구축할 수 있습니다.
문제 상황
모바일 같은 경우 UI 표시되었지만 클릭 또는 입력 가능한 상태가 아닌 상태에서 클릭과 입력 이벤트가 실행되는 현상과 간헐적으로 로그인 진행 시 인디케이터만 계속 나타나고 다음 화면까지 3~4초 이상 소요되는 현상이 발견되었습니다.
WebdriverIO의 조건부 대기를 활용하여 validate, click, input, clear, back, scroll과 같은 동작을 수행하는 validateAndPerformActions 메서드를 리팩터링 하여 해결하게 되었습니다. 모바일 프로젝트의 자동화 설계 구조는 내가 더 이상 페이지 객체 패턴을 활용하지 않는 이유에 보다 상세히 작성되어 있습니다.
Flaky 테스트 문제를 해결하기 위해 WebDriverIO의 조건부 대기와 performance API를 활용해 UI 상태와 네트워크 요청 간의 동기화를 개선했습니다. 이후, 개선된 테스트 로직을 모바일과 웹 환경에서 자동화 테스트 인프라인 Appium Device Farm을 통해 꾸준히 실행하며 안정성을 점검했습니다.
이틀간 웹과 모바일에서 총 967번의 회귀 테스트를 실행했으며, 이 중 938개의 테스트가 성공하며 약 97%의 성공률을 기록했습니다. 실패한 29건(약 3%)은 대부분 자동화 인프라에서 발생하는 외부 요인으로 분석되었고, Flaky 테스트 문제는 완전히 해결된 것으로 확인되었습니다. 주요 실패 원인은 macOS 환경에서 발생한 프로세스 수나 파일 핸들 제한 초과 문제(fork failed: resource temporarily unavailable)로 확인되었는데, 현재는 코드 안정성을 확보하기 위해 회귀 테스트를 상시 실행하고 있지만 실제 운영 환경에서는 테스트 실행 빈도가 지금처럼 높지 않을 예정이라 이러한 문제가 운영 환경에 미치는 영향은 크지 않을 것으로 보고 있습니다.
운영 환경에서도 안정적인 테스트를 유지하기 위해 추가적인 최적화를 진행 중이고 대규모 병렬 테스트를 빠르고 일관성 있는 결과를 팀 전체가 확인할 수 있도록 노력하며 자동화 운영 프로세스 관점에서도 신뢰할 수 있는 테스트 기반을 마련해 나가고 있습니다.