brunch

You can make anything
by writing

C.S.Lewis

by 이지원 Oct 01. 2022

WebdriverIO에서 Pytest 전환 후기

지난 4개월간 학습하고 실무에 활용했던 WebdriverIO에서 처음 다뤄보는 Pytest 프레임워크로 전환한 과정을 공유합니다.


사용 언어, 프레임워크, IDE 등과 같은 개발 환경 자체가 바뀌었지만 감정을 드러내지 않고 평정심을 찾고자 모든 얽힘과 설킴에 대한 맹목적인 이해와 더불어 주어진 상황을 엔지니어로서의 성장 관점에서만 해석하다 보니 보다 성숙해진 태도를 배우게 되었습니다.


소프트웨어 엔지니어는 문제를 정의하고 문제를 해결하는 사람이다 라는 조언이 생각났습니다. 문제를 정의하고 문제를 해결하려면 언어와 환경이 바뀌는 것에 대한 부담감을 없애야 할 텐데, 오히려 모든 걸 새롭게 다시 시작하는 상황을 좋은 기회로 받아들였습니다.


그간 평일 주말 밤낮으로 열심히 해왔던걸 갈아엎은 상황에서도, 언어와 프레임워크 모두 새로 학습해야 하는 상황에서도, 어떠한 보상 심리와 격려를 바라지 않고 오로지 주어진 상황을 성장의 관점에서만 해석하니, 모든 것들이 SDET의 새로운 기술 스택을 쌓을 수 있는 기회로만 보였습니다.


많은 회의감이 들었던 요즘이었지만, 오랜 기간 신뢰가 쌓인 SW QA 동료분들과 리더분들 그리고 주최 예정인 Testing Korea Tech 컨퍼런스를 지지해주시는 분들 덕분에 혼자라는 생각을 떨쳐내고 제 길을 묵묵히 계속해서 걸어갈 수 있게 되었습니다. 마음이 따뜻해지는 주말, 다시 힘내서 블로깅도 해보아요 :)


SDET로 성장해서 QA Engineer분들의 시작을 돕고 싶은 사람이 되고 싶어요. 해당 포스팅이 자동화 구축에 조금이나마 도움이 되길 바라며 Pytest 전환 후기도 재미나게 읽어주세요 :)


Pytest 전환 전 E2E 테스트 구축 및 실무

WebdriverIO(웹, 모바일-Appium)를 활용했습니다. 로케이터 생성에 필요한 도구는 모바일 같은 경우 UiautomatorViewer와 같은 도구가 있었지만, 안드로이드와 아이폰 모두 커버 가능하고 클라우드 테스트 외부 서비스와의 연동을 지원하는 Appium Inspector를 활용했습니다. 웹은 SelectorsHub 플러그인을 사용하면 최적화된 로케이터를 쉽게 찾아낼 수 있어 편리했습니다.


테스트 자동화 구조는 화면 개체 모델 기반으로 구현했습니다. 실무 투입 초기엔 describe/it 블록 형태로 안드로이드 에픽에 대한 구현이 어느 정도 끝마친 상태였는데 이후 내부 사정에 의해 BDD 프레임워크인 Cucumber 구조로 재작업하게 되었습니다. 이 과정에서 다양한 에러와 경험을 했었고 BDD에 대한 이해도가 생기게 되었습니다.


어떤 점을 개선하고자 했나?

WebdriverIO로 테스트 자동화 실무 진행 간에 느꼈던 갈증은 두 가지였습니다. 첫 번째는 웹, 안드로이드, 아이폰을 1개의 프로젝트에서 여러 개의 환경 파일을 만들지 않고 구축하고 싶었습니다. 두 번째는 코드 재사용 가능한 구조를 만드는 것이었습니다.

물론 WebdriverIO 구축을 선형 구조가 아닌 화면 개체 모델을 기반으로 진행했기 때문에 1개의 파일에 모든 로직이 포함된 형태는 아니었지만 실무 코드를 작성하다 보니 다소 지저분한 느낌이 강했습니다. 지저분한 느낌의 원인이 로케이터를 활용하는 로직에 대한 함수가 설계되어 있지 않은 점과, 재사용 가능한 설정들을 분리하지 않은 점을 주원인으로 판단했습니다.


관련해서 TypeScript와 객체지향 설계 방법론을 통해 리팩토링 하고자 20만 원가량의 강의와 서적을 구매하고 개인 학습 진행하려던 찰나에 다시 파이썬 언어와 프레임워크로 E2E 방향이 변경되었는데, 시간과 비용 그리고 이미 결정된 학습 방향을 갈아엎어야 한다는 상황에 많은 생각이 들었습니다.


불필요한 감정적인 상황을 피하고자 일에 감정을 빼고 성장의 관점에서만 바라보기로 했으니 주어진 상황이 오히려 성장의 기회로 보였습니다. 긍정적인 마음과 태도로 파이썬 문법을 간단하게 훑어봤고 재사용한 구조를 어떻게 만들까에 대한 고민이 시작되었습니다.


어떤 점을 개선했나?

개선한 전체 구조

