Front-end Test

1) Why Should We Test

개인이 혼자서 작성한 소규모 프로젝트이거나 개인이 한 프로젝트에 대한 지속적이고 충분한 이해가 가능한 상황이라면, 테스트의 중요도는 현저히 낮아진다. 하지만, 여러 사람이 파트를 나누어 중간 규모 이상의 프로젝트를 개발하는 상황에 테스트가 없으면 상위자의 “잘 돌아가?” 라는 질문에 쉽사리 대답할 수 없게 되며, 작게는 개발자 개개인을 불안과 불행에 빠지게 하고, 크게는 프로젝트 전체의 견고함을 보장할 수 없게 된다.

따라서 테스트를 짠다는 것은 새로운 기능추가에도 코드의 견고함을 보장할 수 있는 수단이 생기게 된다는 뜻이며, 더 나아가 어떤 컴포넌트가 어떤 기능을 하기 위해 설계되었으며, 구체적으로 어떻게 활용할 수 있는지도 코드로 공유된다는 뜻이다. 하지만 기능을 짠 뒤에 테스트 코드를 짠다는 것은 굉장히 귀찮은 일이다. 이미 잘 돌아가고 있는 것 같은데, 굳이 그것을 왜 또 테스트로 짜야하며, 어떻게 짜야하는지 조차 명확하지 않다.

이 글에서는 테스트를 잘 짜기 위한 방법론과 그것을 실제로 활용하여 코드를 짜는 과정에 대해서 설명하고, 프론트엔드 테스트만의 특징 / JavaScript Eco-System의 Testing Convention / Testing Framework 등을 설명하고, 마지막에는 그것을 활용한 예제들을 살펴본다.

2) What is TDD

테스트 주도 개발, Test-Driven Development (이하 TDD) 에 대해 상세히 설명하는 것은 이 문서에서 다룰 수 있는 범위를 벗어나므로 간략한 개념과 절차만을 소개한다. 테스트로 주도되는 개발이란 무엇일까? TDD의 창시자 (혹은 재발견자)로 여겨지는 Kent Beck은 TDD를 하기 위해서는 다음과 같은 절차를 따라야한다고 소개한다.

  1. 오직 자동화된 테스트가 실패할 경우에만 새로운 코드를 작성한다.
  2. 중복을 제거한다.

본질은 ==새로운 기능을 작성하기 에, 그 기능을 테스트하는 코드가 먼저 작성되어야 한다는 발상이다.== 컴파일도 안되는 코드를 작성한다는 것에 생리적인 거부감이 생길 수 있다. 이해한다. 하지만 그렇게 생성된 모듈을 가져다 쓸 자신이, 보기 좋고 쓰기 좋은 형식으로 미리 함수의 prototype을 정의해본다는 것이 코드 퀄리티를 올려주게 되며, 생각보다 재미있다.

테스트 주도 개발이라는 것은 생각보다 별 거 아닌 개념이다. (제발 쉽게 생각해달라는 뜻). 그저, 우리가 어떤 클래스를 작성할 때 클래스로부터 객체를 main function에서 만들어서, 이것 저것 printf() 해보며 동작을 확인하는 코드를, some-class.test.js 라는 파일에 영속적으로 저장하는 것일 뿐이다. 그리고 그 코드는 우리가 새로운 기능을 추가할 때마다 새로운 기능이 이전의 기능을 침식하는지를 알아서 테스트해 줄 것이다.

​ TDD의 자세한 설명은 테스트 주도 개발 책이나 다음 링크들을 참고.

3) Testing Levels & Frameworks

소프트웨어 테스트의 레벨은 보통 4단계 (유닛, 통합, 컴포넌트 인터페이스, 시스템)으로 나뉘게 되는데, JavaScript에서는 보통 Unit Test와 E2E Test(= System Test)로 나누어서 테스트를 작성한다.

Unit Test(= 단위 테스트) 는 각 컴포넌트 단위로 테스트를 작성하는 것을 의미한다. 외부 컴포넌트가 미리 정의된 규칙으로 (= 정상적으로) 작동한다는 가정하에 테스트하려는 해당 컴포넌트가 제대로 동작하는지를 확인한다. 즉, 의존성을 제거한 채 짜는 단위 테스트가 좋은 단위 테스트다. JavaScript 환경에서 단위 테스트는 Enzyme, Jasmine, Mocha, Chai, Jest 등의 프레임워크를 사용한다.

