Overview

웹 프론트 개발을 할 때 대부분 반복적인 테스트 작업을 하고 있습니다. 하나의 기능을 개발하고 웹 브라우저에 접속하여 기능을 테스트하고 수정하고 합니다. 이러한 반복된 테스트 작업을 줄이기 위해 코드로 작성해서 자동화를 하게 되면 테스트가 누락되거나 잘못 검증하는 등의 실수도 방지할 수 있으며, 이는 곧 코드의 품질 향상으로 이어지게 됩니다.

그래서 현재 운영중인 프로젝트들이 Vuejs로 되어있기 때문에 Vuejs에 적합한 Jest라는 프레임워크를 찾아서 적용해 보기로 했습니다. 현재 운영중인 프로젝트엔 바로 도입하기 어렵기 때문에 데모 프로젝트를 하나 생성하여 시작하기로 합니다.

설정하기

vue-cli 를 사용하여 project 생성

  • Vue CLI 는 Jest를 사용해 어려운 설정 없이 유닛테스트를 진행하기 위한 옵션이 있습니다.
  • test runner 선택시 jest를 선택합니다.
$ vue init webpack 프로젝트명
jest


  • project 가 생성되었습니다. 아래는 생성된 project 폴더 구조 입니다.
  • Jest는 기본적으로 test.js 또는 spec.js로 끝나는 파일을 찾아 테스트를 실행합니다.
  • 테스팅 할 파일은 test > unit > specs 폴더 안에 테스트할 컴포넌트와 동일한 파일명으로 생성하면 됩니다.
jest


간단한 예제로 Vue Component 테스트

1. Vue Component 생성

  • 간단한 사칙연산이 가능한 화면을 구현합니다.
  • component > HelloWorld.vue

<template>
  <div class="hello">
    <h1>{{ message }}</h1>
    <ul>
      <li><input type="text" v-model="frist" /></li>
      <li><input type="text" v-model="second" /></li>
      <li> = {{ total }} </li>
    </ul>
    <ul>
      <li><input type="button" value="더하기" @click="plus(frist, second)" /></li>
      <li><input type="button" value="빼기" @click="minus(frist, second)" /></li>
      <li><input type="button" value="곱하기" @click="multiply(frist, second)" /></li>
      <li><input type="button" value="나누기(반올림)" @click="divide(frist, second)" /></li>
    </ul>
    <h2></h2> <!-- 결과값 -->
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      message: 'Unit Test',
      frist: 7,
      second: 2,
      total: 0
    }
  },
  methods: {
    plus (frist, second) {
      this.total = parseInt(frist) + parseInt(second)
      return this.total
    },
    minus (frist, second) {
      this.total = parseInt(frist) - parseInt(second)
      return this.total
    },
    multiply (frist, second) {
      if (parseInt(frist) == 0 || parseInt(second) == 0) {
        return false
      }
      this.total = parseInt(frist) * parseInt(second)
      return this.total
    },
    divide (frist, second) {
      if (parseInt(frist) == 0 || parseInt(second) == 0) {
        return false
      }
      this.total = parseInt(frist) / parseInt(second)
      this.total = Math.round(this.total) // 소스점 반올림
      return this.total
    }
  }
}
</script>


2. 테스트파일을 생성

  • 테스트 코드를 작성합니다. 테스트 코드 작성시 describe()를 사용하여 그룹으로 묶어주면 테스트 코드가 늘어날 경우에도 쉽게 관리할 수 있으며, 테스트 결과도 그룹별로 정리해서 보여 줍니다.
  • test > unit > HelloWorld.spec.js
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'

let Constructor
let vm
let frist
let second

// 초기화
beforeEach(() => {
  Constructor = Vue.extend(HelloWorld)
  vm = new Constructor().$mount()
  frist = vm._data.frist
  second = vm._data.second
})

describe('HelloWorld.vue', () => {
  describe('유효성 검사', () => {
    it('숫자가 아닌 Null 또는 문자열을 입력할 경우 결과값이 에러(NaN)인지 확인한다.', () => {
      second = ''
      // NaN값인 경우
      expect(vm.plus(frist, second)).toBeNaN()
      expect(vm.minus(frist, second)).toBeNaN()
      expect(vm.multiply(frist, second)).toBeNaN()
      expect(vm.divide(frist, second)).toBeNaN()
    })
    it('0으로 곱하기 또는 나누기 하려고 하는지 확인한다.', () => {
      second = 0
      // toBeFalsy : if 문의 리턴값이 false 인 경우
      expect(vm.multiply(frist, 0)).toBeFalsy()
      expect(vm.divide(frist, 0)).toBeFalsy()
    })
  })
  describe('사칙연산 테스트', () => {
    it('더하기의 결과값을 비교하여 일치한지 확인한다.', () => {
      // toBe : 기본값을 비교할때 사용
      expect(vm.plus(frist, second)).toBe(9)
    })
    it('뺴기 결과값을 비교하여 정수인지 음수인지 확인한다.', () => {
      expect(vm.minus(frist, second)).toBe(5)
    })
    it('곱하기의 결과값을 비교하여 일치한지 확인한다.', () => {
      expect(vm.multiply(frist, second)).toBe(14)
    })
    it('나누기의 결과값을 비교하여 소스점 반올림이 되었는지 확인한다.', () => {
      // toBeCloseTo : 소수점 다음에 확인할 자리 수를 제어하는데 사용
      expect(vm.divide(frist, second)).toBeCloseTo(4, 5)
    })
  })
})


3. 테스트 결과

  • 테스트를 실행합니다.
$ npm run unit
  • 테스트 결과 화면 입니다.
  • 컴포넌트 메소드의 모든 Line을 테스트 하여 Stmts, Branch, Funcs, Lines 테스트가 100% 진행완료 되었습니다.
jest


  • 만약 메소드의 함수를 테스트 하지 않았거나, 함수안에 if문의 결과값을 테스트 하지 않을 경우 100% 달성을 하지 못하면 Uncovered Line에 테스트 진행하지 않은 줄수를 표시하여 줍니다.
  • 테스트 코드에서 Vue Component > Method > Function > IF문을 테스트 하지 않은 결과내용 입니다.
jest


Code Coverage

Statements, Lines

  • 비슷한 성격으로 전체 코드라인 또는 명령문이 얼마나 수행되었는지를 확인합니다.

Branches

  • 전체 코드중 분기문이 얼마나 수행 되었는지를 확인합니다.
  • 아래 코드처럼 if문에 대한 테스트를 수행할 경우 100%의 결과가 나옵니다.
multiply (frist, second) {
  if (parseInt(frist) == 0 || parseInt(second) == 0) {
    return false
  }
  this.total = parseInt(frist) * parseInt(second)
  return this.total
},
it('0으로 곱하기 또는 나누기 하려고 하는지 확인한다.', () => {
  second = 0
  // toBeFalsy : if 문의 리턴값이 false 인 경우
  expect(vm.multiply(frist, 0)).toBeFalsy()
  expect(vm.divide(frist, 0)).toBeFalsy()
})


Functions

  • 전체 코드중 함수가 얼마나 수행 되었는지를 확인합니다.
  • 아래 코드처럼 함수에 대한 테스트를 수행할 경우 100%의 결과가 나옵니다.
plus (frist, second) {
  this.total = parseInt(frist) + parseInt(second)
  return this.total
},
it('더하기의 결과값을 비교하여 일치한지 확인한다.', () => {
  // toBe : 기본값을 비교할때 사용
  expect(vm.plus(frist, second)).toBe(9)
}


Uncovered Line

  • 수행되지 않은 코드 라인을 알려 줍니다.

Conclusion

Jest를 사용하여 Vue Component Test를 해 보았습니다. 간단한 Unit Test로 밖에 사용하지 못하였지만 Jest에는 Snapshot이라는 UI Test, 타이머제어, 비동기 테스트등 다양한 테스트를 할수 있는 기능이 있습니다.

간단한 예제를 통해 단위 테스트를 진행해 보았더니 브라우저 열어서 값을 입력하고 결과값을 확인하고 하는 반복적인 동작을 줄일 수 있어 편리하고, 테스트의 양이 많으면 반복적으로 하더라도 누락되는 실수를 방지할 수 있을 거라 생각됩니다!

참고

https://jestjs.io/docs/en/getting-started
https://vuejs.org/v2/guide/unit-testing.html
https://vue-test-utils.vuejs.org/guides/#testing-single-file-components-with-jest
https://blog.octo.com/tdd-with-vue-js/


이경희 대리 | R&D 개발3팀
leekh@brandi.co.kr
브랜디, 오직 예쁜 옷만