brunch

You can make anything
by writing

C.S.Lewis

by 이지원 Oct 01. 2022

WebdriverIO에서 Cucumber로 전환하기

WebdriverIO describe/it 블록 구조의 웹, 모바일 E2E 테스트 코드를 작성해오다 WebdriverIO에 BDD 프레임워크인 Cucumber 셋업 후 Cucumber 구조와 코드로 전환하며 겪은 경험과 시행착오를 공유합니다.


WebdriverIO(describe/it)와 Cucumber(Given, When, Then) 코드는 어떻게 다른가?

클래스팅뿐 아니라 로그인 에픽에는 수많은 Feature가 있을 텐데요. 그중에서 '나는 사용자로서, 이메일과 비밀번호를 입력하여, 로그인할 수 있다.'라는 Feature의 2가지 시나리오에 대해, 동일한 코드를 통해 비교해보겠습니다.


WebdriverIO(describe/it)

첫 번째 시나리오는 단일 계정 로그인이 가능한지를 검증합니다. 두 번째 시나리오는 다중 계정 로그인 시도 시 계정 선택 화면이 나타나고, 계정 선택 후 로그인 이상 없는지 검증이 목적입니다.


최상위 describe는 하위 describe를 묶어주기 위한 용도로 사용했습니다. 두 번째 describe에서는 Feature를 뜻하는 '나는 사용자로서, 이메일과 비밀번호를 입력하여, 로그인할 수 있다.'를 작성했고, 하위 Scenario에서 공통적으로 실행되어야 할 작업들을 beforeEach에 작성했습니다. Feature의 하위 describe에는 단일 계정과 다중 계정 describe 작성을 위해 묶어주기 위한 용도로 '사용자는 이메일과 비밀번호로 로그인할 수 있다.' describe를 작성했습니다.


Cucumber(Given, When, Then)와 비교하기 위해 중점적으로 봐야 할 코드는 각 describe 안에 작성된 beforeEach인데요.


WebdriverIO(describe/it)에서는 Given, When을 표현하고자 beforeEach(또는 before)에 관련 코드를 작성합니다. Then을 검증하기 위해 필요한 사전 과정들은 모두 beforeEach에서 실행됩니다. E2E 테스트에서 중요한 점은 expect()가 담긴 it 블록의 Then이기 때문에, it 블록에는 최대한 expect()만 사용하여 가독성을 높일 수 있도록 작업했습니다.


위와 같은 방식으로 다른 시나리오들도 작성하게 되는데, 코드 작성에는 여러 가지 방법이 있을 것 같습니다. 어떤 구조로 작성할 것인지에 대해서는 WebdriverIO Boilerplates에 여러 프로젝트의 코드를 살펴보면 많은 도움이 될 것 같습니다.


아무쪼록 WebdriverIO(describe/it)의 코드 핵심은 Then을 검증하기 위해 필요한 사전 과정들은(Given, When) 모두 beforeEach에서 실행되는 점입니다. 그렇다면 WebdriverIO에 Cucumber를 셋업 하고, 동일한 Feature와 Scenario를 Cucumber의 BDD 철학을 기반으로 Given When Then으로 표현했을 시 어떻게 다른지 살펴보겠습니다.


Cucumber(Given, When, Then)

Cucumber는 Feature 파일이 필요합니다.


Feature 파일에 작성된 시나리오의 각 단계를 사용자가 작성한 순서대로 한 번에 하나씩 실행합니다. 이때 Cucumber는 stepDefinitions에 정의된 step 파일에서 일치하는 단계를(테스트 코드) 찾아서 실행하게 됩니다.


중요한 점은 각 단계에 동일한 텍스트가 있을 경우 Multiple step definitions match 에러를 뱉고, wdio.conf.js에 정의된 failAmbiguousDefinitions 값에 따라, 테스트 결과의 상태가 Pending 또는 Fail로 나타나게 됩니다.


