웹 개발자의 최소한 이정도 테스트 코드 만큼은 (feat.Cypress)

Doodream·2023년 2월 4일
31

개발자는 어플리케이션을 개발하며 개발 테스트를 진행합니다. 개발하는 방식으로 어떤 것을 중심으로 두느냐에 따라 다르긴하나 기본적으로 당연한 말이지만 버그를 내지 않는 기능을 개발하고자 합니다. 버그를 발견하는 시기는 때마다 다를 수 있습니다. 하지만 버그는 일찍 발견할 수 록 수정되는 리소스 크기는 크게 작아집니다.
물론, 기능이 크지 않고 충분히 예상가능한 작은 어플리케이션을 개발할 때는 필요가 없을 수는 있겠습니다만 (속도 중심의 개발로 인한 불필요성 ), 대부분의 회사의 프로젝트의 크기에서는 필요하다고 생각합니다. 대부분 아래 왜 같은 시나리오로 개발을 진행하기 때문입니다.

버그를 수정할 때는 발견즉시 수정될 수록 추후 들어가는 리소스가 작아집니다.

버그가 쌓이면 위 그림과 같이 하나씩 수정하는 것이 감당할 수가 없습니다. 디버깅보다 새로만드는 것이 빠른 결과..

  1. 개발 진행중 버그를 발견하게 되면 하나의 커밋 혹은 빠른 수정으로 버그 수정이라는 기록자체가 없이 하나의 피쳐개발로서 완성됩니다.
  2. 리뷰시 버그를 발견하게 되면 동료들의 리뷰하는 리소스와 추가적인 수정 커밋을 남기는 리소스가 들어갑니다.
  3. 테스트 개발서버에 올라 QA를 올리면 QA리소스를 잡아먹으며 이미 개발적으로 완성되었다고 생각한 커밋을 되돌아보며 디버깅과정을 거쳐 버그를 수정합니다.
  4. 운영시 버그를 발견하게되면 위 모든 과정을 다시 진행하거나 hotfix 과정을 통한 배포 리스크를 감수해야합니다.
  5. 알면서도 우선순위에 밀리면 서비스를 운영하며 지속적인 고객 CS가 들어오며 서비스의 질의 하락을 가져옵니다. 쌓이는 버그들을 결국 하나씩 개선하기 어려워지고 리팩토링이라는 큰 스프린트 단위 작업을 유발 시킵니다.

웹 어플리케이션에서 테스트의 의미

결국 우리는 포커싱을 하나로 맞출 필요가 있습니다. 가장 빠르게 버그를 발견하여 고쳐야 한다 라는 주제에 대해 고민하게 되죠.
근원을 찾으면 결론은 클린한 코드를 짬으로서 사이드 이팩트를 발생시키지 않는다. 가 나오지만 오늘은 테스트에 대해서 이야기 하려합니다.

테스트 방식들과 장점

저는 리액트 개발자이므로 리액트 예시를 통해 글을 써보겠습니다.
리액트는 컴포넌트방식의 개발을 지향 합니다. 컴포넌트는 단일 책임의 원칙을 가지고 있습니다. 이는 최소 단위로 만들어지는 한개의 컴포넌트는 한가지의 기능을 갖고 있다는 것이죠.
함수또한 사이드 이팩트를 방지하려면 순수함수를 지향하는데 이와 비슷한 맥락입니다. 그렇다면 정말 이함수나 컴포넌트가 정해진 한개의 기능만을 수행하는지 알려면 검증을 해야합니다. 이는 Unit 테스트로 이어집니다.
검증된 컴포넌트를 조합하여 더 큰 맥락의 기능을 담당하는 컴포넌트를 생성하는 방식에서 Intergation 테스트로 이어지고
마지막 사용자 시나리오로 정말 상용 환경에서 요구사항대로 동작하는지 검증하려면 종단간 테스트 (e2e) 로 이어집니다.
작업방식에 따라 퍼블리싱 -> 기능 구현 순으로 작업하는 경우가 많아 디자인 검증을 위해 storybook 같은 툴을 통해 UI 테스트 가 들어가기도 합니다.

테스트의 한계점

