[모던자바스크립트] 21. Mocha를 이용한 테스트
in Devlog on JavaScript
이 글은 번역 및 정리 글입니다. 출처: javascript.info
Mocha를 이용한 테스트
자동화 된 테스트는 실제 프로젝트에서도 널리 사용된다.
왜 테스트가 필요한가?
함수를 작성할 때 일반적으로 어떤 기능을 수행해야하는지 떠올리며 개발한다.
하지만 주먹구구식의 개발방식에는 문제가 있을 수 있다.
수동방식으로는 놓치는게 있을 수 있다.
예를 들어 f(1)함수가 잘 작동하고 f(2)가 잘 작동하지 않는다면, f(2)를 수정해서 확인하고 넘어가는게 맞는걸까? 정답은 아니다. 우리는 f(1)을 다시 확인해야 한다. 수정사항으로 인해 기존 결과가 달라졌을 수 있기때문이다. 이런 경우를 놓치고 가는경우가 많다.
우리는 자동화된 테스트를 별도로 작성하여 테스트를 확인할 필요가 있다.
Behavior Driven Development (BDD)
BDD를 이해하기 위해 예제 개발 사례를 살펴 보자
pow의 개발: 스펙
pow(x, n) 함수를 만들고 싶다고하자. 우리는 n>=0고 가정한다.
스펙을 정의해서 Mocha를 이용해 기술해 볼 수 있다. test.js 파일을 만들고 아래 구문을 작성하자.
describe("pow", function() {
  it("raises to n-th power", function() {
    assert.equal(pow(2, 3), 8);
  });
});
describe("title", function() { ... })
우리가 구현할 함수의 기능을 설명 하는 부분이다. ìt블록을 그룹화하는데에 사용된다.
it("use case description", function() { ... })
사람이 판독가능한 제목을 적어 용도를 설명하고, 테스트 함수를 작성한다.
assert.equal(value1, value2)
예상대로 작동하였는지 직접 확인하는 구문이다.
개발 흐름
- 가장 기본적인 기능에 대한 테스트와 함께 초기 사양을 작성한다.
 - 초기 구현이 생성된다.
 - Mocha를 실행하여 오류를 확인한다. 모든 작동이 통과할때까지 수정한다.
 - 초기 구현을 완료했다.
 - 더 많은 유즈케이스를 스펙에 추가한다. 테스트가 실패하는경우가 생길 수 있다.
 - 3으로 이동하여 구현을 업데이트한다.
 - 온전히 함수가 준비될 때 까지 3-6을 반복한다.
 
기본적으로 반복적인 개발방법이다. 작은걸 완성하고 덩치를 부풀려나간다. 결국 우리는 동작하는 함수와 테스트를 모두 갖게 된다.
이 흐름을 따라 pow를 마저 개발해보자.
실제 사양
이 튜토리얼에서 사용하는 라이브러리는 아래와 같다.
- Mocha: 테스트기능을 제공하고 실행한다.
 - Chai: 다양한 
assert를 가지고 있는 라이브러리. 
Node.js를 사용할수도 있고 브라우저에서도 가능하다. 이 예제에서는 브라우저에서 진행한다.
mocha.html을 만들고 아래 구문들을 작성하자.
<!DOCTYPE html>
<html>
  <head>
    <!-- add mocha css, to show results -->
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css"
    />
    <!-- add mocha framework code -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></script>
    <script>
      mocha.setup("bdd"); // minimal setup
    </script>
    <!-- add chai -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script>
    <script>
      // chai has a lot of stuff, let's make assert global
      let assert = chai.assert;
    </script>
  </head>
  <body>
    <script>
      function pow(x, n) {
        /* function code is to be written, empty now */
      }
    </script>
    <!-- the script with tests (describe, it...) -->
    <script src="test.js"></script>
    <!-- the element with id="mocha" will contain test results -->
    <div id="mocha"></div>
    <!-- run tests! -->
    <script>
      mocha.run();
    </script>
  </body>
</html>
이 페이지는 다섯 부분으로 나눌 수 있다.
<head>- 테스트를 위해 타사 라이브러리과 스타일을 추가 할 수 있다.<script>- 테스트를 위한 함수. 우리의 경우pow- 테스트 - 우리의 경우 외부 스크립트 test.js에서 
describe("pow", ...)를 한다. - HTML 요소 
<div id="mocha">는 Mocha에서 결과를 출력하는 데 사용된다. - 테스트는 명령으로 시작된다 
mocha.run(). 

실행결과는 현재 이와같다. 테스트에 실패한것이다. 우리는 8을 기대한다고 테스트파일을 작성했지만 실제로는 undefined가 반환되어 실패하는것이다.
html코드에서 pow를 이렇게 작성해보자.
function pow(x, n) {
  return 8; // :) 사기를 쳐보자!
}

