frontend

프론트엔드 전체 테스트 환경 구축해보기(1.설계)

하리하링웹 2024. 8. 22. 20:05

개발 목표

현재 회사의 테스트 코드는 작성은 잘 되어 있지만 이를 실행하는 환경이 너무 느려 모든 테스트를 한 번 실행하는데 수십분 길면 한 시간이 넘는 시간이 걸린다.

 

심지어 제대로 전역으로 테스트를 실행하는 로직도 없어 사실상 유닛 테스트를 여러 번 실행하는 느낌으로 동작하고 있다.

 

약 1~2주 정도 타임라인을 잡고 단위 테스트, 전체 테스트를 빠르게, 테스트 코드 개발자가 편하게 작성할 수 있도록 하는것이 목표로 개발을 진행할 예정이다.

 

아래는 기획, 설계에 앞서 정리한 개발 요구사항이다.

  • CLI로 실행 가능해야한다. ex)ecDev testRun —path
  • 테스트 파일에서 F6과 같이 shorcut 입력 시 동작해야한다.
  • 백그라운드에 테스트를 위한 웹 서버 실행
    • 스토리북 서버: 스토리북 UI에서 단순 클릭을 통해 관련 테스트 실행이 가능해야 한다.
    • 테스트 목록 ui: 테스트 목록 ui에서 유닛 테스트, 전체 테스트를 실행 가능해야 한다.
    • 테스트 run api: dir 단위, file 단위, 전체 등 파일 경로 기반으로 이를 판단가능해야 한다. 파일 경로는 사전에 우리의 디렉토리 구조에 정의된 경로를 참조하여 사용한다.
  • 테스트를 위한 테스트 세션이 존재해야 하며 해당 세션에서 테스트가 실행되어야 한다.
  • 전체 테스트 실행 시 테스트 성능을 위해 실행중인 테스트 세션을 유지시키며 테스트를 순회하며 실행해야한다.
  • 테스트를 위한 각종 assertion이 필요하며 테스트 실행을 위한 test환경 역시 필요하다.

개발 기획, 설계

목표와 요구사항이 명확해졌으니 이를 기반으로 효율적인 개발을 위해 기획, 설계를 먼저 진행해보았다.

CLI로 실행 가능해야 한다.

ecDev runTest —path와 같이 cli로 실행해야하기에 이를 도와주는 라이브러리를 사용한다. 대표적으로 commander, yargs 두개가 있는데 docs를 읽어보면 둘 다 역할은 비슷해보이기에 더 많이 사용하고, 더 사용성이 좋고, 더 사이즈가 작고, 더 관리가 잘되는 라이브러리를 선택한다.

// commander 예시
program.command('split')
  .description('Split a string into substrings and display as an array')
  .argument('<string>', 'string to split')
  .option('--first', 'display just the first substring')
  .option('-s, --separator <char>', 'separator character', ',')
  .action((str, options) => {
    const limit = options.first ? 1 : undefined;
    console.log(str.split(options.separator, limit));
  });
  

// yargs 예시
yargs(Deno.args)
  .command('download <files...>', 'download a list of files', (yargs: any) => {
    return yargs.positional('files', {
      describe: 'a list of files to do something with'
    })
  }, (argv: Arguments) => {
    console.info(argv)
  })
  .strictCommands()
  .demandCommand(1)
  .parse()

일단 사용성은 비슷해 보이며 기능도 거의 동일하다고 봐도 될 것 같다. 하지만 이슈 관리, 다운로드 수, 업데이트 주기를 포함해 모든면에서 commander가 더욱 활발하고 좋아보이니 이를 사용하여 cli를 구현한다.

테스트 파일에서 F6과 같은 키 입력 시 바인딩된 task가 동작해야한다.

이 부분은 간단하게 개발이 가능할 것으로 예상된다. vscode의 설정 파일을 수정하여 원하는 task를 정의하고 원하는 key를 바인딩 한 뒤 프로젝트에 저장해주면 된다.

 

백그라운드에 테스트를 위한 웹 서버 실행

서버 프레임워크

웹 서버에서 복잡한 동작을 하는것은 아니며 개발용이기에 사용성이 좋고 가벼운 서버 프레임워크를 사용한다. 현재 회사에서 비슷한 역할을 하는 개발서버는 express로 되어있지만 이번에 새로 만들기로 결정했기에 이를 fastify로 사용하는 것을 고려해본다. 현재 단계에서 어떤 서버 프레임워크를 사용할지가 중요한 것은 아니기에 추후 개발 시에 좀 더 디테일하게 생각해본다.

 

스토리북

먼저 컴포넌트 리스트를 보여주는 스토리북 UI가 있어야한다. 스토리북 UI는 이미 따로 만들어져 있으니 추가적으로 이번에 새로 개발하는 웹 서버에서 해당 UI를 연결하는 작업을 진행해준다. 경로는 /storybook으로 설정하는 것이 좋을 것 같다.