결과적으로 위와 같은 구조가 되었습니다.


1) 엘리먼트 핸들링에 필요한 사전 작업이 정의된 함수와, 해당 함수를 기반으로 엘리먼트 핸들링 함수가 담긴 utils,


2) 클래스팅 각 화면에 존재하는 엘리먼트의 공통적인 동작이 정의된 함수가 담긴 pages,


3) pages에 정의된 함수를 기반으로 테스트 시나리오별 함수가 담긴 actions,


4) actions에 정의된 테스트 시나리오 함수를 기반으로 실제 테스트 로직이 담긴 tests,


5) 테스트 전/후 처리에 필요한 setup과 teardown 함수를 settings 값을 기반으로 config.yml에 정의된 디바이스 값을 불러와서 실행하는 config.py,


6) yaml 파일 데이터를 확인해서 어떤 플랫폼에 어떤 테스트 데이터를 사용하는지 확인하는 함수가 담긴 settings.py,


7) 그 외 테스트 환경과 디바이스 데이터가 정의된 yml 파일과 같이 재사용성을 고려해서 각각의 역할을 구분 지어 설계하였습니다.


예를 들어 아래와 같은 형태입니다.


utils 디렉토리에 있는 page_utils 파일의 PageUtils 클래스 안에는 엘리먼트 핸들링에 필요한 사전 작업이 정의된 함수들을 설계했습니다. 예를 들어 아래와 같은 함수입니다. 아래와 같은 형태로 엘리먼트 핸들링에 필요한 사전 작업들을 설계함으로써 PageUtils 클래스를 import 하여 Base 클래스에서 사용할 수 있습니다.


Ex) def get_element(self, locator):


1. locator를 받아서 엘리먼트 값을 찾는 함수입니다.


2. locator의 첫 번째 인덱스와 두 번째 인덱스를 각각 method와 values에 저장하여 str인지 list인지의 타입 체크를 통해, method와 values를 인자로 받는 get_element_by_type을 리턴합니다.


3. get_element_by_type 함수에서 다양한 method의 상황(id, xpath, class_name 등)에 대한 모든 값을 리턴 시킴으로써 어떠한 locator가 입력이 되어도 값을 찾을 수 있고, 다른 클래스에서 계속해서 활용할 수 있습니다.


4. 만약 정의된 로케이터를 찾을 수 없을 경우 raise Exception을 발생시켜 에러 상황을 파악하도록 만들었습니다.


위처럼 각각의 클래스마다 역할을 부여하고 각 역할에 적합한 함수들을 설계하여 재사용과 유지보수를 고려해서 초기 프레임워크 셋업을 진행하였습니다. 작업 간에 흥미로웠던 사실은 WebdriverIO는 셋업과 동시에 conf 파일을 자동 생성해줬는데, 생성된 conf 파일에는 기본적으로 프레임워크에서 제공하는 다양한 설정 값들이 세팅되어 있습니다. Pytest는 저러한 파일이 자동으로 세팅되지 않기에 WebdriverIO가 굉장히 편리했다는 걸 Pytest 구축 간에 깨닫게 되었습니다.


선형 구조의 문제점

물론 모듈화 없이 순차적으로 작성하는 선형 구조를 사용해도 괜찮습니다. 그냥 쉽게 작업하면 될 걸 왜 이런 고민을 통해 더 복잡하게 만들었나라는 생각이 들 수도 있습니다. 선형 구조의 문제점을 코드로 살펴보겠습니다.


예시를 위해 클래스팅 안드로이드 회원가입을 선형 구조로 작성해보았는데요. 보시다시피 1개의 파일에 정말 많은 내용이 담겨 있습니다. 엘리먼트를 찾고, 엘리먼트를 기다리고, 엘리먼트를 클릭하며 필요한 로직을 작성해나갑니다. 뿐만 아니라 WebdriverIO를 사용하지 않다 보니 테스트 로직이 담긴 파일에서 desired_caps도 작성해줘야 하네요. 진입 장벽이 낮다는 장점 외에는 실무에서 사용하기 어려운 구조입니다.


첫째로 재사용성 없이 반복되는 많은 코드를 작성해야 합니다. 둘째로 테스트 데이터를 하드 코딩으로 작성하다 보니 다른 로직에서 사용할 수 없습니다. 재사용과도 관련이 있고 데이터가 많아질 경우 유지 관리가 어렵습니다. 오류 파악에도 어려움이 있습니다.


이렇다 보니 선형 구조는 실무에서 많은 문제점을 발생시키므로 프레임워크 동작 원리를 파악하거나 테스트 용도가 아닌 이상, 가독성과 재사용, 모듈성 등을 고려한 프레임워크 셋업이 필요하게 됩니다.

만약 좌측 코드를 우측의 선형 구조로 작성하면 어떻게 될까요? 아마도 150~200줄가량의 코드가 한 파일에 작성되지 않을까 싶고, 유지보수는 더욱 어려워집니다. 뿐만 아니라 로직 사이에 스크롤 값이 필요한 경우가 있다면 전/후 로직에 대한 흐름을 다시 살펴보면서 유지 보수해야 하는 어려움이 있습니다.