E2E Test (= 종단간 테스트) 는 전체 시스템이 제대로 작동하는지 확인하기 위한 테스트로, 최대한 실제 시스템을 이용하는 환경의 유저 관점에서 시뮬레이션한다.1 유저가 움직일거라 예상하는 시나리오를 매크로와 비슷한 스크립트를 작성하여 브라우저 내에서 자동적으로 시뮬레이션 하는 방식이 일반적이며, JavaScript 환경에서 종단간 테스트는 Phantom.js, Slimer.js, Casper.js, Webdriver, Cucumber.js, Protractor, Nightwatch 등의 프레임워크를 사용한다.

어떤 테스트에서 어떤 프레임워크를 사용하냐는 개인의 취향이 짙게 반영되는 부분이지만, 최근 ==유닛 테스트에서는== Facebook에서 개발하고 Zero-configuration을 지향하는 ==Jest 를 주로 사용==하며, ==종단간 테스트에서는== Selenium을 사용하기 좋게 래핑한 ==Nightwatch.js 를 주로 사용==하는 추세다. 본 문서에서도 러닝 커브가 가장 완만하다고 느껴지는 이 두 프레임워크를 중심으로 설명할 예정이다.

4) Front-end Unit Test

프론트엔드의 단위 테스트는 크게 렌더링이 정상적으로 되고 있는지를 테스트하거나, API 호출의 결과로 정상적인 값이 계산 / 저장되는가를 테스트한다. 이 때, 네트워크를 사용하는 부분에 있어서는 그 부분에 해당하는 API 호출을 실제로 수행하기 보다는 캐싱해놓은 파일을 읽어들여 수행하는 것이 일반적인데, API 캐싱 (조금 더 정확하게는 API Mocking, Mock Function) 은 다음과 같은 장점이 있다.

  1. 앞서 언급한 외부 의존성을 제거할 수 있다.

    실제로 백엔드 서버가 돌아가고 있지 않거나 사용하려는 API가 정상적으로 동작하지 않는 상황에서도 프론트의 유닛 테스트는 언제나 실행된다.

  2. 테스트 실행속도가 매우 빨라진다.

    네트워크 호출을 굉장히 지연이 긴 함수 호출로 간주할 수 있다. 이런 함수 호출이 여러 개 쌓이다보면 테스트를 한 번 돌리는 데에 너무 많은 시간이 소요될 것이라는 것을 쉽게 예상할 수 있다. 실제로 간단한 테스트들은 0.x-2ms 내로 실행되는 반면 네트워크 직접 호출 한다면 각 호출마다 200-500ms 의 시간이 소요된다.

5) E2E Test

프론트엔드에서의 E2E 테스트는 대게 다음과 같은 방식으로 이루어진다.

  1. 사용자의 시나리오를 작성한다.
  2. 시나리오대로 동작하는 E2E Test Script를 작성하여 사용자의 행동을 자동적으로 수행하는 매크로를 짠다.
  3. 해당 시나리오가 정상적으로 성공하는지, 또는 특정 의도된 상황에서 우리가 원하는 대로 에러를 내뱉는지를 테스트한다.

E2E 테스트의 시나리오는 보통…

.. 와 같은 형식으로 짜여지게 되며, e2e 테스트의 양은 보통 유닛 테스트의 그것보다는 훨씬 적은 편이며 실제로 많은 테스트 가이드라인에서는 종단간 테스트의 양을 적당량으로 유지하라는 조언도 있다. 2

6) Unit Test Examples

– Prerequisite

  1. Jest Introduction + Guides
  2. Official Vue Unit Tesing
  3. Vuex Testing

– Simple Jest example

일반적인 Jest 테스트 코드는 다음과 같이 이루어진다. 테스트하려는 컴포넌트를 불러와서 (line 2), 그것을 describe 함수로 감싸고 (line 4), test 함수에서 컴포넌트의 행동과 상태를 기술한다. 만약에 내가 기대하는 어떤 변수가 어떤 값을 갖지 않는 경우에는 해당 테스트가 실패한 것으로 간주되며, test 함수 하나가 곧 하나의 테스트 단위이다.

– Jest example with axios

일반적으로 프론트엔드에서 http 요청을 처리하는 라/이브러리는 axios를 주로 사용하는데, jest에서도 이를 Mocking 하는 기능을 지원한다.3 앞서 언급한 API 호출의 캐싱을 처리하는 예제 코드는 다음과 같다.


some-class2.jsline5에서 axios 라이브러리를 mocking 한다. 그리고 axios.get return 값을 지정해주기 위해 axios.get.mockResolvedValue() 함수를 이용해서 mocked_response.json 의 내용을 넣어주는 모습이다. 이렇게 모킹된 값은 sc.getUserInfo() 내부에 작성된 axios.get() 의 호출을 대신하여 반환된다.

– Test example written with vue-test-utils & Jest