또한 스토리북 실행 시 해당 경로로 알아서 이동되도록 추가적인 개발을 진행한다.

여건이 된다면 스토리북 UI 환경에서 연관 테스트를 실행시킬 수 있도록 개발해본다. 이 부분은 그리 중요한 개발은 아니기에 우선순위는 낮게 잡는다.

 

테스트 목록 UI

테스트 목록을 UI로 보여주는 페이지를 개발한다. 스토리북과 마찬가지로 UI 환경에서 테스트 실행이 가능해야하며, dir 단위, unit 단위, 전체 등 다양한 범위에서 테스트를 진행할 수 있도록 개발해준다. UI가 존재하지 않기에 테스트 데이터를 먼저 정규화 한 뒤 정규화된 데이터를 기반으로 React로 UI를 구축한다.

 

테스트 실행 API

경로 정보를 받아 테스트 코드를 실행시켜주는 API이다. 이 API에서 TestRunner라는 별도의 테스트 전용 모듈을 사용하여 세션을 구성한 뒤 실제 환경과 최대한 유사한 환경에서 실행될 예정이다. 경로는 단일 파일일수도, 디렉토리일수도 있다. 또한 전체 테스트도 가능해야한다. 경로를 기반으로 dir일 경우 테스트 파일 목록을 뽑아내는 api를 테스트 api 앞쪽에 추가해준다.

 

나머지 테스트 환경, 세션, 전역테스트 부분

자체 프레임워크를 개발하여 사용하기에 jest, cypress와 같은 다양한 테스트 라이브러리에 100% 의존해서 개발하기는 쉽지 않다. 따라서 이전에 사용하던 TestRunner를 사용한다. 다만 현재의 TestRunner는 반쪽짜리이기에 많은 개선이 필요하며 이 작업도 같이 수행한다.

 

TestBed

현재 회사의 상황에 맞게 필요한 모듈을 모두 정의해줘서 테스트 코드 작성자가 불편함을 느끼지 않는것을 목표로 한다.

 

Assertion

현재의 테스트의 Assertion의 종류는 너무 부족하기에 Jest와 같은 라이브러리를 참고하여 필요해 보이는 Assertion들을 추가해준다. ex)isEmpty, isNotNull …

 

Test Utils

마찬가지로 사용성을 위한 유틸들을 만들어준다. ex) beforeAll, afterAll, beforeEach, mock …

 

Session

현재 테스트 파일 경로와 함께 웹서버에 요청을 보내면 웹서버에서 .pug 파일 형태 즉 템플릿 언어 형태로 브라우저에 테스트 세션을 보여주게된다. 이 때 테스트 세션은 React로 만들어진 TestRunner내부에서 실행되며 targetTestPath 정보를 가지고 있다. TestRunner 실행 도중 targetTestPath를 import 하면서 테스트 코드를 기반으로 TestBed를 초기화, 등록해준다.

 

이 과정에서 TestBed 여러개의 유닛 테스트들도 등록이 되고, 테스트 세션을 만들면서 정의된 기본 viewmode, datamodel, context등에 테스트를 위해 개발자가 정의한 데이터가 업데이트 된 이후 TestBed의 정보를 기반으로 테스트가 실행되는 방식으로 동작한다.

 

여기까지는 이미 구현되어 있기에 신경쓰지 않아도 되지만 전체 테스트를 구현할 때 매 번 세션을 새로 만드는 것은 비효율적이기에 세션은 유지하며 각 테스트 실행 시 viewmodel, datamodel, context를 초기화하여 효율적인 테스트가 가능하도록 만들어야한다.

 

TestRunner를 재실행 하는것은 사실상 세션을 다시 만들겠다는 의미이기에 TestRunner 최초 실행시 받는 targetTestPath의 자료구조를 배열 형태로 변경해준다. 이후 각 실행 과정에서 viewmodel, datamodel, context, testBed를 초기화하며 각 targetTestPath를 통해 다시 테스트 데이터를 적용시켜준다.

 

이 부분은 문제가 발생할 수 있는 부분이 맞기에 실제 개발할 때 더 많은 고민, 설계를 진행하여 개발한다.

 

설계 도안

위 구조를 그림으로 그려보면 아래와 같다.

 

그림의 각 색깔 영역은 책임과 역할이 명확하므로 아래와 같이 나눠서 개발해도 전혀 문제가 없는 것을 확인할 수 있으며 실제로도 아래와 같이 나눠 개발한다. 단 문제가 생기면 유동적으로 잘 대처한다.

  1. VSCode
  2. CLI
  3. Web Server
  4. Test Runner

 

 

결론

이제 설계가 완료되었으니 개발을 진행해보자 처음부터 모든 것을 고려하고 개발하는 것은 불가능하니 프로토타입을 만든 뒤 구체화하는 방식으로 개발을 진행할 예정이다.

 

다음 글에는 CLI 환경을 만들고 테스트 하는 글을 작성하도록 하겠다.