서비스 UI 테스트 자동화 시작 후기...!

Dahun Yoo·2023년 8월 18일
1

Lessons learned

목록 보기
9/13
post-thumbnail

이번에 회사에서 UI테스트에 대해 자동화를 진행하기 시작했습니다.

이전까지는 혼자서 진행한게 대부분이었다면 이번에는 팀원들과 협업하며 좀 더 대규모로 프로젝트를 진행했기에, 배울 수 있었던 점들과 앞으로 고민해봐야할 점에 대해 기록을 남겨봅니다.

뭐 완전히 끝난 것은 절대 아니고, 일부 테스트에 대해 자동화를 한 것이고 앞으로도 유지보수는 계속해 나가야하기에 나중에 또 레슨런이 생기면 기록해보겠지만 무튼...


결론부터 말하자면...

결론부터 말하자면 유지보수 가 가능한, 지속가능한 자동 테스트 코드를 작성하려고 했습니다.

이전회사에서, 매뉴얼테스트를 자동화한 코드에 대해 기존 팀원에게 인수인계하면서 느낀 것들이 많이 있었습니다.

https://velog.io/@dahunyoo/Manual-QA-Team에서-자동-회귀-테스트-코드를-인수인계하면서

이 때 느낀 것이

  • 내가 퇴사한 후에도 계속 일할 팀원들을 생각하지않고, 함부로 테스트를 자동화해선 안되겠다.
  • 그래도 자동화의 필요성이 생기거나, 테스트자동화쪽의 커리어도 경험해보고싶은 누군가가 있을 수 있으니까 조금 길어져도 좋으니까, 최대한 파악하기 쉽게 작성해야겠다.

라고 생각했었습니다.

마침 지금 팀에서는 본격적으로 테스트 자동화를 수행하기 시작했고, 이전 회사와는 다르게 테스트 자동화업무를 하기 위해 어느정도 학습하신 분들도 계신 상황이라 다른 팀원에 대한 부담감?은 느끼지 않아도 되는 환경이었습니다.

해서, 저번 회사에서 느낀 점을 토대로, 이번 회사에서 한 번 실현해보자고 생각했고, 대부분의 경우에 대해 유지보수성 을 생각하면서 업무를 진행하게 되었습니다.


팀원들과 토론해보거나, 내 나름대로 고민해서 시도해본 것들

✍🏻 Naming convention

네이밍 컨벤션은 인터넷에 돌아다니는 파이썬 컨벤션을 그대로 차용하긴 했지만, 팀원들과는 좀 더 얘기를 나눈 것이, 메소드명이나 변수명은 좀 길어저도 좋으니까 다른사람들이 봐도 바로 알 수 있게 하자 였습니다.

QA엔지니어는 개발을 메인으로 하는 사람들이 아니기 때문에 당연히 코드에 대해 이해가 떨어지는 상태인데, 메소드명나 변수명도 알기 어려운 축약어로 만들어버리면 추후에 퇴사 등으로 코드 유지보수담당자가 바뀌었을때 더 힘들어질 것이라는 것을 팀원들과 얘기를 나누었습니다.

📜 DocString

In programming, a docstring is a string literal specified in source code that is used, like a comment, to document a specific segment of code. Unlike conventional source code comments, or even specifically formatted comments like docblocks, docstrings are not stripped from the source tree when it is parsed and are retained throughout the runtime of the program. This allows the programmer to inspect these comments at run time, for instance as an interactive help system, or as metadata.

https://en.wikipedia.org/wiki/Docstring

코드를 작성할 때에 클래스나 메소드에 가능하면 docString 을 달게끔 팀원들과 공유했습니다. docString을 추가하는 것으로 인해, 타인이 작성한 메소드나 클래스를 사용할 때에, 해당 docstring을 보고 파라미터로는 어떤값을 넘겨줘야하면 리턴값은 무엇인지를 명확히 알 수 있게 되어, 코드 작성하는 것에 익숙하지 않은 사람이 코드를 작성하더라도 충분히 어떤 가이드 역할을 할 수 있을 것이라 기대했습니다.

🏛 프로젝트 구성과 시스템 디자인?