개선 후 코드

선형 구조의 문제점으로 인해 WebdriverIO에서 화면 개체 모델 기반으로 테스트 코드를 구현했지만, Pytest로 전환하는 김에 좀 더 재사용성에 초점을 두고 개선한 최종 코드입니다. 좌측의 구조 덕분에 우측의 테스트 로직에서는 단 1줄이면 로그인 테스트가 종료됩니다. 개선 효과를 살펴보겠습니다.


1. 테스트 로직에 더 이상 setUp 함수에 desired_caps과 같은 값을 작성하지 않아도 괜찮고 tearDown과 같은 driver.quit()을 작성하지 않아도 됩니다. 실무에서 비즈니스 요구사항을 코드 기반으로 자동화 진행하다 보면, 테스트 로직 외적인 설정 값들에 대한 가독성과 편의성 니즈가 생기기 시작합니다. 크게 변하지 않는 환경 설정 값들이 이미 재사용 가능한 형태로 설계되어 있고, 변경된다 하더라도 해당 값을 관리하는 파일에서 간단하게 변경만 해주면 됩니다.


2. 또한 앞으로 utils 역할에 해당하는 클래스에서, 클래스팅 앱과 화면을 동작시키는데 필요한 행동들을 모두 설계하여 필요로 할 때 손쉽게 사용할 수 있도록 개선해나갈 예정입니다. 결과적으로 스크립트를 작성하는 QA 엔지니어는 각각의 클래스가 서로 어떠한 관계로 설계되었는지에 대한 파악만 된다면, 손쉽게 시나리오를 구현할 수 있고 수정 필요한 시나리오도 적은 리소스로 대응 가능합니다.


3. E2E 테스트 자동화에서 에러가 발생하는 경우는 크게 3가지입니다첫째는 로케이터의 변경이고 둘째는 자동화 프레임워크의 불안정한 아키텍처로 인한 실패이고, 세 번째는 셀레늄 기반의 Appium만 사용할 경우 핸들링 동기화 이슈가 있기 때문에 sleep과 Implicit Wait와 Explicit Wait를 자동화 엔지니어의 판단을 통해 적절한 값을 설정해줘야 합니다. 그렇지 않을 경우 에러가 발생하게 됩니다.


특히 세 번째 같은 경우 꽤나 중요한 개념인데요. 서버로부터 데이터를 받아오기도 전에 엘리먼트 동작 관련된 함수가 실행되면 당연히 에러가 발생하고 의도한 테스트가 동작하지 않습니다. 이러한 현상을 예방하기 위해 테스트 로직 사이에 time.sleep을 넣어주는 경우가 있는데요, 몇 초가 적정한 값 인지도 파악하기 애매할뿐더러 테스트 실행 속도에 영향을 미치게 됩니다. 따라서 특별한 상황이 아니라면 time.sleep 사용을 피하는 게 좋습니다.


대안책으로 서버로부터 데이터를 받아올 때까지 정해진 값을 기다리다가 데이터를 받아오면 정해진 값에 도달하지 못하더라도 다음 명령어를 실행하는 Implicit Wait를 사용하거나, 테스트에 필요한 엘리먼트가 나타날 때까지 기다리는 Explicit Wait를 사용하는 게 좋습니다.


Explicit Wait가 왜 필요한지 의문이 생길 수 있는데, 동적 DOM의 영향으로 인해 Explicit Wait 사용 필요한 상황이 생길 수 있습니다. 예를 들어 Implicit Wait를 5초로 설정했고, 페이지 데이터들이 2초쯤 넘어왔는데, 테스트에 필요한 엘리먼트는 아직 넘어오지 않은 상태라면 에러가 발생하게 됩니다. 모든 엘리먼트가 렌더링 되지 않은 상태에서 만약 다음 명령을 실행할 경우 테스트에 필요한 엘리먼트가 렌더링 되지 않은 상황에 대비해서 테스트 필요한 엘리먼트가 나타날 때까지 기다리는 Explicit Wait를 사용해야 합니다. 물론 Implicit와 Explicit로 해결할 수 없는 경우엔 time.sleep값을 적절히 섞어가며 설계 필요합니다.


마치며

아직 BDD behave 구조로 작업하는 과정이 남아있지만 Pytest 프레임워크로 초기 작업을 끝마친 현재, 지난 시간을 돌이켜보고 관점을 조금만 바꿔서 생각해보니, 축복받은 환경에서 커리어를 쌓고 있는 게 아닐까 싶습니다.


덕분에 다뤄보지 않은 Espresso와 XCUITest 기반의 E2E 환경을 제외하곤, 프론트 진영의 여러 E2E 프레임워크를 다룬 경험과 더불어 일반적으로 QA 사이드에서 많이 사용하는 셀레니움과 앱피움에 대한 경험을 통해, 어느 조직에 속하던 웹 모바일 E2E 자동화 초기 구축부터 유지보수에 필요한 R&R과 리딩까지도 할 수 있겠다는 생각이 들었습니다.

매거진의 이전글 pytest에서 behave로 전환하기
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari