Posts 모던 자바스크립트 #3.5. 테스트 자동화와 Mocha
Post
Cancel

모던 자바스크립트 #3.5. 테스트 자동화와 Mocha

모던 JavaScript 튜토리얼을 따라가면서 정리합니다.

3.5. 테스트 자동화와 Mocha

테스트를 하는 이유

  • 코드를 수동으로 재실행하면서 테스트를 하면 무언가를 놓치기 쉽다.
  • 테스팅 자동화는 테스트 코드가 실제 동작에 관여하는 코드와 별개로 작성되었을 때 가능하다. 테스트 코드를 이용하면 함수를 다양한 조건에서 실행해볼 수 있는데, 이때 실행 결과와 기대 결과를 비교할 수 있다.
  • 잘 테스트된 코드는 더 나은 아키텍처를 만든다. 테스트를 작성하려면 함수가 어떤 동작을 하는지, 입력값은 무엇이고 출력값은 무엇인지 정의하고 난 후에 구현을 시작한다. 코드는 정의된 사항을 뒷받침할 수 있게 작성해야 한다. 구현을 시작하는 순간부터 이미 좋은 아키텍처가 보장된다.

Behavior Driven Development

  • BBD는 테스트(test), 문서(documentation), 예시(example)을 한데 모아놓은 개념이다.

거듭제곱 함수와 명세서

코드를 작성하기 전에 코드가 무슨 일을 하는 지 상상한 후 이를 자연어로 표현해야 한다. 이때 만들어진 산출물을 BDD에서는 명세서(specification) 또는 스펙(spec)이라고 부른다. 명세서엔 유스 케이스에 대한 자세한 설명과 테스트가 담겨 있다.

1
2
3
4
5
describe("pow", function() {
  it("주어진 숫자의 n 제곱", function() {
    assert.equal(pow(2, 3), 8);
  });
});

스펙은 세 가지 주요 구성요소로 이루어진다.

  • describe("title", function() {...}): 구현하고자 하는 기능에 대한 설명이 들어간다. it 블록을 한 데 모아주는 역할도 한다.
  • it("유스 케이스 설명", function() {...}): 첫 번째 인수에는 특정 유스 케이스에 대한 설명이 들어간다. 이 설명은 누구나 읽을 수 있고 이해할 수 있는 자연어로 적어준다. 두 번째 인수에는 유스 케이스 테스트 함수가 들어간다.
  • assert.equal(value1, value2): 기능을 제대로 구현했다면 it 블록 내의 코드 assert.equal(value1, value2)이 에러 없이 실행된다. 함수 assert.*pow가 예상한 대로 동작하는지 확인해준다.

명세서는 실행 가능하다. 명세서를 실행하면 it 블록 안의 코드가 실행된다.

개발 순서

  1. 명세서 초안을 작성한다. 초안엔 기본적인 테스트도 들어간다.
  2. 명세서 초안을 보고 코드를 작성한다.
  3. 코드가 작동하는지 확인하기 위해 Mocha라 불리는 테스트 프레임워크를 사용해 명세서를 실행한다. 이때, 코드가 잘못 작성되었다면 에러가 출력된다. 개발자는 테스트를 모두 통과해 에러가 더는 출력되지 않을 때까지 코드를 수정한다.
  4. 모든 테스트를 통과하는 코드 초안이 완성되었다.
  5. 명세서에 지금까진 고려하지 않았던 유스케이스 몇 가지를 추가한다. 테스트가 실패하기 시작할 것이다.
  6. 세 번째 단계로 돌아가 테스트를 모두 통과할 때까지 코드를 수정한다.
  7. 기능이 완성될 때까지 3~6단계를 반복한다.

위와 같은 방법은 반복적인(iterative) 성격을 지닌다.

스펙 실행하기

  • Mocha – 핵심 테스트 프레임워크로, describe, it과 같은 테스팅 함수와 테스트 실행 관련 주요 함수를 제공한다.
  • Chai – 다양한 assertion을 제공해 주는 라이브러리이다.
  • Sinon – 함수의 정보를 캐내는 데 사용되는 라이브러리로, 내장 함수 등을 모방한다.

세 라이브러리 모두 브라우저나 서버사이드 환경을 가리지 않고 사용 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html>
<head>
  <!-- 결과 출력에 사용되는 mocha css를 불러옵니다. -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css">
  <!-- Mocha 프레임워크 코드를 불러옵니다. -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></script>
  <script>
    mocha.setup('bdd'); // 기본 셋업
  </script>
  <!-- chai를 불러옵니다 -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script>
  <script>
    // chai의 다양한 기능 중, assert를 전역에 선언합니다.
    let assert = chai.assert;
  </script>
</head>