관련해서 다양한 시행착오를 겪었는데, 조금 뒤에서 보다 자세하게 작성해보겠습니다. 동작 원리와 단계를 찾을 때 필요한 expressions에 대해서는 공식 문서를 참고하면 좋습니다.


앞서 Cucumber는 stepDefinitions에 정의된 step 파일에서 일치하는 단계를(테스트 코드) 찾아서 실행하게 된다고 하였는데요. 이제 step 파일에 있는 코드를 살펴보겠습니다.

WebdriverIO(describe/it)에 비해서 굉장히 간결해진 걸 알 수 있습니다.


또한 describe/it 구조에서는, beforeEach에 Given과 When에 해당하는 코드를 작성하다 보니, 사실상 어떠한 코드가 Given이고, 어떠한 코드가 When에 해당하는지 파악이 다소 어렵습니다.


코드를 한 줄씩 해석해야 하는 번거로움이 존재했었고 만약 로케이터에 변경이 생기거나 기능의 추가 또는 변경으로 인해 테스트 로직 수정이 필요할 경우 수정하기 편리할까 라는 의구심이 있었던 상태였습니다.


하지만 WebdriverIO에 Cucumber를 셋업 하여 동일한 시나리오에 대한 테스트 코드를 작성하고 검증해보니 직관적이고 디버깅도 쉬울뿐더러 중요한 건 각 단계별로 필요한 테스트 코드가 한눈에 파악되어 유지보수에 편리하다는 생각이 들었습니다.


(이 글의 주제와는 벗어나는 얘기지만 위 코드에서 각 단계에 쓰인 내용들은 Cucumber의 BDD 철학과 맞지 않은 부분이 있습니다.


입사 전 이미 작성 완료된 BDD 시나리오를 활용해서 E2E 테스트 자동화 및 BDD 프레임워크에서 구현하였는데, BDD 스타일로 테스트 코드 작성 간에 Cucumber의 각 단계별 철학과 맞지 않은 부분이 있다는 걸 뒤늦게 발견하게 되었습니다.


따라서 추후 개선을 해야 하는 부분이기에 각 단계별로 쓰인 코드가 Cucumber의 BDD 철학과 다른 점을 참고하시면 좋을 것 같습니다.


각 단계별 레퍼런스와 Cucumber의 안티 패턴에 대한 자료는 공식문서를 참고하면 좋습니다. 테스트 자동화 실무를 언제까지 할지는 모르겠지만, Cucumber를 사용하게 된다면 기존 작성된 시나리오 모두 Cucumber에서 얘기하는 철학과 일치하도록 변환하는 과정이 필요하고, Cucumber에서 정의한 안티 패턴을 회피하기 위한 여러 기능들에 대한 활용이 필요하네요.)


WebdriverIO(describe/it) / Cucumber(Given, When, Then)

동일한 시나리오에 대해 비교 분석을 해보았습니다. 확실히 Cucumber가 유지보수에 있어서 효과적일 것 같다는 생각이 들었습니다.


그런데 Cucumber 코드를 살펴보면 시나리오에 정의된 각 단계별 개수와, 코드의 개수가 다르다는 걸 알 수 있습니다. Cucumber는 각 단계마다 동일한 텍스트가 있는 상황을 중복으로 판단하기 때문인데요.


관련해서 이 글을 쓰기까지 엄청난(?) 시행착오를 겪은 경험을 공유하며 글 마칩니다.


토요일 아침부터 원인 분석을 시작해서 방금 끝마치게 되어 기쁘네요. 별것 아니지만 에러를 만나고 해결하는 과정 속에서 가장 큰 성장과 깨달음을 얻는 것 같습니다. 에러의 난이도와 크기는 중요치 않은 것 같아요. 내가 발생시킨 에러는 내가 해결하겠다는 의지가 중요한 것 같습니다. 각자의 속도에 맞춰 천천히 나아가도 괜찮겠다는 생각이 들었습니다.


WebdriverIO에서 Cucumber 코드로 전환 간에 겪은 시행착오


사건의 발단

3개월간의 Test Automation Workflow 구축 PoC가 끝나고 클래스터로 전환되었다. 예정된 스쿼드 소속되어 E2E 테스트 자동화 실무를 진행하게 되었다. BDD로 작성된 Epic에 대응되는 각각의 Feature와 Scenario에 대한 E2E 테스트 코드 작성이 필요했다. BDD 프레임워크를 사용하지 않았기에 describe-it 구조로 안드로이드 로그인 에픽에 대한 테스트 코드를 구현했다.



결과적으로 테스트 시나리오 의존성이 제거된 5개의 Feature, 22개의 Scenario를 520줄가량의 테스트 코드를 구현했다. 테스트 코드 구현 자체에는 큰 어려움이 없었다. 이미 PoC 단계에서 클래스팅 앱에 대한 구현 가능성을 직접 확인했었고, 그 과정에서 이미 수많은 시행착오를 겪으며 성장해왔기 때문이다.



다만, PoC때 구현한 코드와는 다르게 실무 Deep Dive를 진행하다 보니 새로운 고민들이 나타나기 시작했다. 병렬 테스트에 중요한 테스트 시나리오 의존성 제거, 유지보수 가능한 테스트 코드와 구조와 같은 것들이 고민이었다. 다행히도 소속된 테크 리더님께 받은 피드백을 통해 의존성이 제거된 테스트 코드를 작성하게 되었다.



안드로이드 구현을 끝내고 리뷰를 거쳐 아이폰과 웹에서 동작하는 테스트 코드를 작성하려던 준비를 하던 찰나, 사용 중인 테스트 프레임워크에 BDD 프레임워크를 연동하여 작성된 코드를 BDD 프레임워크 구조로 변환 필요한 상황이 생겼다. 일을 하는 실무자 입장에서 이중 작업이라 느껴질 만큼 굉장히 번거롭고 귀찮은 작업이라 생각할 수 있을 법했지만, 여러 시행착오를 겪으며 성장이 필요한 지금의 상황에선 좋은 기회로 생각했다. 이미 로케이터와 로직 작성은 완료되었기에 프레임워크 셋업 후 Cucumber 구조로 변경만 하면 될 거라 생각했다.



하지만 역시나 늘 그랬듯 생각지 못했던 이슈들이 생겼고(셋업 과정에서 생긴 여러 에러) 끝내 해결했지만, Cucumber 공식 문서를 학습하며 코드를 다시 작성하던 중, 도저히 이해가지 않는 이슈가 발생했다. 그리고 오늘 그 이슈가 해결되었다. 그 과정을 돌이켜보고 느낀 점과 생각을 기록으로 남겨본다.



당면한 문제와 해결 과정

1. Pending과 Skipped을 마주하다

PoC 경험을 토대로 작업을 진행할 때 문제를 잘게 쪼개어 하나씩 해결하며 최종적으로 의도한 모습대로 만들고자 했다. 하나의 시나리오에 대응하는 코드가 완성될 때마다 실행하며 검증했고, 해당 피처에 대한 7개 시나리오 모두 문제없음을 확인했다.



이후 전체 코드와 시나리오가 의존성 없이 동작하는지, 함께 실행했을 때 발견되는 문제는 없는지에 대한 최종적인 검증을 진행하였는데, 여기서 문제가 발생하게 되었다. 바로 Cucumber Report에서 Pending과 Skipped 상태가 발견된 것이다.



혼자만의 추측으로 이어지는 비효율적인 해결을 최소화하고자 바로 Cucumber 공식문서에서 Step Results를 살펴봤다. 우선 내부적으로 어떠한 상태일 때 Pending이 나타나는지를 알아야 하기 때문이다.

https://cucumber.io/docs/cucumber/api/