Vue Component를 테스트할 때 도움을 주는 함수들을 모아놓은 라이브러리가 있다. Vue.js Official Github에서 관리하는 이 라이브러리는 우리가 사용하려는 Jest에 대한 예제코드도 있고, 유지보수도 끊기지 않고 진행되고 있으니 본 분서에서는 이 라이브러리를 활용하여 뷰 컴포넌트를 테스트한다.

vue-test-utils 에는 Vue Component를 객체화 해주는 두 가지 Helper 함수가 있다. 하나는 mount() 함수로 우리의 컴포넌트가 브라우저 상에서 마운트 됐을 때 출력될 html을 그대로 보여주는 함수다. 하지만, 이는 단위 테스트가 다른 컴포넌트에 의존성을 갖게 만드는 단점을 가지고 있다. 즉, 우리가 테스트 하려는 컴포넌트가 가지고 있는 자식 컴포넌트의 렌더링 결과물까지 출력하는 것은 우리가 원하는 결과가 아니라는 것.

이런 상황에서는 shallow() 함수를 이용하면 자식 컴포넌트의 렌더링을 막아 의존성을 없애고, 렌더링의 범위도 좁아져서 테스트 속도가 빨라지는 부가적인 장점도 생긴다. 다음은 vue-test-utils의 공식 깃허브 예제 레포4의 코드다.

위 코드에서 뷰 컴포넌트의 prop을 shallow 함수의 두 번째 인자로 넘겨서 초기화하는 것을 볼 수 있다. 두 번째 테스트 코드의 snapshot testing에 대한 상세한 기술은 본 문서의 범위를 벗어나므로 간략하게 설명하자면, 렌더링된 결과물을 저장해놓았다가 이전 테스트에서 렌더링된 결과물과 달라진 경우 그 변경이 의도된 것인지 아닌지를 물어봐주는 테스트. (의도된 변경인 경우 해당 컴포넌트의 스펙이 변경됐다고 판단, 저장된 결과물을 업데이트한다.) 상세한 설명은 이 곳에서 참조!

7) E2E Test Examples

– Prerequisite

– Test example written with Nightwatch

Nightwatch의 스크립트 작성 컨벤션은 이 링크BDD Expect Assertions 섹션을 보는 것이 좋다. BDD는 TDD와 다른 개념이 아니라, Jest에서도 채용하고 있는 TDD 작성 가이드 쯤 (…) 으로 보고 자세한 설명은 위와 같은 이유로 생략하는 점 양해를 구한다. 정말 간단하게 설명해서, assert 대신에 expect를 사용하면 BDD다.

훨씬 더 readable한 테스트 코드가 생산되며, Jest의 그것과도 convention을 같이하며, 무엇보다 표현력이 assert based code보다 풍부한 것이 장점이다. 시나리오를 작성하여 원하는 대로 사이트가 반응하는지를 테스트 하면 된다. 코드를 조금 더 상세히 설명을 해보자면,

  • line3-5에서 페이지를 열고 기다린다.
  • client.expect.element() 함수를 이용하여 브라우저에 있는 element를 css selector로 선택하여 특정한다.
  • 특정한 엘리먼트가 가져야하는 상태 / 값을 서술한다.
  • line 22 client.url() 로 열었던 페이지를 닫는다.

이 외에, 여러가지 테스트에서 반복적으로 접근해야하는 엘리먼트나 섹션은 미리 정의하여 바로가기처럼 접근할 수 있는 편의기능도 있다.

종단간 테스트는 코딩이 어렵다기 보다는 시나리오를 적절하게 잡는 것이 어려운 부분이니 그것에 대한 작성은 개개인의 센스에 맡기는 걸로…

8) Thank you!

읽어주셔서 감사! 여기서부터는 기타 팁들…

  • TypeScript로 작성된 모듈을 불러와서 작업하는 경우, 해당 class의 priavte method를 테스트 하기 위해서는 ``(c as any).privateMethodName() 와 같이, 해당 객체를 any로 캐스팅하여 pirvate method를 호출할 수 있다.
  • 사실 테스트 작성보다 더 힘든게 테스트 환경 구축이다. vue-cli에서는 대부분의 환경세팅을 위와 같이 해주고, package.json에서 해당 테스트를 구동시킬 수 있는 명령어를 확인할 수 있다.
  • 컴포넌트를 개발하면서 특정 컴포넌트를 어딘가에 일단 마운트 시켜서 이것 저것 해보는 경우가 많은데, 아예 그런 개발을 위해서 만들어진 툴, storybook도 있다. 해당 컴포넌트가 가질 수 있는 상태들을 독립적으로 테스트 할 수 있는 훌륭한 개발도구다.
  • yarn test --watch 로 test를 실행시키면 jest가 파일 변화를 감지해서 파일이 변할 때 마다 테스트를 돌려준다.

 

Leave a Reply