단위테스트
단위(Unit) - 특정 조건에서 어떻게 작동해야 할지 정의한 것, 대개 “함수”로 표현
준비(arrange), 실행(act), 단언(assert) 패턴을 따른다.
테스트 주도 개발
적색, 녹색, 리팩터 순환 / 하나의 기능에 Red - Green - Refactor 사이클로 개발한다
테스트하기 쉬운 코드
관심사의 분리
- 테스트 꾸러미(Test Suite) describe - 테스트 설명, 테스트 구현 함수
- 테스트 스펙 it - 테스트 설명, 기대식을 가진 테스트 구현 함수
- 기대식과 매쳐 expect(결과 값).toBe(기대하는 값)
- 스파이 spyOn - 감시할 객체 , 감시할 메소드
예제01
describe('hello world', ()=> { // 테스트 스윗: 테스트 유닛들의 모음
it('true is true', ()=> { // 테스트 유닛: 테스트 단위
expect(true).toBe(true) // 매쳐: 검증자
})
})
예제 - 클릭 카운터
더보기
테스트 코드가 있기 때문에 안심하고 리팩토링 할 수 있다.
하나의 기능에 Red - Green - Refactor 사이클로 개발한다
var App = App || {};
//Red
App.ClickCounter = () => {
return {
getValue() {},
};
};
//Green
App.ClickCounter = () => {
return {
getValue() {
return 0;
},
};
};
//Refactor
App.ClickCounter = () => {
let value = 0;
return {
getValue() {
return value;
},
};
};
html
<!DOCTYPE html>
<html lang="ko">
<head>
<title>ClickCounter</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="Counter.js"></script>
<script src="CounterView.js"></script>
<script>
document.addEventListener('DOMContentLoaded', ()=> {
const data = { value: 0 }
const counterAsc = App.Counter(data)
const counterDesc = App.Counter(data).setCountFn(value => value - 1)
const btnAsc = document.querySelector('#btn-asc')
const btnDesc = document.querySelector('#btn-desc')
const updateEl = document.querySelector('#counter-display')
const counterViewAsc = App.CounterView(counterAsc, {
triggerEl: btnAsc, updateEl
})
const counterViewDesc = App.CounterView(counterDesc, {
triggerEl: btnDesc, updateEl
})
counterViewAsc.updateView()
})
</script>
</head>
<body>
<button id="btn-desc">-</button>
<span id="counter-display"></span>
<button id="btn-asc">+</button>
</body>
</html>
index.spec.html
<body>
<script src="Counter.js"></script>
<script src="Counter.spec.js"></script>
<script src="CounterView.js"></script>
<script src="CounterView.spec.js"></script>
</body>
Counter.js
const Counter = () => {
let value = 0;
const counter = {
count() {
value++;
},
getValue() {
return value;
},
};
return counter;
};
Counter.spec.js
describe("ClickCounter", () => {
describe("getCounter()", () => {
it("초기값이 0인 카운터 값을 반환한다", () => {
const counter = ClickCounter();
expect(counter.getCounter()).toBe(0);
});
});
describe("increase()", () => {
it("카운터를 1 올린다", () => {
//준비
const counter = ClickCounter();
//실행
counter.increase();
//단언
expect(counter.getCounter()).toBe(1);
});
});
});
//after
describe("ClickCounter", () => {
let counter;
beforeEach(() => {
counter = ClickCounter();
});
describe("getCounter()", () => {
it("초기값이 0인 카운터 값을 반환한다", () => {
expect(counter.getCounter()).toBe(0);
});
});
describe("increase()", () => {
it("카운터를 1 올린다", () => {
const initialValue = counter.getCounter();
counter.increase();
expect(counter.getCounter()).toBe(initialValue + 1);
});
});
});
beforeEach는 it 함수 호출 직전에 실행 되는 자스민 함수다. 중복코드는 beforeEach로 옮기자
CounterView.js
const CounterView = (counter, options) => {
const view = {
countAndUpdateView() {
counter.count();
view.updateView();
},
updateView() {
options.updateEl.innerHTML = counter.getValue();
},
};
options.triggerEl.addEventListener("click", () => {
view.countAndUpdateView();
});
return view;
};
CounterView.spec.js
모듈 패턴
함수로 데이터를 감추고, 모듈 API를 담고 있는 객체를 반환하는 형태
- 임의 함수를 호출하여 생성하는 모듈과
- 즉시 실행 함수(IIFE) 기반의 모듈이 있다.
모듈 생성 원칙
- 단일 책임 원칙에 따라 모듈은 한가지 열할만 한다.
- 모듈 자신이 사용할 객체가 있다면 의존성 주입 형태로 제공한다.
테스트 더블
단위 테스트 패턴으로 테스트하기 곤란한 컴포넌트를 대체하여 테스트하는 것
특정한 동작을 흉내만 냄
더미 - 인자 채우기
스텁 - 리턴 값이 존재
스파이 - 기록을 남긴다,.
페이크 - 운영에서 사용은 못함
목 - 더미 스텁 스파이 혼합
정리
- 컴파일러가 없는한 테스트가 최선
- 테스트 주도 개발(TDD)
- SOLID하고 DRY한 코드