병렬 테스트를 제대로 수행하는 방법: 파이프라인이 실패하는 이유(그리고 해결 방법) 

모든 QA 테스터는 자동화 테스트 도구 모음이 시간이 지남에 따라 점점 커지는 것을 지켜보는 답답한 심정을 잘 알고 있습니다. 5분짜리 간단한 스모크 테스트로 시작했던 것이 점차 2시간짜리 대규모 실행으로 진화합니다. 새로운 기능이 추가될 때마다 Appium 모바일 스크립트나 Selenium 브라우저 플로우가 몇 개씩 추가됩니다. 얼마 지나지 않아 개발자들은 발을 동동 구르고, 릴리스 관리자들은 업데이트를 요구하며, QA는 은밀하게 불편한 꼬리표를 달게 됩니다. 배송 병목 현상. 

그래서 테스트 프레임워크 설정 파일을 열고 동시 실행 설정을 발견하면 "왜 안 되겠어?"라는 생각이 듭니다. 

스위치를 켜고 스위트를 다시 실행합니다. 속도 향상이 되어야 할 작업이 설명할 수 없는 오류로 가득 찬 빌드 결과로 이어집니다. 

세 시간 후, 당신은 똑같은 커밋을 다시 실행합니다. 이번에는 통과합니다. 다음 빌드를 기다립니다. 그런데 다른 두 테스트가 실패합니다. 당신은 자신이 미쳐가는 건 아닌지 의심하게 됩니다. 

실제로 파이프라인 속도를 높인 게 아닙니다. 값비싼 고속 무작위 오류 발생기를 만든 셈입니다. 

 문제점: 병렬 스위치의 허상

현대 테스트 자동화에서 흔히 잘못 알려진 사실은 순차 테스트에서 병렬 테스트로 전환하는 것이 단순히 설정만 변경하면 되는 간단한 작업이라는 것입니다. 하지만 그렇지 않습니다. 이는 복잡한 과정입니다. 근본적인 건축적 변화. 

테스트를 순차적으로 실행하면 마치 좁은 도로에서 예의 바르게 운전하는 운전자들처럼 작동합니다. 하나의 테스트가 실행되고, 뒷정리를 한 다음, 다음 테스트가 시작될 수 있도록 자리를 비켜줍니다. 한 번에 하나의 작업만 수행되므로 모든 것이 질서정연하게 진행됩니다. 

기본 테스트 스위트를 재구성하지 않고 병렬 처리 스위치를 켜면, 동일한 드라이버를 신호등 없는 혼란스러운 4차선 고속도로에 투입하고 즉시 연쇄 추돌 사고가 발생하는 이유를 의아해하는 것과 같습니다. 

갑자기, 하나씩 실행할 때는 문제없이 통과했던 테스트들이 병렬로 실행되면서 알 수 없는 요소 시간 초과, 드라이버 연결 끊김 또는 예기치 않은 세션 종료와 같은 오류와 함께 무작위로 실패하기 시작합니다. Jenkins에서 빌드를 다시 실행하면 실패했던 테스트는 통과하지만, 다른 세 가지 테스트가 실패합니다. 하이젠버그에 오신 것을 환영합니다. 

실패 원인: 네 가지 주범

순차적으로 실행할 때는 완벽하게 통과하던 테스트들이 병렬로 실행하는 순간 오작동하는 이유는 무엇일까요? 거의 항상 한 가지 원칙으로 귀결됩니다. 서로 발을 밟는 테스트. 