아예 없는 시스템을 폴더구성부터 만들어야했는데, 자바의 경우 어느정도 업계 관습적인 폴더구성이 있는가 하면, 파이썬의 경우에는 그런 것이 없어서 폴더의 구성부터 어떻게 해야할지 고민하게 되었습니다. (아니 있는데 못찾은 것 일수도...)

서비스는 안드로이드, iOS, PC웹, 모바일웹으로 서비스되고 있었고, 이 플랫폼들의 하위에 대해 각각 locator, pages, tests 파일로 하위 구성을 해나갔습니다.

페이지 클래스에서 다시 로케이터만 분리한 이유로는 , 테스트흐름이나 페이지 클래스 내에서 로직을 구현하다보면

  • 다른 페이지와 로케이터가 같은 경우
  • 어떤 이유로 테스트 메소드 내에서 로케이터를 사용해 직접 driver 를 조작해야하는 경우

가 있을 것 같아, 페이지와 로케이터를 분리하였고, 상황에 따라 로케이터 단독으로 전혀 별개의 소스코드 위치에서 사용할 수 있도록 하였습니다. 물론 대전제는 항상 서로 관련있는 페이지와 로케이터를 하나로만 묶어서 사용하자고 팀원들과 논의하긴 했습니다.

그 이후에는 common 폴더를 만들어서 모든 플랫폼에서 공통적으로 쓰일만한 공통 모듈을 만들어서 넣기로 했습니다.

- android
  - locator
  - pages
  - tests
- iOS
  - locator
  - pages
  - tests
- pc_chrome
  - locator
  - pages
  - tests
- common
.
.
.

이렇게 하면 각 플랫폼별로 로케이터를 각각 관리할 수 있고, 각 플랫폼별 테스트케이스 관리도 용이하지않을까? 하는 생각으로 구성하게 되었습니다.

상속을 이용한 페이지 내 행위의 공통화와 관계의 명시화

페이지 객체모델을 구현할 때, 이른바 PageFactory 를 만들어서 어떤 플랫폼이던지 공통적으로 발생하는 행위들을 정의한 다음, 각각의 플랫폼별로 팩토리를 다시 만들어서 플랫폼 특유의 동작 등을 오버라이딩한다던지 추가해보았습니다. 그 다음 각각의 페이지로 다시 상속받아서 이용합니다.

이렇게 했을 때, 사실 appium도 결국 Selenium라이브러리를 사용하고 있는 것이라, find_element()를 이용한 click()이라던지, send_key() 등을 웹이던 앱이던 같이 사용할 수 있고, wait 또한 공통적으로 사용할 수 있어서, 각 코드를 작성하는 담당자들은 상속받는고 생성자에서 상위 클래스의 생성자를 호출해주는것만으로 드라이버 객체를 생성한다던지, 드라이버의 행위를 별도로 관리하지 않아도 되었습니다.

로케이터들을 각각의 페이지들에게 상속해주는 것은, 파이썬은 다중상속을 허용하는데 이 다중상속을 이용해 해당 로케이터와 페이지의 관계에 대해 명시화해주려했습니다.
로케이터를 상속받는 것으로 인해 페이지 클래스에서 페이지 클래스의 필드인 것 처럼 사용할 수 있습니다.
(다중 상속을 통해, 하나의 페이지 클래스는 팩토리클래스와 로케이터 클래스를 같이 상속받게됨)

Test step 공통모듈화

테스트케이스에서는 어떤 기대결과를 확인하기 위한 일련의 테스트 스텝들이 존재합니다. 혹은 시나리오 기반으로 케이스를 작성한 경우에도, 특정 기대결과를 확인하기 위한 일련의 스텝들이 존재하게됩니다.
이번에 자동화하게된 테스트케이스들은 기대결과만 조금씩 다를 뿐, 해당 기대결과를 확인하기위해 필요한 스텝들은 동일한 부분들이 많았습니다. 때문에 여러 테스트케이스에서 중복적으로 호출하게 되었습니다.
이러한 것들은 따로 클래스를 만들어 그 내부에 펑션들을 선언하고 그것들을 필요한 테스트케이스에서 호출해서 사용하게 해서 코드의 중복을 많이 막았던 것 같습니다.