통과했다!!!
사양개선
지금 우리가 작업한 코드는 분명한 속임수이다. 실제 우리가 원하는것처럼 작동하진 않을것이다.
pow(3, 4) 일 때도 검증하도록 해보자.
두가지 방법중 하나를 택할 수 있다.
assert를 추가
describe("pow", function() {
  it("raises to n-th power", function() {
    assert.equal(pow(2, 3), 8);
    assert.equal(pow(3, 4), 81);
  });
});
테스트를 하나 더 만들기
describe("pow", function() {
  it("2 의 3 승은 8", function() {
    assert.equal(pow(2, 3), 8);
  });
  it("3 의 4 승은 81", function() {
    assert.equal(pow(3, 4), 81);
  });
});
주요 차이점은 assert 오류가 발생하면 it블록이 종료된다는 것이다. 따라서 1번 케이스에서 첫번째 assert가 실패하면 두번째는 표시되지 않는다.
테스트를 분리하는건 다양한 결과를 받아보기 좋으므로 두번째 방법이 일반적으로 더 좋다.
한 번의 테스트로 한 가지를 확인하자.
두 번째 방법으로 test.js에 작성하고 테스트해보자.

실패했다.
구현 개선
테스트가 통과할 수 있도록 좀 더 실제적인 코드를 짜보자.
function pow(x, n) {
  let result = 1;
  // 제대로
  for (let i = 0; i < n; i++) {
    result *= x;
  }
  return result;
}
함수가 제대로 작동하는지 확인하기 위해 더 많은 값을 테스트하자. it 블록을 수동으로 작성하는 대신 for문으로 작성할 수 있다.
describe("pow", function() {
  function makeTest(x) {
    let expected = x * x * x;
    it(`${x} 의 3 승은 ${expected}`, function() {
      assert.equal(pow(x, 3), expected);
    });
  }
  for (let x = 1; x <= 5; x++) {
    makeTest(x);
  }
});

중첩 기술
더 많은 테스트를 추가할건데, 그전에 현재 테스트를 그룹화 해보자.
describe("pow", function() {
  describe(" x ** 3 ", function() {
    function makeTest(x) {
      let expected = x * x * x;
      it(`${x} ** 3 = ${expected}`, function() {
        assert.equal(pow(x, 3), expected);
      });
    }
    for (let x = 1; x <= 5; x++) {
      makeTest(x);
    }
  });
  // ... 다양한 테스트들이 여기 추가 된다.
});

추가 함수들
테스트를 실행하기전/후에 실행하는 before/after, 모든 it 전/후에 실행되는 beforeEach/afterEach
describe("test", function() {
  before(() => alert("Testing started – before all tests"));
  after(() => alert("Testing finished – after all tests"));
  beforeEach(() => alert("Before a test – enter a test"));
  afterEach(() => alert("After a test – exit a test"));
  it("test 1", () => alert(1));
  it("test 2", () => alert(2));
});
이렇게 작성했다면 아래의 순서로 작동된다.
Testing started – before all tests (before)
Before a test – enter a test (beforeEach)
1
After a test – exit a test   (afterEach)
Before a test – enter a test (beforeEach)
2
After a test – exit a test   (afterEach)
Testing finished – after all tests (after)
스펙 확장
pow의 기본기능이 완료되었다. 첫번째 반복이 끝났고, 샴페인을 한잔하자.
…
다 마셨으면 이제 다시 작업을 시작해보자.
n이 양의 정수가 아니면 문제가 생길거고 그로인한 결과들을 테스트해보자.
describe("pow", function() {
  // ...
  it("마이너스 n 의 결과는 NaN", function() {
    assert.isNaN(pow(2, -1));
  });
  it("정수가 아닌 n 의 결과는 NaN", function() {
    assert.isNaN(pow(2, 1.5));
  });
});

테스트가 통과하지 못하므로 몇가지 코드를 더 작성해야한다.
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;
}
여러가지 assert
assert.isNaN은 NaN을 확인한다.
Chai 에는 다음과 같은 다른 주장이 있다 .
- assert.equal(value1, value2)– 평등을 확인 value1 == value2.
 - assert.strictEqual(value1, value2)– 엄격한 동등 점검 value1 === value2.
 - assert.notEqual, assert.notStrictEqual– 위의 것과 반대로 점검.
 - assert.isTrue(value) – value === true
 - assert.isFalse(value) – value === false
 - … 전체 목록은 문서에 있다.
 
숙제
테스트를 어떻게 고치는게 더 바람직할까?
아래 테스트를 어떻게 고치는게 더 바람직할까?
it("Raises x to the power n", function() {
  let x = 5;
  let result = x;
  assert.equal(pow(x, 1), result);
  result *= x;
  assert.equal(pow(x, 2), result);
  result *= x;
  assert.equal(pow(x, 3), result);
});
2023년 새해에는 성장하고 함께하고 싶다면?
Pre A 단계 이상의 스타트업 C 레벨들이 모여서 커뮤니티를 만들었습니다. 같이 스터디하고 친해질 일잘러를 찾습니다. 
  
 
