TEST - TOAST UI (2019.03.29) 글을 요약 • 정리한 글입니다.

테스트


테스트의 종류

  1. 단위테스트
  2. 통합테스트
  3. E2E(End to End) 테스트


자바스크립트 테스트 도구

테스트를 구동할 수 있는 환경을 제공하는 테스트러너

테스트 파일을 읽어들여 작성한 코드를 실행하고, 그 결과를 특정한 형식으로 출력해준다.

테스트의 수행 결과는 리포터를 지정해서 원하는 형태로 출력할 수 있다. 부가적으로 테스트 코드나 소스 코드가 변경된 경우 영향을 받는 테스트를 자동으로 재실행해주는 왓쳐 등의 기능도 제공한다.

테스트 러너는 크게 Karma와 같이 브라우저에서 직접 코드를 실행하는 러너와, Jest와 같이 Node.js 환경에서 코드를 실행하는 러너로 나눌 수 있다. 이 중 Node.js 기반의 테스트 러너들은 굳이 러너의 샐항 환경과 코드의 실행 환경을 구분할 필요가 없기 때문에 대부분 테스트 프레임워크와 통합된 형태로 제공된다.

테스트 코드 작성을 위한 기반을 만들어주는 테스트 프레임워크

대표적인 테스트 프레임워크로는 MochaJasmineAVA 등이 있으며, 최근에는 Jest가 빠른 속도로 점유율을 높여가고 있다.

테스트를 좀 더 편리하게 작성할 수 있도록 도와주는 단언(assertion) 라이브러리, 테스트 더블 라이브러리

테스트 코드는 주로 테스트를 위한 초기화와 단언으로 이루어지며, 단언은 개별 테스트가 통과하기 위한 조건을 명확하게 기술하기 위해 사용된다. 보통은 테스트 프레임워크에서 다양한 방식의 단언 API를 기본 제공하고 있으며, Mocha의 경우에만 Chai와 같은 별도의 단언 라이브러리를 사용하도록 권장하고 있다.