(물론 처음부터 그런 건 아니고, 코드를 많이 작성해놓고 나서 보니까 중복이 눈에 띄어서... 나중에 리팩토링한 식이지만...)

물론 예외 스텝이 있는 케이스도 존재하는데 이 경우에는, 아래와 같이 대응하려고 했습니다.

  • 최대한 수정하지 않고, default parameter를 이용해서 모듈 내에서의 분기처리
  • 분기처리가 정 어려울 경우에는 비슷한 로직을 해당 예외 케이스에서만 추가로 작성

Test suite와 Test run의 구분

Understanding the Differences Between Test Cases, Test Suites, and Test Runs
https://www.fleekitsolutions.com/understanding-differences-test-cases-test-suites-test-runs/

자동화 대상 케이스들 중에 특정 테스트케이스들은 특정 스텝만 다를 뿐, 기대결과는 같은 케이스도 있었습니다.
예를 들어 특정 상품을 결제할 때 결제수단이 여러개 있는 경우, 결국 결제가 잘되는지를 확인해야하는데 결제수단별로 확인해야하는 경우가 있습니다. 이런 경우에는 하나의 케이스에 대해 결제수단별로 똑같은 테스트를 반복수행해야합니다.

그래서 어떠한 테스트케이스의 모음인, Test suite를 하나의 클래스로 만들고 해당 클래스 하위 메소드에 해당 케이스의 모든 test step들을 작성하였습니다.

이 후에 결제수단 별 테스트클래스를 별도로 만들어 (Test run), 해당 클래스 내에서 suite의 testcase를 끌어오는 것으로 진행했습니다.

이것으로 실질적인 테스트 코드를 재사용하면서, 테스트 코드의 수정이 필요할때는 suite의 코드만 수정하면 모든 run에 반영되도록 되었습니다.

Duck typing을 이용한 테스트 로직의 공통모듈화

Duck Typing 🐥

If it walks like a duck and it quacks like a duck, then it must be a duck

해석해보면 "오리처럼 걷고, 오리처럼 꽥꽥거리면 그것은 틀림없이 오리다." 라는 뜻이다.

파이썬에서 덕타이핑을 지원한다는 뜻은 동적 타입의 언어에서 본질적으로 다른 클래스라도 객체의 적합성은 실제 유형이 아니라 특정 메서드와 속성의 존재에 의해 결정된다는 말이다.

https://dev-jy.tistory.com/29
https://en.wikipedia.org/wiki/Duck_typing

페이지들을 페이지 오브젝트 모델을 구현하다보니, assert를 하기 위해 몇몇 페이지에서 똑같은 목적을 가진 메소드들을 구현하게 되었습니다. 그리고 이 페이지 오브젝트의 메소드들은 테스트 메소드 내부에서 반복적으로 호출하고 있었는데, 목적이 같으면 어떻게든 공통화할 수 있지않을까 생각했고, 덕 타이핑 개념을 통해 하나로 묶을 수 있었습니다.

from typing import Union

def check_header(page: Union[A_Page, B_Page], header_name:str):
	assert page.check_header_is_displayed(header_name) is True, "헤더값 존재하지 않음"

위와 같은 함수를 통해, A페이지 이던 B페이지 이던 check_header_is_displayed() 메소드를 가지고만 있다면, 내부 구현이 어떻든 메소드를 통해 이루고자 하는 목적은 찾고자 하는 헤더가 노출되는지를 확인하는 것이기 때문에 여러 클래스에서 반복적으로 사용가능하였습니다.

확인하고자 하는 페이지가 테스트케이스마다 달랐으므로, 페이지의 객체를 주입해줌으로 인해, 테스트와 페이지간의 의존관계를 느슨하게 만들 수 있었습니다.

또한 파라미터 타입을 명시해줌으로 인해서, 해당 함수를 사용하는 다른 팀원들도 충분히 인지할 수 있도록 했습니다.


이렇게 정리해보니 이번 프로젝트를 진행하면서 제가 많이 고민한 부분은 앞서 글 도입부에 말씀드린대로, 유지보수를 위한 구현를 포함한, 로직의 공통화, 코드의 재사용 으로 정리해볼 수 있을 것 같습니다.