테스트 불안정성에 대한 연구는 이러한 실패를 여러 범주로 분류합니다. 제37회 IEEE/ACM 자동화 소프트웨어 엔지니어링 국제 컨퍼런스(ASE '22)에서 발표된 문헌 검토에 따르면, 불안정한 테스트는 일반적으로 세 가지 원인으로 발생합니다. 테스트 기반 불안정성(결함 있는 스크립트, 불안정한 식별자, 비동기 대기, 순서 의존적 테스트), 환경 기반 불안정성(네트워크 상태, 리소스 경합, 다중 환경 충돌), 그리고 제품 기반 불안정성(애플리케이션 자체의 경쟁 조건 및 메모리 누수)입니다. 아래 네 가지 원인은 병렬 테스트 스위트에서 이러한 범주의 실제적이고 일상적인 발생 사례입니다. 

데이터 충돌: 두 가지 테스트, 하나의 기록 

두 사람이 스프레드시트의 동일한 셀을 동시에 편집한다고 상상해 보세요. 테스트 A가 사용자 프로필(user_id: 101)을 업데이트하는 동시에 테스트 B가 user_id: 101을 삭제하면 둘 중 하나는 오류가 발생합니다. 두 테스트 모두 잘못 작성된 것이 아니라, 동일한 데이터를 두고 충돌이 발생한 것입니다. 

평행 세계에서는 이러한 충돌이 대규모로 발생합니다. 공유 스테이징 데이터베이스를 대상으로 여러 테스트를 동시에 실행하는 팀은 조만간 이러한 문제에 부딪힐 가능성이 매우 높습니다. 

드라이버 유출: 리모컨 공유하기 

Selenium으로 브라우저를 제어하든 Appium으로 모바일 앱을 제어하든, 스크립트에는 전용 드라이버 세션이 필요합니다. 흔히 발생하는 문제는 정적 드라이버를 스레드 간에 공유하는 것입니다. 

// 안티 패턴: 병렬 스레드 간 드라이버 공유
공개 정적 인 Web드라이버 드라이버;

@테스트
공개 무효화 testA() {
  드라이버.get(“https://example.com”);
  driver.findElement(By.id("제출하다")).딸깍 하는 소리();
}

@테스트
공개 무효화 testB() {
  driver.findElement(By.id("사용자 이름")).sendKeys("관리자");
  // 죄송합니다: testA는 지금 다른 페이지에 있을 수도 있습니다.
} 

마치 두 사람이 TV 리모컨을 두고 싸우는 것 같아요. 한 사람이 페이지를 넘기는 동안 다른 사람은 클릭하는 중이죠. 완전 난장판이에요. 

극작가조차도, 분리하지 못하는 경우가 있습니다. 브라우저 컨텍스트 인스턴스는 쿠키와 로그인 세션이 동시 테스트 간에 공유되도록 합니다. 

// 안티패턴: Playwright에서 컨텍스트 공유
const를 sharedContext = 기다리다 브라우저.newContext();
// 여러 테스트에서 sharedContext를 사용하므로 세션이 겹칩니다. 

해결책: 사용 스레드로컬 (자바) 또는 작업자별 컨텍스트 격리 (자바스크립트/극작가) 

BDD 상태 오염: 오이 함정 

Cucumber와 같은 프레임워크는 일반적인 영어로 테스트 코드를 작성하는 데 매우 유용합니다. 하지만 자동화 엔지니어는 시나리오 상태를 전역 변수에 저장하는 경우가 많습니다. 

// 안티패턴: Cucumber에서 공유되는 단계 상태
공개 정적 인 사용자 로그인됨;

@주어진("사용자가 로그인했습니다.")
공개 무효화 userLoggedIn() {
  loggedInUser = 사용자(“alice@example.com");
}

@언제(사용자가 주문을 합니다)
공개 무효화 placeOrder() {
  // 스레드 A가 loggedInUser를 덮어썼을 수도 있습니다.
} 

병렬로 실행될 때, 스레드 A는 아무런 경고 없이 기존 내용을 덮어씁니다. 로그인한 사용자 스레드 B가 주문 페이지를 확인하는 도중에 무작위 어설션 오류가 발생합니다. 해결 방법: 의존성 주입 (PicoContainer, Spring)을 사용하면 각 시나리오 인스턴스가 자체 상태 객체를 갖게 됩니다. 

"남은 것"의 함정: 버려진 데이터와 장치 

병렬 파이프라인 실패의 가장 큰 원인은 바로 정리 작업의 누락입니다. 테스트가 실행 도중 중단되면 정리 작업이 생략되어 "고아 데이터"가 남게 됩니다. 예를 들어 잠긴 장바구니, 중복된 이메일 주소, 또는 다음 테스트에서 사용하기 전에 제대로 재설정되지 않은 장치 정보 등이 발생할 수 있습니다. 

이는 물리적 및 가상 테스트 장치뿐만 아니라 데이터에도 적용됩니다. 공유 장치 그리드에서 테스트가 자체적으로 정리를 하지 않으면(남은 앱 상태, 캐시된 자격 증명, 오래된 앱 설치) 해당 장치에서 실행되는 다음 테스트가 조용히 손상될 수 있습니다. Digital.ai Appium 테스팅 모범 사례 가이드 이 솔루션은 독립적인 테스트 방법, 정적 변수 사용 방지, 병렬 실행 전반에 걸친 적절한 드라이버 관리, 세션 간 장치 정리 등을 통해 그리드를 안정적인 상태로 유지함으로써 이러한 문제를 직접적으로 해결합니다. 

순차적인 환경에서는 다음 테스트가 다른 부분을 확인하기 때문에 이전 테스트에서 발생한 오류를 그대로 두어도 괜찮을 수 있습니다. 하지만 병렬적인 환경에서는 3초 후에 다른 작업자 스레드가 그 오류를 처리하려고 시도하면서 문제가 발생할 수 있습니다. 이러한 오류는 연쇄적으로 발생하여, 하나의 부실한 테스트가 여러 하위 테스트에 걸쳐 실패를 유발할 수 있습니다.

비용: 숨겨진 세금과 같은 변덕스러움

병렬 테스트 스위트가 불안정해지면 대시보드의 빨간색 표시등에 그치는 것이 아니라, 팀 문화 자체를 근본적으로 훼손하고 자원을 낭비하게 만듭니다. 

확률 복리 수학 

병렬 파이프라인의 수학적 원리를 생각해 보세요. UI 테스트 스위트의 크기가 아주 작다면, 플레이크 비율 1%이러한 테스트를 50번 연속으로 실행하면 녹색 빌드를 볼 확률이 상당히 높아집니다. 

하지만 50개의 테스트가 동시에 실행되는 8개의 병렬 워커에 분산되면 1%의 오류율이 누적됩니다. 이는 측정된 업계 통계가 아니라 기본적인 확률이며, 병렬화할수록 오류율이 개선되는 것이 아니라 악화되는 이유를 보여주는 예시 계산입니다. 

병렬 작업자  누적 합격률  오판율 
1 (순차적)  99.5%  0.5% 
2  98.0%  2.0% 
4  96.1%  3.9% 
8  92.3%  7.7% 
16  85.2%  14.8% 

 

연쇄 반응: 여러 워커가 경쟁 조건(데이터 충돌, 락 경합, 고아 세션)을 유발하여 코드에는 문제가 없는데도 빌드가 실패합니다. 개발자들은 운 좋게 성공적인 조합을 찾을 때까지 빌드를 반복합니다. 

경고 피로와 재실행 문화 

테스트 스위트가 무작위로 실패할 때, 엔지니어링 팀은 경고 피로감을 겪습니다. ASE '22 테스트 불안정성 관련 문헌 검토에서 지적했듯이, 특히 주니어 개발자들은 "불안정한 테스트 케이스를 무시하거나 통과할 때까지 계속 반복 실행하여 잠재적으로 위험한 결함을 보고하지 않는 경향이 있습니다." 같은 연구에서는 불안정성의 근본 원인을 조사하는 데 시간이 많이 소요되며, 불안정성이 오경보로 판명될 경우 그 비용이 완전히 낭비된다고 지적합니다. 이러한 상황은 팀이 문제 해결에 적극적으로 나서지 않고 "그냥 다시 실행"하는 습관을 부추깁니다. 

병렬 테스트 스위트를 다시 실행할 때마다 실제 컴퓨팅 시간과 클라우드 그리드 예산이 소모됩니다. 정확한 비용은 팀 규모, 스위트 사용 기간 및 인프라 가격에 따라 다르지만, 추세는 예측 가능합니다. 근본적인 격리 문제를 해결하지 않는 팀은 격리를 통해 예방할 수 있었던 문제에 대해 엔지니어링 시간과 인프라 비용 측면에서 반복적으로 비용을 지불하게 되는 경향이 있습니다.

패턴: 격리된 테스트 환경 구축

병렬 테스트 간의 충돌을 막으려면 테스트 실행기들이 서로 정보를 공유하지 못하도록 해야 합니다. 각 테스트 실행기는 자체적인 독립적인 환경 내에서 작동해야 합니다. 

임시 인프라: 컨테이너 패턴 

최근 개발팀들은 모든 병렬 워커를 단일 스테이징 데이터베이스로 연결하는 대신, 컨테이너화를 통해 수명이 짧은 환경을 활용합니다. Docker를 사용하면 워커 스레드별로 전용의 격리된 데이터베이스 컨테이너를 즉시 생성하고 테스트가 완료되는 즉시 삭제할 수 있습니다. Kubernetes 기반 CI 러너는 이러한 기능을 더욱 확장하여 파이프라인 실행마다 전체 일회용 테스트 환경을 예약 실행할 수 있습니다. 

대규모 모바일 및 웹 테스트를 위해, Digital.ai 지원 Appium, Selenium 및 크로스 브라우저 테스트를 병렬로 실행할 수 있는 실제 디바이스 클라우드 그리드를 제공하며, 세션 간 디바이스 정리를 통해 다음 테스트가 이전 실행에서 남은 앱 데이터나 구성을 상속받지 않고 깨끗한 상태에서 시작되도록 합니다.  

Jenkins 파이프라인의 각 워커 스레드는 동적 UUID 기반 테스트 사용자, 개인 Docker 데이터베이스 컨테이너 및 전용 브라우저/장치 컨텍스트를 포함하는 자체 격리된 환경을 제공받습니다. 

브라우저 및 장치 컨텍스트 영역 설정 

셀레늄의 경우 해결책은 스레드입니다.safe 드라이버 할당: 

// 패턴: 스레드 로컬 드라이버 격리
사설 정적 인 최후의 스레드로컬 드라이버 스레드 = ThreadLocal<>();

공개 정적 인 WebDriver getDriver() {
  if (driverThread.get() == null로) {
    driverThread.set( ChromeDriver());
  }
  return driverThread.get();
}

안녕하세요.
공개 무효화 티어다운() {
  WebDriver driver = driverThread.get();
  if (운전자 != null로) {
    드라이버 종료();
    driverThread.remove();
  }
} 

Appium을 사용하는 경우, 그리드가 스레드별로 깨끗하고 공유되지 않는 디바이스 인스턴스를 동적으로 프로비저닝하는지 확인하십시오. 각 워커는 자체적인 디바이스 인스턴스를 가져야 합니다. AppiumDriver 독특한 세션 세션 ID그리고 다음 세션에서 해당 장치를 사용하기 전에 기본 장치를 재설정해야 합니다.  

이것이야말로 바로 그러한 기기 위생 관리 방식입니다. Digital.ai 지원 이 플랫폼은 다음과 같은 기능을 지원하도록 설계되었습니다. 테스트 스크립트는 모든 세션이 끝날 때마다 quit() 함수를 호출해야 하며, 플랫폼은 세션 간 자동 장치 정리 주기를 통해 이를 지원합니다. 따라서 다음 병렬 작업자는 개별 테스트가 종료 후 장치를 얼마나 잘 정리했는지에 관계없이 항상 깨끗한 장치를 사용할 수 있습니다. 지침과 결합하여 Digital.ai 테스트의 병렬 실행 모범 사례 정적 변수를 사용하지 않고 테스트 방법을 독립적으로 유지함으로써, 모든 장치는 실행 간에 알려진 정상 상태를 유지합니다. 

극작가로서, 포용하라 브라우저 컨텍스트 객체: 각 컨텍스트는 격리된 시크릿 창처럼 작동하므로 토큰과 저장소는 동시 실행 간에 절대 유출되지 않습니다. 

// 패턴: 테스트별로 분리된 Playwriter 컨텍스트
const를 브라우저 = 기다리다 크롬.시작하다();
const를 컨텍스트1 = 기다리다 브라우저.newContext();
const를 컨텍스트2 = 기다리다 브라우저.newContext();
// 각 컨텍스트는 자체적인 쿠키, 로컬 스토리지, 세션 스토리지를 가지고 있습니다. 

합성 데이터 생성: UUID 쉴드 

복잡한 데이터베이스 재설정 없이 데이터 충돌을 방지하는 방법은 무엇일까요? 한 가지 확실한 방법은 런타임에 생성되는 고유 식별자(UUID)를 사용하는 것입니다. 이렇게 하면 병렬 작업자가 동일한 환경에서 동시에 실행되면서도 동일한 레코드를 건드리지 않게 됩니다. 

// 패턴: UUID 기반 합성 데이터
@테스트
공개 무효화 testCheckout() {
  문자열 uniqueUserId = UUID.randomUUID().toString();
  사용자 사용자 = 사용자(고유사용자ID + "@ example.com", 고유사용자ID);
 
  // 스레드 A는 사용합니다 user-a1b2c3d4@example.com
  // 스레드 B는 사용합니다 user-x9y8z7w6@example.com
  // 충돌 없음
} 

합성 데이터 생성과 임시적이고 자체적으로 정리되는 환경을 결합함으로써 팀은 테스트 종속성을 완전히 제거할 수 있습니다. 테스트는 더 이상 공유 상태를 두고 충돌하지 않으며, 각 테스트는 자체적으로 동적으로 생성된 정리된 데이터 세트를 대상으로 실행됩니다. AI로 생성된 테스트 데이터와 자동화된 환경 정리 기능을 포함한 이러한 파이프라인 구축에 대한 자세한 내용은 다음을 참조하십시오. 개발자를 위한 합성 데이터 생성 및 자체 정리 테스트 환경 가이드.

플레이북: 최소한의 로드맵

기존 시스템을 병렬 실행에 맞게 리팩토링하는 데 대대적인 재작성이 필요한 것은 아닙니다. 다음 네 단계를 순서대로 따르면 됩니다. 

  1. 감사 상태 관리. 정적 필드와 공유 드라이버를 스레드로 대체하세요.safe 패턴: Cucumber의 의존성 주입 스레드로컬 Selenium/Appium의 경우, 분리됨 브라우저 컨텍스트 Playwright 인스턴스를 확인하십시오. 세션 간에 장치 및 브라우저 인스턴스가 제대로 정리되었는지 확인하십시오. 
  1. 업무량을 균형 있게 분배하세요. CI 시스템의 테스트 지속 시간 기록을 활용하여 부하가 큰 테스트를 먼저 분산시키면, 한 작업자만 부하를 독차지하는 대신 모든 작업자가 거의 동시에 테스트를 완료할 수 있습니다. 
  1. 격리 후 점진적으로 규모를 확장하십시오. 취약하거나 속도 제한이 있는 테스트는 순차적으로 실행되도록 태그를 지정하세요. 처음에는 소수의 병렬 워커로 시작하여 발생하는 경쟁 조건을 해결한 다음 규모를 확장하세요. 
  1. 검증하고 반복합니다. 규모 확장에 따라 오탐률과 빌드 시간을 추적하세요. 재실행률이 감소하는 것은 테스트 스위트가 단순히 빨라지는 것이 아니라 실제로 더 안정적으로 작동하고 있다는 좋은 신호입니다.
  1. 향후 전망: 게이트키퍼에서 속도 촉진자로

향후 전망: 게이트키퍼에서 속도 촉진자로

수년간 QA는 최종 관문으로 여겨졌습니다. 자동화된 테스트 도구가 스크립트를 천천히 실행하는 동안 릴리스를 지연시키는 팀으로 인식되었죠. 테스트가 최종 관문 역할을 한다는 인식이 있었습니다. 둔화 공학. 

테스트 스위트의 기본 아키텍처(격리된 테스트 데이터, 스레드 등)를 수정함으로써safe Selenium, Playwright 또는 Appium에서의 세션 관리와 테스트 그리드에서 제대로 정리된 장치를 사용하는 병렬 테스트는 이러한 인식을 완전히 바꿔놓을 수 있습니다. 예전에는 몇 시간씩 걸리던 테스트 스위트가 이제는 몇 분 만에 신뢰할 수 있는 피드백을 제공할 수 있게 되었습니다. safe 대규모로 운영하기 위해. 

테스트의 미래는 단순히 더 많은 테스트를 더 빠르게 실행하는 것에만 국한되지 않습니다. 테스트를 제대로 실행하는 것 자체가 중요합니다. 바르게—격리, 청결한 환경, 그리고 결과에 대한 확신을 바탕으로. 

참조 및 추가 읽을거리 

  1. Digital.ai 테스팅: 병렬 테스트 – 모범 사례: 독립적인 테스트 방법, 정적 변수 사용 금지, 병렬 처리 및 로깅, 스레드 사용 등에 대한 공식 지침safe Appium용 드라이버 관리. 
  1. 개발자를 위한 합성 데이터 생성 및 자체 정리 테스트 환경 가이드합성 데이터를 사용하여 일시적이고 자체적으로 정리되는 테스트 환경을 구축합니다. 
  1. Ngo, K., Nguyen, V., & Nguyen, T. (2022). “테스트 불안정성 연구: 단위 테스트에서 시스템 테스트까지.” 제37회 IEEE/ACM 자동화 소프트웨어 엔지니어링 국제 컨퍼런스(ASE '22). 불안정한 테스트를 테스트 기반, 환경 기반 및 제품 기반으로 분류하고, 이를 감지하고 수정하는 데 사용되는 학계 및 산업계 도구를 조사하는 문헌 검토. 
  1. Selenium WebDriver 문서Selenium 공식 문서에서 드라이버 세션 및 브라우저 자동화에 대한 내용을 확인할 수 있습니다. 
  1. Selenium: 테스트마다 새 브라우저 시작Selenium의 공식 테스트 격리 지침입니다. 
  1. 극작가: 고립 (브라우저 컨텍스트)Playwright가 테스트 격리를 위해 BrowserContext를 사용하는 방법에 대한 공식 문서입니다. 
  1. 테스트 피라미드마틴 파울러의 테스트 격리 및 범위 세분화에 대한 기본 참고 자료입니다. 
  1. Digital.ai 테스팅: 확장 가능한 모바일 및 웹 크로스 브라우저 테스팅 클라우드실제 iOS/안드로이드 기기 및 브라우저에서 병렬 실행을 위한 엔터프라이즈 인프라. 

 

당신은 또한 좋아할 거라