<body>

  <script>
    function pow(x, n) {
      /* 코드를 여기에 작성합니다. 지금은 빈칸으로 남겨두었습니다. */
    }
  </script>

  <!-- 테스트(describe, it...) 있는 스크립트를 불러옵니다. -->
  <script src="test.js"></script>

  <!-- 테스트 결과를 id가 "mocha" 요소에 출력하도록 합니다.-->
  <div id="mocha"></div>

  <!-- 테스트를 실행합니다! -->
  <script>
    mocha.run();
  </script>
</body>

</html>
  1. <head> – 테스트에 필요한 서드파티 라이브러리와 스타일을 불러옴
  2. <script> – 테스트할 함수(pow)의 코드가 들어감
  3. 테스트 – describe("pow", ...)를 외부 스크립트(test.js)에서 불러옴
  4. HTML 요소 <div id="mocha"> – Mocha 실행 결과가 출력됨
  5. mocha.run() – 테스트를 실행시켜주는 명령어

코드 초안

1
2
3
function pow(x, n) {
  return 8;
}

스펙 개선하기

스펙을 개선할 때 기존 it 블록에 assert 하나 더 추가하는 방법과, 테스트 하나 더 추가하는 방법 (it 블록 하나 더 추가하기)이 있다. assert에서 에러가 발생하면 it 블록은 즉시 종료된다. 따라서 기존 it 블록에 assert를 하나 더 추가하면 첫 번째 assert가 실패했을 때 두 번째 assert의 결과를 알 수 없다. 따라서 두 번째 방법처럼 it 블록을 하나 더 추가해 테스트를 분리해서 작성하면 더 많은 정보를 얻을 수 있기 때문에 두 번째 방법이 권장된다.

또한 테스트 하나에선 한 가지만 확인하는 것이 좋다. 연관이 없는 사항 두 개를 테스트 하나에서 점검한다면 이 둘을 분리하자.

코드 개선하기

1
2
3
4
5
6
7
8
9
function pow(x, n) {
  let result = 1;

  for (let i = 0; i < n; i++) {
    result *= x;
  }

  return result;
}

함수가 제대로 동작하는지 확인하기 위해 더 많은 값을 테스트해보자. 수동으로 여러 개의 it 블록을 만드는 대신 for문을 사용해 자동으로 it 블록을 만들 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
describe("pow", function() {

  function makeTest(x) {
    let expected = x * x * x;
    it(`${x}을/를 세 번 곱하면 ${expected}입니다.`, function() {
      assert.equal(pow(x, 3), expected);
    });
  }

  for (let x = 1; x <= 5; x++) {
    makeTest(x);
  }

});

중첩 describe

중첩 describe는 새로운 테스트 ‘하위그룹(subgroup)’을 정의할 때 사용된다. 이렇게 새로 정의된 테스트 하위 그룹은 테스트 결과 보고서에 들여쓰기 된 상태로 출력된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
describe("pow", function() {

  describe("x를 세 번 곱합니다.", function() {

    function makeTest(x) {
      let expected = x * x * x;
      it(`${x}을/를 세 번 곱하면 ${expected}입니다.`, function() {
        assert.equal(pow(x, 3), expected);
      });
    }

    for (let x = 1; x <= 5; x++) {
      makeTest(x);
    }

  });

  // describe와 it을 사용해 이 아래에 더 많은 테스트를 추가할 수 있습니다.
});

before/afterbeforeEach/afterEach

함수 before은 전체 테스트가 실행되기 전에 실행되고, 함수 after는 전체 테스트가 실행된 후에 실행된다. 함수 beforeEach it이 실행되기 전에 실행되고, 함수 afterEach it이 실행된 후에 실행된다.

스펙 확장하기

자바스크립트에선 수학 관련 연산을 수행하다 에러가 발생하면 NaN을 반환한다. 함수 pown이 조건에 맞지 않으면 NaN을 반환해야 한다. 이를 검사해주는 테스트를 추가해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
describe("pow", function() {

  // ...

  it("n이 음수일 때 결과는 NaN입니다.", function() {
    assert.isNaN(pow(2, -1));
  });

  it("n이 정수가 아닐 때 결과는 NaN입니다.", function() {
    assert.isNaN(pow(2, 1.5));
  });

});

기존엔 n이 음수이거나 정수가 아닌 경우를 생각하지 않고 구현했기 때문에, 새롭게 추가한 테스트는 실패할 수밖에 없다.

BDD의 핵심은 여기에 있다. 실패할 수밖에 없는 테스트를 추가하고, 테스트를 통과할 수 있게(에러가 발생하지 않게) 코드를 개선하는 것이다.

테스트를 통과할 수 있게 다음과 같이 코드를 개선한다.

1
2
3
4
5
6
7
8
9
10
11
12
function pow(x, n) {
  if (n < 0) return NaN;
  if (Math.round(n) != n) return NaN;

  let result = 1;

  for (let i = 0; i < n; i++) {
    result *= x;
  }

  return result;
}
This post is licensed under CC BY 4.0 by the author.

스프링 입문 #6 스프링 DB 접근 기술

모던 자바스크립트 #3.6. 폴리필

Loading comments from Disqus ...