pending 메서드를 호출할 때 발생하는 상태로 파악이 되었고, 문제 원인을 step 파일에 정의된 각 단계(Given, When, Then)의 코드에 문제가 있을 것으로 좁히게 되었다. Skipped 상태는 Pending 상태가 해결되면 자연스레 해결될 문제로 파악했고, Pending 상태가 왜 발생하는지 원인을 찾기 시작했다.


2. Cucumber Report로 문제를 분석하다

WebdriverIO에 셋업 한 Cucumber Report가 생각보다 디버깅에 많은 도움이 되었다. 이전에 Cucumber를 사용하기 전에는 Spec과 Allure Report를 사용했었는데, 직관적이지 않고 복잡하다 느꼈다. 하지만 Cucumber Report는 꽤나 직관적이고 정확히 어느 부분에서 오류가 발생했는지 파악이 쉬웠다.



하지만 위 내용만을 확인해서는 정확한 원인 파악이 힘들었다. 단서가 되었던 상황은 시나리오를 개별적으로 실행할 땐 문제가 없었고, 전체를 한 번에 실행할 때 문제가 된 것인데, 관련해서 특이점을 찾을 수 없었다.



Cucumber Pending 관련해서 수많은 구글링을 시도했지만 나와 비슷한 사례를 찾지 못했다. 하지만 이미 내가 겪고 있는 문제는 과거 누군가 겪었을 테고 그에 대한 해답이 분명 인터넷 어딘가에 존재할 거라 생각했다. 구글링이 부족하거나, 원인 파악에 필요한 키워드로 검색하지 않았거나, 둘 중 하나가 문제였다. 우선 문제 원인에 필요한 키워드를 찾기 위해, 문제를 다시 정의했다.



3. 문제 재정의

시나리오를 하나씩 실행할 때는 문제가 없지만, 왜 전체를 실행할 때 문제가 생기는지를 알아야 했다. 관련 조사를 진행하던 중, 왜 Fail 발생하지 않고 Pending이 발생했는지 의문이 생겼다. 차라리 Fail 발생했다면 에러가 좀 더 명확하게 로그로 남겨졌을 텐데 하고서 아쉬움이 남았다. Pending 상태가 발생할 경우 Fail 처리되도록 하는 방법을 조사하기 시작했다.

조사 결과 WebdriverIO에서 failAmbiguousDefinitions 관련 옵션을 정의할 수 있었는데, Default 값이 false로 되어있었다.

wdoi.conf.js에서 관련 설정을 true로 변경했고 그 결과 Fail 로그가 나타났다.

문제의 원인은 Multiple step definitions match:였다. 에러를 보자마자 아차 싶었다. 입사 전 이미 작성된 에픽 단위의 피처와 시나리오들은 E2E 테스트 자동화와 BDD 프레임워크 사용을 크게 고려하지 않고 기능 개발을 위해 작성된 것들이었다.



그러다 보니 매뉴얼 테스트와 기능 개발하기엔 문제가 없었지만 이후 E2E 자동화를 위해 Cucumber 프레임워크를 사용하게 되면서 Cucumber가 추구하는 BDD 방향과 철학에 위배되는 단계들이 존재했었다.



클래스팅 서비스에는 수많은 피처가 있고, 각 피처에는 수많은 시나리오가 있는데, 각 단계마다 동일한 텍스트로 쓰인 것들이 존재했다. Cucumber 프레임워크를 사용하기 전에는 문제가 될지 인지하지 못했지만, 위와 같은 시행착오를 겪으며, Cucumber가 추구하는 BDD의 철학을 살펴보게 되었다.



4. 문제를 해결하다

문제를 재정의하고 관련 오류로 구글링 결과, 동일한 문제가 스택오버플로우에서 발견되었다.

피처 파일의 각 단계는 유지하되 테스트 코드에서 중복된 단계를 제거함으로써 문제는 해결되었다.

매거진의 이전글 안드로이드 에뮬레이터 E2E 병렬 테스트 방법
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari