dolog
테스트 자동화와 Mocha 본문
테스트는 왜 해야 하는가?
코드를 수동으로 재실행 하면서 테스트를 하면 무언가를 놓치기 쉽다
함수 하나를 만들어 매개변수 1 값을 넣었을 땐 잘 돌아가지만 2를 넣었을 땐 실행이 되지 않는다
그래서 2 값이 들어 갔을 때 실행이 되게끔 수정을 했을 때 이때 1 값을 넣어도 실행이 똑같이 제대로 되는지 까지 생각해봐야 한다
테스트 자동화
- 테스트 코드가 실제 동작에 관여하는 코드와 별개로 작성되었을 때 가능
- 테스트 코드를 이용하면 함수를 다양한 조건에서 실행
- 실행 결과와 기대 결과를 비교할 수 있다
Behavior Driven Development(BDD 방법론)
- 테스트(test), 문서(documentation), 예시(example)를 모아놓은 개념
거듭제곱 함수와 명세서
function pow(x, n) { // 이때 n은 자연수이자 n≥0}
자바스크립트엔 거듭제곱 연산자(**)가 존재한다 그럼에도 불구하고 직접 구현하는 이유는 1)구현 과정에 초점 2)BDD 직접 적용
작성할 코드가 무슨일을 하는지 예측 ➡️ 자연어로 표현
만들어진 코드 = 명세서(specification) or 스펙(spec)
// 명세서 초안 작성 및 기본적인 테스트
describe(“pow”, function() {
it(“주어진 숫자의 n 제곱”, function() {
assert.equal(pow(2, 3), 8); // 인수끼리 동등 비교 - 다르면 error 반환
});
});
1.describe(“title”, function() { … }
- 구현하고자 하는 기능에 대한 설명
- 예시에선 함수 pow의 동작에 대한 설명
- it 블록을 모아주는 역할
2.it(“Use case 설명”, function() { … }
- it의 첫번째 인수에는 특정 유스 케이스 설명 - 누구나 읽고 이해가능한 자연어
- 두번째 인수엔 유스 케이스 테스트 함수
3.assert.equal(value1, value2)
- 제대로 구현 ➡️ 에러 없이 실행
- assert.함수(비교 또는 확인에 쓰이는 다른 함수들이 들어온다)
개발 순서 #iterative
a. 명세서 초안 작성(+ 기본적인 테스트)
b. 명세서 초안으로 코드 작성
c. 코드 작동 확인을 위해 “Mocha”라는 테스트 프레임워크 사용해 명세서 실행
코드가 잘못 작성 ➡️ error, error가 나지 않을 때까지 수정하기
d. 모든 테스트를 통과하는 초안 완성
e. 다른 유스케이스를 추가하여 테스트를 실패하게 하기
f. 세번째 단계로 다시 돌아가 모두 통과할 때까지 수정하기
g. 기능이 완벽히 완성될 때 까지 3~6 단계 반복
스펙(spec) 실행하기 - 3개의 라이브러리
- Mocha : 핵심 테스트 프레임워크, describe, it과 같은 테스팅 함수와 테스트 실행 관련 주요 함수 제공
- Chai : 다양한 assertion(주장)을 제공하고 예시에 사용된 assert.equal 같은 함수
- Sinon : 함수의 정보를 캐내는데 사용되는 라이브러리, 내장 함수를 모방
브라우저 환경 속 HTML 페이지 ⬇️
<!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() : 테스트 실행 명령어
코드 초안 - 테스트 통과 목적
function pow(x, n) {
return 8; // 꼼수… x, n에 다른 값을 입력하면 틀린 결과를 낼 것
}
스펙 개선하기 - 테스트는 통과하지만 함수가 제 역할을 못할 경우
pow(3, 4) = 81 을 만족할 것인가?
- 기존 it 블록에 assert 하나 더 추가하기
describe(“pow”, function() {
it(“주어진 숫자의 n 제곱”, function() {
assert.equal(pow(2, 3), 8); // 인수끼리 동등 비교
assert.equal(pow(3, 4), 81);
});
});
- 테스트를 하나 더 추가하기(it 블록 하나 더 추가)
describe(“pow”, function() {
it(“2를 세번 곱하면 8입니다.”, function() {
assert.equal(pow(2, 3), 8); // 인수끼리 동등 비교
});
it(“3을 네번 곱하면 81입니다.”, function() {
assert.equal(pow(3, 4), 81);
});
}); // 두번째 assert는 실패… 항상 8을 반환… 왜??
하지만 assert에서 에러가 발생하면 it 블록은 즉시 종료(비동기 처리X)
따라서 assert를 추가했을 때 첫번째 assert가 실패 하면 두번째 assert의 결과를 알 수 없다…
그래서 두번째 테스트를 하나 더 추가하는 방법을 추천한다
추가로 테스트 하나에선 한가지만 확인하는 규칙을 따르는 것을 권장한다
코드 개선하기
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} 을/를 세번 곱하면 ${expected}입니다.`, function() {
assert.equal(pow(x, 3), expected);
});
}
for(let x =1 ; x <= 5 ; x++) {
makeTest(x);
}
});
중첩 describe
- 그룹을 만들 때 중첩으로 사용
- 새로운 테스트 하위그룹을 정의할 때 사용 ➡️ 결과 보고서에 들여쓰기 된 상태로 출력
describe("pow", function() {
describe("x를 세 번 곱합니다.", function() { // makeTest and for 문을 묶음 - makeTest는 오직 for 문에서만 사용되기 때문에
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/after and beforEach/afterEach
- before/after : 테스트 실행 전 실행/테스트 실행 후 실행
- beforEach/afterEach : 매 it이 실행 전 실행/ 매 it이 실행 후 실행
- 대게 초기화 용도로 사용(카운터 변수를 0으로, 테스트가 바뀔 때…)
describe("test", function() {
before(() => alert("테스트를 시작합니다 - 테스트가 시작되기 전"));
after(() => alert("테스트를 종료합니다 - 테스트가 종료된 후"));
beforeEach(() => alert("단일 테스트를 시작합니다 - 각 테스트 시작 전"));
afterEach(() => alert("단일 테스트를 종료합니다 - 각 테스트 종료 후"));
it('test 1', () => alert(1));
it('test 2', () => alert(2));
});
실행 순서 ⬇️
테스트를 시작합니다 - 테스트가 시작되기 전 (before)
단일 테스트를 시작합니다 - 각 테스트 시작 전 (beforeEach)
1
단일 테스트를 종료합니다 - 각 테스트 종료 후 (afterEach)
단일 테스트를 시작합니다 - 각 테스트 시작 전 (beforeEach)
2
단일 테스트를 종료합니다 - 각 테스트 종료 후 (afterEach)
테스트를 종료합니다 - 테스트가 종료된 후 (after)
스펙 확장하기 - n이 자연수이자, n≥0이 아닐 때 NaN 반환
describe("pow", function() {
// ...
it("n이 음수일 때 결과는 NaN입니다.", function() {
assert.isNaN(pow(2, -1));
});
it("n이 정수가 아닐 때 결과는 NaN입니다.", function() {
assert.isNaN(pow(2, 1.5));
});
});
NaN 검사 코드를 추가했을 때 에러가 뜨게 된다
그래서 실패할 수 밖에 없는 테스트를 추가하고 테스트를 통과할 수 있게 코드 개선
통과할 수 있도록 코드 수정 ⬇️
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;
}
다양한 assertion
- assert.isNaN : NaN인지 아닌지 확인
- 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)
- etc.
스펙의 용도
- 테스트 : 함수가 의도하는 동작을 제대로 수행하고 있는지 보장함
- 문서 : 함수가 어떤 동작을 수행하고 있는지 설명(describe와 it에 자연어 설명)
- 예시 : 실제 동작하는 예시를 이용해 함수를 어떻게 사용할 수 있는지 알려줌
• 테스팅 자동화는 결국 코드를 더 안정적으로 작성할 수 있도록 도와주는 것이다
• it과 it.only(it 블록이 여러개 있을 때 mocha가 이 블록만 실행)
• assert 보단 it 블록을 여러 개로 쪼개면 에러 파악이 쉽다
• 서버 사이드 환경, karma ??
'JavaScript > 코드 품질' 카테고리의 다른 글
폴리필(polyfill) (0) | 2022.07.13 |
---|---|
닌자 코드(= 편법 코드) (0) | 2022.07.11 |
주석(comment) (0) | 2022.07.09 |
코딩 스타일 (0) | 2022.07.09 |
Chrome으로 디버깅(debugging) 하기 (0) | 2022.07.07 |