E2E test를 위한 cypress 도입 및 활용방법

2024년 11월 15일

1. 배경

서비스 런칭 후, 프로덕트를 디벨롭 할 수 있는 시간이 생겨 테스트 코드를 도입했습니다. 별도의 QA 팀이 없는 환경에서 지속적인 기능 추가와 버그 수정 과정에서 제품의 안정성을 보장하는 것이 중요한 과제였습니다.

협업요청 글

테스트 코드 도입을 고민하던 도중 다음과 같이 프로덕트 CI/CD 파이프라인 고도화를 위한 협업 요청이 들어왔습니다. 테스트 코드를 기반으로 프론트엔드 CI/CD 파이프라인도 함께 구축하기로 하였습니다. workflow file에 테스트 코드를 삽입하여 github pr을 merge 할때마다 자동으로 테스트 코드가 실행되어 코드의 정상 동작을 검증하는 방식으로 구현하기로 하였습니다.

2. 도입

테스트 코드의 종류로는 Unit test, Integration test, E2E test 가 있습니다.

테스트 코드 종류의 계층구조는 다음과 같습니다.

test pyramid

  • Unit test(단위 테스트) : 개별 코드 단위인 함수, 메서드 단위로 코드 동작을 검증하는 테스트
  • Integration test(통합 테스트) : 단위 테스트를 통해 검증된 기능을 결합하여, 실제 API 통신을 통해 기능간의 호환성을 검증하는 테스트
  • E2E test : End To End 테스트의 약자로, 실제 사용자 시나리오 기반으로 테스트하여 프로덕트가 의도한 대로 동작하는지 확인하는 테스트

서비스 특성상 사용자 시나리오에 따라 정상적으로 LLM과 소통하는지를 테스트하는 것이 핵심이었기에, E2E 테스트를 작성하기로 하였습니다.

cypress는 javascript를 위한 테스트 프레임워크입니다. cypress는 실제 브라우저에서 테스트를 실행할 수 있어 디버깅이 용이하며 cypress와 GitHub Actions를 연동하여 CI를 구축할 수 있기 때문에 해당 프레임워크를 선택했습니다.

[cypress 설치 및 실행 방법]

// cypress 설치
yarn add cypress --dev

// cypress 실행
yarn cypress open

config 설정을 통해 env와 포트 넘버를 설정하였습니다.

// cypress.config.js

const { defineConfig } = require('cypress');
module.exports = defineConfig({
  env: {
    ...
  },
  e2e: {
    setupNodeEvents(on, config) {
      config.env = process.env;
      return config;
    }
  },
  pageLoadTimeout: 1000 * 60 * 2,
  component: {
    devServer: {
      framework: 'vue',
      bundler: 'vite',
      host: 'http://0.0.0.0:3000',
      port: 3000
    },
  }
});

cypress 테스트 코드는 다음과 같은 구조로 작성되었습니다.

  • describe(): 전체 테스트 정의
  • context(): 관련된 테스트 그룹화
  • it(): 개별 테스트 케이스를 정의
// conversation.cy.js

describe('convesation 기능 테스트', () => {
  beforeEach(() => {
    cy.visit(Cypress.env('NUXT_APP_URL')});

	  context('질문 내용', () => {
	    it('질문, 답변 및 광고가 정상적으로 노출되는지', () => {
	      // 질문, 답변 체크
	      ...

	      // 광고 키워드 체크
				...

	      // 광고 리스트 열기/접기 기능
				...
	    });
	  });
  });

[테스트 코드 작성 과정에서의 주요 고려사항]

  1. 테스트 시나리오

    어떻게 서비스 플로우를 적절한 단위로 분리할지 고민이되어 사수분께 여쭈어 보았는데, 기획팀에서 진행하는 테스트 시트를 활용하는 것을 추천해주셨습니다. 테스트 시트에는 QA 테스트를 진행하기 위해 기능별로 작성되어있기에, 해당 시트를 기준으로 유저 플로우를 나누었습니다. 누락되는 케이스 없이 유저 시나리오를 작성할 수 있었으며, 문서를 코드로 변환하여 기획팀의 QA업무부담과 기획-개발 간의 커뮤니케이션 비용도 줄어들었습니다.

    cypress 디버깅 브라우저

  2. 코드별 응집도 향상

    테스트 코드를 작성하면서 유사한 기능들의 테스트 패턴을 일관성 있게 유지하고자 했습니다. 예를 들어, 에러 처리나 상태 검증과 같은 공통 패턴들을 표준화하고 기존에 존재하던 수동 테스트 과정을 자동화하여 개발 생산성이 향상되었습니다. 또한, 테스트 코드를 작성하며 주석을 달아두어 전체적인 서비스 플로우를 쉽게 확인할 수 있도록 하였습니다.

  3. 실제 API 연결

    E2E 테스트를 통해 각 기능이 정상적으로 동작하는지 뿐만 아니라 프로덕트가 전반적으로 원활하게 운영되는지도 확인하고자 했기에 LLM과의 커넥션이 유지되었는지도 테스트할 때 체크가 필요했습니다. 테스트 코드를 수행하는데 시간이 다소 소요되긴 했지만 mock 데이터가 아닌 실제 API를 사용해 테스트를 진행하였습니다.

  4. CI 구축

    GitHub Action을 통한 자동화된 테스트 검증 시스템을 구축했습니다. Pull Request 시 테스트가 실패하면 merge 버튼이 자동으로 비활성화되도록 설정하여, 새로운 기능 개발이나 리팩토링 시 테스트 코드가 안전장치 역할을 하여 사이드 이펙트를 조기에 발견할 수 있었습니다. 이를 통해 프로덕션 환경의 안정성을 한층 강화할 수 있었습니다.

3. 결과

테스트 코드를 작성하였을때 프론트엔드 팀에게만 유용할거라 생각했었는데, 기획/인프라팀의 업무 효율에도 영향을 끼쳤다는 것이 신기했습니다. E2E 테스트 도입은 단순히 버그 방지를 넘어서 전반적인 개발 문화와 안정적인 프로덕트를 만드는데 도움이 되었습니다.

테스트 코드를 작성하는 것은 단순한 작업은 아닌것 같습니다. 하지만, 팀원들이 테스트 코드의 도입의 필요성과 중요성에 공감한다면, 프로덕트의 맞게 적절한 테스트 코드를 도입했을때 생산성을 크게 향상시킬 수 있는 것 같습니다.

🙇‍♂️ 참고문헌

E2E 테스트 도입 경험기

코드와 함께 살펴보는 프론트엔드 단위 테스트 – Part 1. 이론 편

테스트 코드를 왜 그리고 어떻게 작성해야 할까?