이 모든방식을 적용하여 최대한 여러 단계에서 검증하여 리소스를 줄이는게 맞아 보이지만 위 모든 과정의 테스트 코드를 작성하는 리소스는 생각보다 큽니다.
꽤 많은 순간에서 기능 개발 보다 테스트 코드 작성시간이 더 큽니다. 서비스 안정성과 빠른 대응은 개발자의 제한된 리소스 속에 많은 경우 반비례한 관계를 취하게 됩니다.

결국 위의 테스트 단계들은 꼭 절대로 버그가 일어나서는 안되는 핵심 기능들에 한해 작업이 되어 기본적인 서비스 안정성을 도모하게 되는 것이 효율적이라 생각하게 됩니다.

그럼에도 불구하고 있어야 하는 테스트

선택적으로 테스트를 적용하게 되면 아래와 같을 수 있습니다.

UnitTest: util 성 함수 혹은 글로벌하게 사용되는 기능들에 대한 검증
UI 테스트: 아토믹한 컴포넌트들에 대한 디자인 시스템 검증
Intergation 테스트: 글로벌하게 사용되는 컴포넌트 기능 혹은, 핵심 도메인 컴포넌트 기능에 대한 검증

하지만 개인적으로 위 테스트들 보다 가장 있어야 하는 테스트는 e2e 테스트 라고 생각합니다.

e2e 테스트

개발자는 개발시 명확한 요구사항에 대한 이해를 바탕으로 기능개발을 합니다. 당연한 말이지만 배포시 배포내용들은 해당 요구사항들을 정확히 반영해야합니다. 이는 타협될 수 없는 기본입니다. 만약 타협이 된다면 서비스 운영상 큰 타격이 없는 기능들에 한해서 일 뿐, 결제, 장바구니, 로그인, 어플리케이션이 터지는 상황등에서는 타협될 수 없습니다.

그렇다면 이러한 요구사항에 대한 검증은 개발자의 개발테스트, QA 과정중에 일어나게 되지만, 핵심기능에 대한 검증을 매 배포시마다 사람의 인력으로 할 수는 없는 노릇입니다. 즉, 이러한 검증은 자동화가 필요합니다.

타협될 수 없는 핵심 기능들에 대하여

  • 매 배포시 테스트 자동화를 통해 검증을 진행하고
  • 테스트 용 서버에서 지속적으로 테스트를 돌려 버그가 일어나는 상황을 빠르기 캐치하는 것

등이 자동화 테스트를 적용하는 방식으로 볼 수 있습니다.

그렇다면 이러한 테스트를 적용하는 방식은 어떤게 있을까요?

추천하는 e2e 테스트 방식

많은 회사들에서 Cypress를 통해 리액트 웹 앱 테스트를 진행하고 있습니다. Cypress는 간편한 환경설정과 다양한 테스팅 툴을 제공하고 있으며 테스팅 툴을 커스터마이징하여 원하는 자주일어나는 시나리오를 간편하게 짤 수 있도록 도와줍니다.
이와 비슷한 방식으로는 Jest, Puppeteer를 함께 쓰는 방식 이 있습니다.

Cypress는 JQuery 스러운 문법으로 인해 초반 테스팅 접근이 어렵지만 상세한 Docs와 일반적으로 사용하는 방식이어온 만큼 많은 레퍼런스 자료가 있습니다. 후자의 장점은 어쩌면 많은 사람들이 웹 어플리케이션 라이브러리를 React를 선택하는 것처럼 필수적이며 자연스러운 선택으로 강요되어지는 형태라고 생각합니다.

여러 오픈소스 프로젝트들과 회사에서 Cypress를 사용하는 상황에서 역시 처음 테스팅을 하신다면 Cypress가 유용해보입니다.

Cypress의 한계

모든 사용자 시나리오 영역에서 테스팅은 불가능합니다. 당연한 결과입니다. 선천적인 구동형태의 한계입니다.
사실 Cypress 자체가 웹 앱안에서 돌아가는 만큼

  • 아임포트 라던지 토스 결제등 다른 웹 어플리케이션 환경으로 들어가는 시나리오: 제한적인 테스팅
  • 하이브리드 앱이라면 네이티브 영역 이라던지: Cypress 자체만으로는 불가
    이러한 부분은 커버할 수가 없습니다. 억지로 테스팅용으로 브릿지 이벤트를 만들어 진행하려하다간 배보다 배꼽이 훨씬 커지게 되죠.

그럼에도

하지만 그럼에도 불구하고 웹 앱 내부에서 Cypress는 강력한 테스팅 방식을 자랑합니다.

  • 쉬운 테스팅 환경변수 세팅: 개발 테스트 서버, QA 테스트 서버, 로컬 서버등, 여러 환경에 대응이 보다 쉽게 가능하게 합니다.
  • 중복되는 시나리오에 대한 쉬운 커스터마이징
  • 간편한 컴포넌트 탐색기능

3번째가 꽤 강력한 기능이라고 생각되는데 Cypress는 탐색기를 통해 컴포넌트를 클릭하면 해당 컴포넌트의 유니크한 id값으로 접근 할 수 있습니다.
이는 테스팅 시나리오를 짜면서 빠른 테스팅 코드를 짤 수 있도록 합니다. 물론 리팩토링이 자주일어나 코드의 변경이 일어난다면 특별히 지정한 유니크 아이디가 변경되어 좋은 방식은 아닙니다, data-cy 같은 attribute id를 만들어 대응하는 것이 더 유연한 대응 방식이 되겠죠.

하지만, 하나의 기능을 배포할 때에 Cypress 테스트 코드를 짜서 검증을 하는 것이 필수적이라면 빠른 테스트 코드 작성으로 시나리오 테스트를 만들어 볼수 있습니다. 시나리오상 중복된다면 그때 리팩토링해도 늦지 않죠.

그럼 정말 Cypress를 쓴다고 버그가 안나?

아닙니다. 만약 테스트 시나리오를 먼저 생각하지 않고 개발부터 진행하게 되면 대부분 생각한대로 동작을 하는 해피케이스만을 테스트 하게 됩니다. 당연한 말이지만 해피케이스는 하나의 경우일 뿐 사용자는 수많은 케이스로 서비스를 이용하기에 일어날 수 있는 모든 시나리오를 짜야합니다.
따라서

  • 먼저 개발 테스트 시나리오를 짜서 일어날 수 있는 모든 케이스를 생각해야합니다.
  • 시나리오를 커버하는 개발을 진행합니다.
  • 시나리오를 검증하는 테스트 코드를 짜서 실제 기능을 점검합니다.

위와같은 단계가 QA 테스팅 전에 이뤄져야 합니다.

Cypress로 커버 할 수 없는 시나리오 인 경우 직접 테스트를 해야합니다.

개발자에게 테스트란

개인적으로 개발자에게 테스트란 버그를 유발 시키지 않을 코드를 짜는 자세를 갖추게합니다. 기한 내 기능완성을 하는게 가장 중요하겠지만 동작을 안한다면 아무런 의미가 없고, 유지보수하기 어려운 코드를 짠다면 결국에 불안정한 서비스로 이어져 버그를 유발시키기 때문에 하나하나 개발할 때마다 사전 테스트를 해야하죠.

최소한의 리소스를 테스트에 투자해서 최악의 상황은 막자 라는 태도로 글을 쓰게 된 것 같은데 Cypress를 쓰면서 개인적으로 가장 좋았던 점은
테스트 시나리오를 먼저 만들어 놓고 개발하여 애초에 사이드 이팩트가 일어나는 상황을 만들지 않게 짜고자 하게 된 것입니다.
TDD는 더 깊숙하게 들어가야 하지만, 코드를 짜는 태도에 영향을 주는 것 만으로 최소한의 테스트 코드는 필요하다고 생각합니다.

profile
일상을 기록하는 삶을 사는 개발자 ✒️ #front_end 💻

5개의 댓글

comment-user-thumbnail
2023년 2월 8일

gif 갖고싶어요ㅜㅠ

답글 달기
comment-user-thumbnail
2023년 3월 9일

사실 tdd 작성 좋죠.
근데 테스트 작성하고 있으면 상사가 보고 그거하지 말고 다른 거하라고 지시하는게 대다수 회사들인게 현실이라..
자체 서비스 회사면 어떨런지.

1개의 답글