특정 테스트, 특정 플랫폼의 구현담당자가 부재/퇴사했을 때, 신규 코드작성자가 코드를 보고, 최소한 이미 존재하는 모듈/로직에 대해 재작성하지 않도록 고려했던 것 같습니다.


앞으로 고민해야할 것들 🤔

🖨 디자인 패턴

소프트웨어 디자인 패턴(software design pattern)은 소프트웨어 공학의 소프트웨어 디자인에서 특정 문맥에서 공통적으로 발생하는 문제에 대해 재사용 가능한 해결책이다. 소스나 기계 코드로 바로 전환될수 있는 완성된 디자인은 아니며, 다른 상황에 맞게 사용될 수 있는 문제들을 해결하는데에 쓰이는 서술이나 템플릿이다. 디자인 패턴은 프로그래머가 어플리케이션이나 시스템을 디자인할 때 공통된 문제들을 해결하는데에 쓰이는 형식화 된 가장 좋은 관행이다.

https://ko.wikipedia.org/wiki/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4_%EB%94%94%EC%9E%90%EC%9D%B8_%ED%8C%A8%ED%84%B4

아무래도 코드를 작성하다보니 중복이 발생하는 코드들도 많고.. 단순히 공통모듈로 분리하기 보다 좀 더 효율적인 해결책이 있을 수 있겠다 싶었습니다.
지금 당장은 어떤 패턴이 딱 필요한 해결책이다!! 이것은 없지만, 추후에 바로바로 대응하기 위해서 대표적인 디자인 패턴에 대해 공부는 해놔야겠다~ 라는 생각이 들기 시작했습니다.

💨 실행 속도의 최적화

기술적인 측면에서는 아무래도, 테스트 수행 속도를 고려하지 않을 수 없는 것 같습니다.
단적인 테스트케이스보단, 유저의 사용흐름을 고려한 step으로 구성된 시나리오를 자동화하고 있는데요, 갯수는 몇개안되지만 속도가 생각보다 많이 느리더라구요.

조금 예시로 설명하면...

무조건적인 wait는 오히려 테스트 케이스 수행시간이 불필요하게 늘어나게 됩니다.
예를 들어, wait의 idle time을 10초로 설정하는 경우, element를 찾는데 최악의 경우 최대 10초가 걸리게되고, 이것이 쌓이고 쌓이면 결국에는 전체 테스트케이스 실행시간이 늘어나게 됩니다. sleep 은 말할 것도 없구요.

페이지의 특성에 따라 적절한 waitsleep를 사용해서 flaky하지 않으면서도 수행시간을 좀 더 단축시킬 수 있도록 테스트 코드의 최적화가 필요할 것 같습니다.

🛣 테스트 코드 품질의 평준화

페이지 클래스의 코드들도, 적어도 인터페이스를 사용해서 구현해야할 함수들을 강제한다면 좋겠지만, 팀원들 모두 대규모 프로젝트의 설계경험이 없어서 (다들 테스트만 해서 설계 경험이 있을리가 없음...) 설계단계에서 고려해야할 점들이 좀 부족한 점이 있었습니다.

이미 작성되어있는 페이지 클래스의 코드를 평준화시키는 작업은 좀 어려울 것 같고, 대신에 테스트 코드라도 인터페이스를 사용해서 강제하는건 어떨까 하는 생각을 가지고 있습니다.

이것이 무슨말이냐 하면은... 예를 들어 아래와 같은 흐름을 가진 테스트케이스 있다고 합시다.

  • 테스트 스텝
      1. 로그인 한다.
      1. 상품 상세페이지 내에서 특정 내용이 기재되어있는지 확인한다.
      1. 구매 시도한다.
  • 기대 결과
    • 구매가 잘 되었는지를 확인한다.

위 테스트케이스에 대해서, 테스트 수행자들은 제각각 확인하는 부분이 다를 수 있습니다. 특히 기대결과의 부분에서 A라는 사람은 구매(주문) 상태 를 확인할 수 있고, 어떤 사람은 주문금액을 확인할 수 있으며, 어떤 사람은 구매 후 알림내역이 제대로 발송되는지 등으로 판단할 수도 있을 것 입니다.

행위를 잘게 분할해서 기재를 해야하는 코드의 경우에는 더더욱 그렇습니다.

A라는 사람은

  • 로그인한다
  • 상품을 찾는다
  • 상품 상세페이지로 들어간다
  • 상품 상세페이지 내에 특정 내용들을 확인한다.
  • 구매 버튼을 누른다.
  • 주문 내역을 확인한다.
  • 구매확인 버튼을 누른다

등으로 표현할 수 있다면, B라는 사람은

  • 로그인 아이디를 입력한다
  • 로그인 패스워드를 입력한다.
  • 로그인한다.
  • 검색페이지로 이동한다
  • 상품을 검색한다.
  • 상품의 상세페이지로 들어간다.
  • 상품의 상세페이지 내에서 특정 내용들을 확인하고 구매 버튼을 누른다.
  • 주문 내역을 확인하고 구매확인 버튼을 누른다.

로 표현할 수도 있을 것입니다. 그리고 이것은 추후 다른 사람들이 보았을 때는 자동화 대상 케이스는 똑같은 케이스인데 왜이렇게 절차가 다르지? 라고 생각하며 뭔가 다른점이 있는 것은 아닌지 생각할 수도 있을 것입니다. 실제로는 같은 결과인데 말이죠.

그래서 내부 구현은 각각의 플랫폼별로 다를 수도 있고, 개개인의 코딩스타일(문제 해결을 위한 표현의 다양함)이 있으니 적어도 테스트 메소드는 일관적으로 통일시키는게 좋지 않을까~ 싶은 고민이 있습니다.

🤯 너무 많은 로직의 과도한 공통화로 인한 우려점

코드리뷰를 하다보면 팀원이 코드가 좀 알기 어렵다, 꽁꽁 숨겨놓아서 찾기가 어렵다 라고 해줄 때가 있었습니다. 🥲

같은 행위를 하는 페이지에 대해서는, 테스트 코드나 행위의 플로우를 계속해서 별도의 공통모듈로 만들고, 이것을 호출하는 식으로 수정을 해가고 있습니다. 그런데 너무 많은 부분을 공통화해버리다보면

  • 특정 모듈까지 호출 depth가 깊어져서, 나중에 타인의 코드를 볼 때 해당 코드를 찾아보기가 어렵다던지
  • 특정 모듈은 많은 부분에서 호출하고 있는데, 이 모듈을 수정해버리면 영향범위가 너무 많아지기에, 어디까지 모듈화를 하고 어디까지 모듈화를 하지 않을지를

를 고민해야하는 문제가 생겼습니다.

아무래도 개발자가 아닌 테스트담당자들이 읽고 작성하는 코드인만큼, 프로그래밍이라고 해야할지.. 코딩 실력은 크게 기대할 수는 없는데요, 이런저런 모듈을 끌어다가 테스트케이스를 만드는 만큼, 신규 작업자가 해당 코드를 보았을 때 너무 과도한 공통화로 인하여 프로젝트 이해가 어렵게 되는건 아닌가 싶은 생각이 들기도 하였습니다.

🎨 변화가 심한 UI에 유연하게 대응하려면..?

최근 많은 프로덕트들은 배포주기가 점점 더 빨라지고 있으며, 이용자의 반응을 확인하기 위해 A/B테스트는 이젠 매우 흔하게 되었습니다. 제가 소속된 회사도 마찬가지인데요, UI 자동 테스트는 이런 UI의 변경과 UI의 A/B테스트에 대해서 대응하기가 매우 어렵습니다.

이러한 빠른 배포와 잦은 변경에 대응하기 위해, element를 식별하는 방법에 대해 좀 더 유연한 방법이 필요하겠다 싶었습니다.

제일 좋은 건 각각의 Element에 의미를 가진 유니크한 id 값을 부여하는 것이 가장 좋을 것 입니다. 그래서 개발팀과도 주기적인 미팅을 통해 id 부여를 부탁하고, 이 id에 대응하는 행위들로 element를 식별해야, UI가 변경되어도 해당 id 가 존재한다면, 테스트케이스의 수정없이 그대로 실행이 가능할 것 입니다.

