다양한 브라우저와 디바이스 및 OS 조합에서 여러 테스트를 동시에 실행하는 병렬 테스트(Parallel Tests)는 E2E 테스트 자동화에서 중요합니다. 병렬 테스트 효과로 인해 자주, 더 빨리 릴리즈가 가능하기 때문인데요.
1개의 Epic, 5개의 Feature, 22개의 Scenario를 520줄가량의 테스트 코드로 자동화시킨 경험을 통해, 병렬 테스트 실행에 중요한 시나리오 의존성 제거 방법에 대해 알아보겠습니다.
E2E 구축 환경
테스트 프레임워크는 WebdriverIO와 Appium을 활용 중입니다. 프로그래밍 언어는 현재 JavaScript를 사용 중이지만 가까운 시일 내에 TypeScript로의 전환을 준비 중입니다. 리얼디바이스 테스트 환경 구축은 WebdriverIO에 클라우드 리얼디바이스 BrowserStack을 연동해서 구축했습니다.
E2E 테스트 자동화 패턴
POM이라 불리는 페이지 개체 모델을 활용했습니다. 웹, 모바일 요소를 저장하고 관리하기 위한 page와 screen을 작성했습니다. UI 요소 변경 또는 특정한 액션에 변경이 있을 경우 유용했습니다. 또한 특정 페이지와 스크린에 대한 로직이 작성되면 다른 시나리오에서 재사용이 가능했습니다. 즉 코드를 다시 작성할 필요가 없어지면서 많은 시간과 노력에 따른 리소스를 줄일 수 있었습니다.
뿐만 아니라 3개월 전 POM 활용 전에는 테스트 로직이 작성된 파일 내부에서 모든 요소를 관리함으로써 스크립트 가독성이 나빴지만, 현재는 재사용 가능한 로직과 테스트에 필요한 요소를 별도로 관리함으로써, 테스트 로직 가독성이 이전에 비해 좋아졌고, 디버깅도 수월해졌습니다.
병렬 테스트 실행에 중요한 개념
테스트 시나리오 의존성
그간 테스트 자동화를 진행하며 느낀 점과 배운 점은 테스트 코드에는 의존성이 없어야 한다는 점인데요. 처음 테스트 코드 작성 시에는 의존성에 대한 개념이 잡혀있지 않았습니다. 왜냐하면 지난 6년간 경험했던 매뉴얼 테스트 진행 간에는 시나리오 의존성 문제를 사람의 사고와 생각으로 회피할 수가 있기 때문인데요.
물론 제가 몸담았던 게임 분야의 테스트 같은 경우 특정 콘텐츠의 상태가 다른 콘텐츠에 영향을 주는 경우가 발생하므로 테스트 흐름을 고려해서 테스트 설계를 해야 하지만, 대부분 사람이 진행하는 테스트에는 시나리오 의존성 문제가 큰 이슈가 되었던 적은 없었습니다.
하지만 테스트 자동화를 위해 작성한 테스트 코드에는 Given, When, Then 구조에 따른 로직이 명확히 작성되어있고, 해당 로직에 필요한 액션만을 실행 순서에 의해 진행되기 때문에, A 시나리오의 결과가 B 시나리오 실행 흐름에 영향을 미친다면 테스트 결과를 신뢰할 수 없게 되고, 병렬 테스트 또한 진행할 수 없게 됩니다.
테스트 시나리오 의존성을 피하는 방법
이러한 의존성 문제를 제거하기 위해서는 첫 번째로 모든 시나리오는 서로 독립적으로 구현되어야 합니다. 특히 특정한 시나리오가 종속적인 경우 특정 순서로 실행되어야 하므로 병렬 실행이 불가합니다. 테스트 코드 작성은 실행 순서에 관계없이 언제 어떠한 테스트 시나리오가 실행되어도 검증에 문제가 없도록 작성되어야 합니다.
두 번째로는 테스트 데이터 관리인데요. 핵심은 이전 시나리오에서 사용된 또는 생성된 데이터를 다음 시나리오가 의존하지 않는 형태로 작성되어야 합니다. 즉 1개의 시나리오에 필요한 모든 테스트 데이터는 다른 시나리오와 독립된 형태로 작성되어야 하는데요. 테스트 코드 작성 전에 테스트 시나리오에 필요한 데이터를 분리하거나 불가피하게 분리가 힘들다면, 각 시나리오가 끝나는 시점에서 이전과 동일한 상태로 데이터를 복원하는 작업이 필요합니다.
테스트 Hooks로 의존성 제거하기
(before, beforeEach, after, afterEach)
모든 시나리오는 서로 독립적으로 구현되어야 하고 시나리오에 필요한 모든 테스트 데이터는 독립된 형태로 작성해야 한다는 중요한 개념을 알아보았는데요. 테스트 프레임워크에서 제공하는 Hooks를 통해 의존성 문제를 코드 방식으로 제거할 수 있습니다. 아래 코드를 통해 좀 더 자세히 알아보겠습니다.
최상위 describe에 epic에 해당하는 내용을 작성합니다. 하위 describe에는 Feature를 작성하고 Feature의 하위 describe에는 Scenario를 작성합니다. 여기서 주목할 점은 Feature가 작성된 describe안의 beforeEach인데요. 앱을 종료하고 실행하는 코드가 작성되어 있습니다. 따라서 Feature안의 시나리오(describe)가 실행되기 전에 앱을 종료하고 실행하는 코드가 매번 실행되므로, 다른 시나리오의 결과와 관계없이 독립된 테스트 실행을 보장합니다. 시나리오 안의 beforeEach에서는 Given When And와 같은 액션과 상태를 작성하고, it(Then) 블록에서는 테스트 결과 검증에 필요한 expect()를 작성합니다.
위 시나리오도 살펴보면, 1개의 Feature에 2개의 Scenario가 작성되어 있습니다. 각 시나리오가 실행되기 전 앱을 종료하고 실행하는 동작을 통해 의존성을 제거했고, beforeEach에서는 Given, When, And와 같은 구조에 해당되는 테스트 로직을 작성했습니다. 이전 시나리오에 활용된 테스트 데이터로 인해 다음 시나리오에 영향 가지 않도록 테스트 데이터 또한 분리된 상태입니다.
위와 같은 구조로 1개의 Epic, 5개의 Feature, 22개의 Scenario에 해당되는 E2E 테스트 코드를 작성하게 되었고, 독립된 시나리오와 분리된 테스트 데이터로 시나리오 의존성을 제거함으로써 병렬 테스트 가능한 코드를 만들게 되었습니다.
마치며
지금까지 1개의 Epic, 5개의 Feature, 22개의 Scenario를 520줄가량의 테스트 코드로 자동화시킨 경험을 통해 병렬 테스트 실행에 중요한 개념과 활용법에 대해 알아보았습니다.
테스트 코드는 서로 의존적이면 안되고 언제든지 원할 때 필요로 하는 시나리오를 검증하기 위해선, 자동화 구축 초기 단계부터 병렬 테스트 가능한 코드를 작성하고 보다 확장성 있는 구조로 유지보수하는 것이 중요하다 느꼈습니다.
앞으로도 자동화 관련된 수많은 챌린지가 눈앞에 보이고 기다려집니다. 이제 막 E2E 테스트 자동화 분야에 발 딛게 되었는데 E2E 테스트 코드 또한 코드를 작성하는 행위이기 때문에 보다 엔지니어 관점에서 좋은 테스트 코드를 작성할 수 있도록 노력하고자 합니다.
긴 글 읽어주셔서 고맙습니다.