초기의 단언 라이브러리들은 JUnit과 유사한 방식의 API를 많이 따랐지만, 최근에 가장 많이 사용되는 Chai, Jasmine 등에서는 좀 더 자연어에 가까운 BDD(Behavior-driven development 방식의 API가 사용된다.

테스트 더블이란 실제 객체 대신 테스트를 위해 동작하는 객체를 말하며, 주로 분리된(isloated) 단위 테스트를 위해 외부 의존성을 임의로 주입하기 위해서 사용한다.

테스트 더블은 일반적으로 자바스크립트 객체 혹은 함수를 직접 변경하거나 생성하는 형태로 사용되며, Jest에서는 모듈 단위로 사용할 수 있는 기능도 제공한다.

테스트 실행 환경

브라우저

실제 브라우저를 실행해서 테스트 코드를 실행하는 방식을 의미하며, E2E 테스트 도구들을 제외한다면 현재로서는 Karma를 사용하는 것이 유일한 방법이라 할 수 있다. Karma는 테스트 러너의 역할만 하기 때문에 별도의 테스트 프레임워크가 추가로 필요하며, 보통 Jasmine을 사용하기를 권장한다.

커맨드 라인에서 Karma를 실행하면 먼저 자체 웹서버를 구동한 후 테스트 실행을 위한 HTML 페이지를 만들고, 작성된 테스트 코드 및 소스 코드 전부를 해당 페이지에 로드한다. 이후 브라우저를 직접 실행해서 해당 웹페이지에 접속하면 로드된 코드가 실행되고 테스트의 실행 결과는 브라우저 콘솔에 출력된다. Karma는 이 정보를 받아와 지정된 리포터를 사용해 결과를 정리한 후 커맨드 라인에 보여준다.

이 방식의 가장 큰 장점은 실제 브라우저 환경에서 테스트하기 때문에 브라우저의 모든 기능(네트워크 IO, 렌더링 엔진 등)을 활용해서 테스트할 수 있다는 점이다. 또한 Selenium 등의 도구를 사용하면 동일한 테스트 코드를 다양한 환경(운영체제, 브라우저) 테스트를 실행할 수 있기 때문에, 브라우저 호환성 및 기기 환경에 대한 테스트도 진행할 수 있다.

하지만 브라우저의 프로세스가 Node.js의 프로세스보다 무겁기 때문에 테스트의 초기 구동 속도가 더 느리다는 단점이 있다. 또한 브라우저라는 별도의 애플리케이션을 추가로 실행해야 하기 때문에 실행을 위한 브라우저 런처(launcher) 등을 추가로 설치해 주어야 하는 번거로움이 있으며, 크로스 브라우징 테스트 등을 위해서 별도의 환경을 구축하고 유지보수 하는 비용도 결코 무시할 수 없다.

이러한 단점을 극복하기 위해서 보통 개발 단계에서는 헤드리스 브라우저를 사용해서 빠른 피드백을 얻을 수 있도록 하고, 개발 완료 혹은 배포 시에만 CI 서버와 통합하여 크로스 브라우징 테스트를 하는 방식을 권장한다. 또한 Browser Stack이나 Sauce Lab 등의 외부 서비스를 사용하면 크로스 브라우징을 위한 환경을 직접 구축할 필요 없이 Karma와 손쉽게 연동하여 사용할 수 있다.

Node.js

Node.js 환경에서 테스트 코드를 실행하는 방식을 의미하며, 최근 가장 많이 쓰이는 도구는 Mocha와 Jest이다. 위에서 언급한 것처럼 테스트 러너와 테스트 프레임워크가 통합되어 있어 설치 및 실행이 비교적 간단하다. 이 방식의 가장 큰 장점은 역시 속도인데, Node.js의 프로세스가 브라우저의 프로세스에 비해 훨씬 가볍기 때문에 실행 속도가 빠르다. 또한 브라우저에서는 아직 모듈 단위의 테스트를 실행하기가 어려워 webpack 등의 번들러를 사용해야 하는 제약이 있는 반면 Node.js 환경에서는 개별 프로세스에서 원하는 모듈만 가져와서(import) 테스트할 수 있기 때문에 훨씬 간단하고 안전한 방식으로 테스트를 할 수 있다.

반면 이 방식의 중요한 단점은 브라우저의 모든 API를 제대로 활용할 수 없다는 것이다. Node.js에는 브라우저가 제공하는 DOM(Document Object Model)이나 BOM(Browser Object Model) 등의 API가 없기 때문이다. 이 문제를 해결하기 위해 jsdom과 같은 라이브러리를 사용해서 브라우저 환경을 가상으로 구현하는 방식을 사용하고 있지만, 실제 브라우저의 동작을 100% 구현하지는 못하기 때문에 많은 제약이 있다. 예를 들어 렌더링 엔진을 갖고 있지 않기 때문에 UI 요소의 레이아웃에 대한 테스트를 할 수 없고, 내비게이션 관련 동작도 사용할 수 없다. 그뿐만 아니라 브라우저에서 실행할 수가 없기 때문에 크로스 브라우징에 대한 테스트도 할 수 없다.

브라우저 vs Node.js

위의 설명을 보고도 아직 어떤 환경을 선택해야 할지 고민된다면 다음의 가이드를 따르기를 권장한다.

  1. 크로스 브라우징 테스트가 “반드시” 필요한 경우 브라우저 환경을 사용한다.
  2. 브라우저의 실제 동작(렌더링, 네트워크 IO, 내비게이션 등)에 대한 테스트가 필요한 경우 브라우저 환경을 사용한다.
  3. 그 외의 경우 Node.js 환경을 사용한다.
  • 브라우저 : Karma + Jasmine
  • Node.js : Jest

유닛, 통합 테스트 도구

  1. karma

    1. 앞서 Jasmine으로 작성한 테스트 코드를 브라우저 환경에서 실행하기 위해서는 별도의 페이지를 생성하고, 소스 코드 및 테스트 코드 등을 모두 로드하는 등의 작업이 추가로 필요하다. 또한 테스트 결과를 확인하기 위해서는 UI를 추가하거나 브라우저 개발자 도구의 콘솔 창을 사용할 수밖에 없다. Karma는 브라우저 환경에서 테스트를 할 때 이러한 일련의 작업을 대신해 주는 도구로서, 아래와 같은 기능을 제공한다.
      • 로컬 웹서버를 구동한 후 테스트에 필요한 소스 코드 및 리소스를 모두 로드하는 HTML 페이지를 생성한다.
      • 지정된 브라우저 프로세스를 자동으로 실행한 후 앞서 생성한 웹페이지의 URL에 접속한다.
      • 브라우저에서 실행된 결과를 받아와서 지정된 리포터를 사용해 다양한 형식으로 출력한다.
    2. 브라우저를 자동으로 실행하려면 각 브라우저에 맞는 런처를 별도로 설치해야 한다. 예를 들어 크롬 브라우저를 실행하기 위해서는 karma-chrome-launcher를 설치해야 한다. 크롬 외에도 다양한 브라우저 런처를 제공하며, 지원 브라우저의 목록은 공식 홈페이지에서 확인할 수 있다.
    3. Karma는 프로젝트의 최상위 폴더에 있는 karma.config.js 파일로 설정 파일을 관리한다.

      module.exports = (config) => {
        config.set({
          frameworks: ["jasmine"], // Jasmine 테스트 프레임워크 사용
          files: [
            "src/**/*.js", // 소스 파일 경로
            "test/**/*.spec.js", // 테스트 파일 경로
          ],
          reporters: ["dots"], // 리포터 지정 (점 형태로 결과 출력)
          browsers: ["Chrome"], // 크롬 브라우저를 자동 실행을 위한 런처 지정
          singleRun: true, // 테스트 1회 실행 후 Karma 종료
        });
      };
      
    4. 테스트 커버리지 측정 :

      작성한 테스트 코드의 커버리지는 Istanbul 라이브러리를 사용해 측정할 수 있다. Istanbul은 소스 코드를 분석해서 모든 줄마다 실행 횟수를 측정할 수 있는 코드를 삽입하는 방식으로 커버리지를 측정한다. 코드 실행 후에는 실행 결과를 HTML, LCOV, Cobertura 등의 다양한 포맷으로 출력해 주며, CI 서버에 연동해서 사용할 수도 있다.

      Istanbul을 커맨드 라인에서 직접 실행할 수도 있지만, 일반적으로는 테스트 러너가 플러그인 형태로 제공하는 것을 사용한다. Karma의 경우 karma-coverage 플러그인을 사용하면 istanbul을 사용한 커버리지 측정 결과를 손쉽게 확인할 수 있다.

      1. 코드 커버리지 : 코드 커버리지란, 테스트 코드가 프로덕션 코드를 얼마나 실행했는지를 백분율로 나타내는 지표이다. 즉, 테스트 코드가 실제로 프로덕션 코드를 얼마나 몇 퍼센트 검증하고 있는지를 나타낸다. 코드 커버리지를 통해 현재 작성된 테스트 코드의 수가 충분한것인지 논의할 수 있다.

        module.exports = (config) => {
          config.set({
            frameworks: ["jasmine"],
            files: ["src/**/*.js", "test/**/*.spec.js"],
            reporters: ["dots", "coverage"], // 커버리지 리포터 추가
            coverageReporter: {
              type: "html", // 커버리지 출력 형식 지정
              dir: "coverage", // 커버리지 결과가 저장될 폴더를 지정
            },
            browsers: ["Chrome"],
            singleRun: true,
            preprocessors: {
              "src/**/*.js": ["coverage"], // 전체 소스 코드에 대해 커버리지 측정을 위한 전처리 지정 (커버리지 측정을 원하는 소스파일)
            },
          });
        };
        
    5. 크로스 브라우징 테스트 :

      더 많은 기기를 지원해야 하는 프로젝트에서는 개발자 PC에서만 테스트를 하기가 불가능한 경우도 있다. 예를 들어 인터넷 익스플로러는 윈도우가 설치된 하나의 PC에 하나의 버전만 설치할 수 있기 때문에, 여러 버전의 인터넷 익스플로러를 테스트해야 하는 경우 여러 개의 PC 혹은 가상 머신을 사용할 수 밖에 없다.

      이런 경우 karma와 Selenium WebDriver를 연결하여 사용하면 원격 PC를 사용해 테스트를 실행하고, 결과를 한 곳에 모아 출력할 수 있다. 간단히 설명하면 위의 예제에서 사용한 Chrome 런처 대신를 karma-webdriver-launcher로 변경해 주기만 하면 된다. 그러면 다음 그림과 같이 Hub의 역할을 하는 기기를 통해 연결된 원격 PC가 로컬 Karma 서버에 접근하도록 만들어 테스트를 실행하게 된다.

      스크린샷 2023-09-01 오후 3.38.15.png

  2. jasmine

    1. Jasmine은 BDD 스타일의 단언 API를 사용하는 통합 테스트 프레임워크이며, Node.js와 브라우저 환경 모두에서 사용 가능하다. Mocha의 경우 단언 라이브러리는 Chai, 테스트 더블은 Sinon을 사용해야 하는 반면, Jasmine은 모든 기능을 통합해서 제공하기 때문에 라이브러리를 추가로 설치하고 설정할 필요 없이 쉽게 사용할 수 있다.
    2. it() : 테스트 명세
    3. expect() : 명세를 실행할 함수 내에서 검증을 위한 단언
    4. describe() : 테스트를 그룹화함 중첩해서 사용 가능
    5. beforeEach(), afterEach() : 각 테스트 명세가 실행되기 전 혹은 실행된 후에 필요한 로직 정의
    6. spy : 자바스크립트에서 모의 객체를 사용할 때 가장 유용하게 사용되는 테스트 더블 중의 하나, 단순히 객체를 대신하는 역할을 할 뿐 아니라 실제 함수가 몇 번 호출되었는지, 어떤 인자를 넘겨주었는지 등의 정보를 모두 저장하고 있기 때문에 이러한 정보를 검증에 활용할 수 있다.
      1. spyOn()
      2. toHaveBeenCalledWith()
    7. clock() : 타이머 제어
    8. done() : 비동기 테스트 > 콜백함수 사용 / it((done)⇒done()) done()이 실행될 때 까지 테스트를 종료하지않고 대기함
    9. fetchData() : 비동기 테스트 > 프로미스 사용 / 프로미스가 해결 될 때까지 테스트가 종료하지않고 대기함

      it("fetchData: 프라미스를 반환한다", () => {
        return api.fetchData().then((response) => {
          expect(response).toEqual({
            success: true,
          });
        });
      });
      
    10. async await : 비동기 테스트 > async/await 사용 / it의 콜백함수로 async 함수를 직접 넘길 수 있음.

      it("fetchData: 프라미스를 반환한다.", async () => {
        const response = await api.fetchData();
        expect(response).toEqual({
          success: true,
        });
      });
      
  3. jest

    1. Jest는 페이스북에서 만든 오픈소스 테스트 프레임워크이며, 최근 프론트엔드 개발에서 가장 활발하게 사용되는 테스트 도구이다. 꽤 오랜 기간 동안 개발되어 왔음에도 불구하고 한동안 관심을 받지 못하다가, 최근에 안정성 및 성능이 눈에 띄게 좋아지면서 많은 인기를 끌고 있다. Karma와는 다르게 Node.js 환경에서 실행되며, 내부적으로 Jasmine 스타일의 단언 API를 사용하기 때문에 기존에 Jasmine을 사용하고 있던 사용자들도 쉽게 적응할 수 있다.
      1. 쉬운 설치 및 실행
        1. jest.conf.js 파일을 생성해서 설정 옵션들을 설정 https://jestjs.io/docs/configuration
      2. 쉬운 커버리지 측정
        1. 커버리지를 확인하기 위해서는 커맨드 라인에서 실행할 때 --coverage 옵션만 추가하면 된다.
      3. jsdom 내장
        1. 앞서 언급했듯이 Node.js 환경에서는 브라우저에서 제공하는 DOM이나 window 객체의 API를 사용할 수 없다. 그래서 프론트엔드 코드를 테스트하기 위해서는 이러한 API를 가상으로 구현한 환경이 추가로 필요한데, 그중 가장 완성도가 높고 널리 쓰이는 라이브러리가 바로 jsdom이다. 하지만 jsdom은 라이브러리 형태로 제공되기 때문에 실제 사용하기 위해서는 테스트를 실행할 때마다 초기화 관련 코드를 실행해 주어야 하며, 이는 브라우저 환경에서 직접 실행하는 것에 비해 번거로울 수밖에 없다.
        2. Jest에서는 jsdom을 내장하여 테스트 실행할 때마다 필요한 환경을 자동으로 설정해서 제공하기 때문에, 별다른 추가 작업 없이 마치 브라우저 환경인 것 처럼 테스트를 작성할 수 있다.
      4. 스냅샷 테스트
        1. 스냅샷 테스트는 주로 리액트의 가상 DOM 구조를 비교하기 위해 사용되는데, 테스트를 작성할 때 다음과 같이 toMatchSnapshot() 함수만을 사용하면 된다.
      5. 테스트 파일 필터링
        1. Jest는 테스트할 대상 파일을 구체적으로 지정할 수 있는 기능을 제공한다. 우선 Jest는 기본적으로 Git과 같은 버전 관리 도구와 연동하여 마지막 커밋 이후에 변경 사항이 있는 파일만을 테스트 대상에 포함한다. 이를 통해 이미 검증된 파일에 대해서 불필요한 테스트를 매번 실행하는 것을 방지할 수 있다.
      6. 샌드박스 병렬 테스트
        1. node.js 환경에서 테스트하기 때문에 기본적으로 빠름. 근데, 테스트를 순차적으로 실행하면서 개별 테스트마다 새로운 자식 프로세스를 생성하게되면 단일 프로세스에서 실행하는 것보다 느려지게 됨.이 문 제를 해결하기 위해 Jest는 다수의 프로세스를 병렬로 실행하는 방식을 사용해 속도를 향상시킴

E2E 테스트 도구

지금까지 살펴본 Karma, Jest 등의 도구들은 모두 단위 테스트나 통합 테스트를 위한 도구라고 볼 수 있다. 사실 E2E 테스트는 작성이 번거롭고 실행 속도가 느리며 통제된 환경에서 테스트를 할 수 없다는 단점 때문에 개발자들이 개발 과정에 사용하기에는 어려움이 많았다. 하지만 프론트엔드 개발의 경우 UI/UX 관련 기능을 실제 사용자 환경과 분리된 상태에서 테스트하기에는 한계가 있었기 때문에, 사용자의 관점에서 테스트를 할 수 있는 E2E 테스트에 대한 필요성이 꾸준히 요구되어 왔다.

  1. Cypress
    1. WebDriver와는 다르게 실제 애플리케이션과 테스트 코드를 동일한 브라우저에서 실행하는 방식을 취하고 있다. 이 방식은 HTTP 등을 사용한 프로세스 사이의 통신이 필요 없이 동일한 프로세스 내부에서 테스트를 실행하기 때문에 테스트를 훨씬 빠르고 안정적으로 실행할 수 있다. 또한 브라우저 기반의 GUI를 사용하여 테스트의 실행 상태를 확인하고 디버깅할 수 있는 다양한 편의 기능을 제공한다.
    2. 예를 들면, 실행된 모든 테스트 명령과 각 명령이 실행될 때의 UI 상태를 스냅샷 형태로 모두 저장하고 있어, 특정 시점의 UI 상태를 눈으로 확인할 수 있다. 또한 전체 테스트 진행 과정을 동영상으로 저장하거나 테스트가 실패했을 때 자동으로 스크린샷을 남길 수 있어 테스트가 실패했을 때 원인을 파악하기가 매우 쉽다. 게다가 브라우저에서 실행되기 때문에 필요한 경우 크롬 개발자 도구를 사용해 디버깅을 할 수도 있다.
    3. 하지만 브라우저 내부에서 실행되는 방식에는 단점 또한 존재하며, Cypress의 공식 문서에 잘 정리되어 있다.