그리고 이러한 유니크한 id값을 가진 Element들이 프로덕트 내에 많아진다면, 테스트케이스를 좀 더 행위 중심적인 테스트케이스들과 스텝들로 만들어 프로덕트의 스펙 및 테스트 자동화 코드에 이해도 낮은 신규 QA엔지니어를 포함해서, 이해관계자들이 코드를 보더라도 쉽게 이해할 수 있는 그런 구조를 가져갈 수 있을 것 같습니다.

id 를 부여하는 것은 대부분 개발자이기 때문에, 점진적으로 개발자분들과의 협업을 통해 테스트 코드 퀄리티도 올려나가야하지 않을까 생각하고 있습니다.

📐 테스트의 자동화에서 발생한 결과들에 대해 정량적으로 측정해보기

이것은 프로덕트 외적인 부분이긴한데.. 사실 테스트를 자동화했다고 해서 뭔가 티가 많이 나거나 그렇진 않습니다. 오히려 어느정도 코드가 구성되고, 테스트를 돌리기 시작하면서 느끼는 점은,

나는 열심히 일하고 있는데.. 프로젝트에 합류해서 테스트를 직접 수행하는 거에 비해서 일하는 티도 안나고.. 업무 성과를 어떻게 어필해야할지 모르겠다.

였습니다.

테스트를 자동화한다고해서 못찾던 버그들을 잡아내고 그런 것은 아닌지라.. 오히려 유지보수의 블랙홀로 끌려가는 것이 아닌가 하는 걱정까지 하게 되었습니다.

일단은 자동테스트로 인해 확인되는 이슈들을 취합해서 어떠한 통계를 낸다던가, 테스트의 자동화로 인해서 다른 테스트 담당자들의 관련 리소스가 얼마나 세이브되는지를 계산해보려고 하고 있는데.. 잘 될지는 모르겠습니다.

⚙️ 자동화할 테스트케이스를 잘 골라내기

지금도 여전히 노력하고 있지만, 앞으로도 계속해서 테스트를 자동화해야하는데요, 당장 자동화하였을 때 투자 리소스 대비 효과가 큰 케이스는 무엇인지 골라내는 작업이 계속해서 진행되어야하고, 기존 케이스들에 대해서도 주기적으로 점검해볼 필요가 있을 것 같습니다.

자동화하는 시간 대비 기능적 중요도가 별로 높지않은 케이스라면, 자동화하여도 큰 효과를 얻지 못할 것입니다. 또한 자동화하는 시간에 비해 매뉴얼로 수행했을 때 절약되는 코스트가 그다지 크지 않다면 이 또한 리소스의 낭비로 이어질 것 입니다.

따라서 자동화 대상 케이스들을 선정할 때 항상 이런 조건들을 가지고 판단해야하고, 기존에 자동화한 케이스들에 대해서도 더이상 불필요한 케이스는 아닌지, 시나리오나 스텝 상에 보충이 필요한 점은 없는지 계속해서 점검하고자 합니다.

정리하며..

기존에 매뉴얼 UI테스트와 API 테스트 위주로 업무를 진행하다가, 이번에 UI매뉴얼 테스트의 자동화를 진행하게 되었습니다. 기술적인 면이나, 그 외적인 부분들에서 경험해보지 못하거나, 고민해보지 못한 부분들을 접할 수 있어서 좋은 기회였습니다.
어떻게 하면 팀원들과 함께 으쌰으쌰하면서 좀 더 좋은 코드, 효율적이면서도 효과적인 자동 테스트를 만들어야할지 고민할 수 있었습니다.

앞으로 자동 테스트를 만들어놓고 끝이 아니라 계속해서 유지보수를 해주어야하는 만큼, 이 업무에 대해 내가 좀 더 재미를 느끼고 계속해서 할 수 있을 업무인지, 아니면 다른 프로덕트와 서비스에 대해 기여할 수 있는 다른 업무방법이 있을지 좀 더 고민해보고 싶습니다.

끄읏!

profile
QA Engineer

0개의 댓글