Test 주도 개발
Test Framework 이란..?
Unit Test 를 돕는 Tool
학습 목표
테스트 주도 개발
Overview
TDD(Test-driven Development)는 코드를 작성하기 전에 테스트를 쓰는 방법론. 개발자 자신이 바람직하다고 생각하는 코드의 결과를 미리 정의하고, 이것을 바탕으로 코드를 작성하는 법.
1/ 먼저 개발자는 몇 가지 테스트를 작성한다. - Add Tests
2/ 개발자는 그런 테스트를 실행하고 실제로 테스트할 구현된 기능이 없으므로 당연히 실패한다. - See Tests Fall
3/ 다음으로 개발자는 코드에서 이러한 테스트를 실제로 구현한다. - Write Code
4/ 개발자가 코드를 잘 작성하면 다음 단계에서 테스트가 통과하는 것을 볼수 있다. - Run Tests
5/ 개발자는 새로운 코드가 무언가를 망치면 테스트가 실패할 것이라는 것을 알고 있기 때문에 원하는대로 코드를 리펙토링하고 주석을 추가하고 정리할수 있다. - Refactor
테스트 코드를 작성하고 테스트로 검증된 코드를 가지고 실제코드를 작성하는 방식.
개발을 먼저하고 테스트를 하는 기존의 방식이 아닌 테스트 코드를 작성하고 검증된 코드를 실제 코드로 반영하자는 개념.
Pros
실제 코드보다 테스트를 먼저 작성한다는 것은, 이미 내가 바람직한 코드가 무엇이고, 어떻게 작성해야 되는지에 대한 고민이 이미 끝났다는 의미. 즉, 테스트를 먼저 작성하는 개발은 필연적으로 코드를 어떻게 구성할지 고민하고, 그 과정에서 버그가 더 적은 코드를 짜게 된다. 테스트가 쉽도록 코드의 구조를 기획(Design)하는 것도 같은 효과를 내게 된다.
Cons
TDD는 프로그래머들이 자연스럽게 생각하고 일하는 방식과 일치하지 않는다. 소프트웨어는 집을 건축하는 것과는 달리 더 유동적이다. 아마 대부분의 프로그래머는 테스트를 작성하는 것보다 바로 뭔가 만들어내고 싶어 할 것임.
같은 맥락에서 또 살펴보면, 코드를 기획한다는 것은 초반에 명확하지 않을 수 있고 대신 빠르게 프로토타입을 만들어 전반적인 아웃라인을 그리는 방법이 나을 수 있다. 이런 경우, 기획이 지속적으로 바뀌는 과정에서 초반에 테스트를 짜다가 이후에 다시 짜야 하거나 지워야 할 경우가 높기 때문에 시간 낭비라고 느껴질 수도 있다.
지금까지 살펴본 단점들을 요약한 가장 큰 단점이라고 하면 바로 속도. 장기적으로 피할 수 있는 문제들을 피할 수 있다는 점에서 반론의 여부가 있을 수 있겠지만 속도가 느리다는 생각이 기본 정설.
Popularity
위에 살펴본 단점들 때문에 완전한 TDD를 따르는 사람들은 적다. 그럼에도 자동화된 테스트를 작성하는 것은 기본이며, 어느정도 프로젝트가 완성되고 나면 테스트를 작성하게 된다. 탄탄한 프로그래밍 팀은 테스트를 포함하지 않은 코드에 대한 pull request 요청이 들어왔을 때, 정말 사소한 부분이 아니고서는 merge하지 않는다.
What is Test Framework?
Test Framework 의 구성 요소
유닛 테스트
각각의 컴포넌트들이 서로 의도대로 작동하는지 확인하기 위함.
Ex)
-입력창에 알파벳을 넣을 수 있어야 합니다. 'abc'
-버튼을 클릭할 수 있어야 합니다.
-버튼을 클릭하면 입력창의 내용이 서버로 전송되어야 합니다.
-서버로부터 대문자로 변경된 내용을 응답 받아야 합니다. 'ABC'
Assertion(주장)
유닛 테스트는 개발자가 생각하기에 "코드는 이렇게 짜야해"라는 Assertion(주장) 코드를 작성하며 시작한다. 테스트를 처음 구성할 때 console.log 를 사용하여 유닛 테스트를 하는 것도 좋으나, Assertion 을 기반으로 한 프레임워크를 잘 사용하여 빠른 테스트를 진행하는 것이 better skill.
Assertion의 예
var output = square(5);
expect(output).to.equal(25);
즉, square 함수의 인자를 5로 입력하여 리턴되는 값이 25가 되어야 한다는 Assertion(주장). 만약 내가 작성한 square 함수에 오류가 있다면, 5를 리턴해야 하는 개발자의 주장과 실제 작성한 코드의 결과가 일치하지 않았기 때문에, 테스트가 통과되지 않은 것.
Matcher
expect(output).to.equal(25);
상기 예시의 뒷 부분 to.equal을 Matcher라고 부른다. 우리가 주장하는 바와 실제 코드 실행 결과가 어때야 하는지에 대한 정보를 담고 있으며, 여기서는 "같아야" 한다 로 사용됨.
Mocha Test
Mocha 테스트는 다음 기능을 하는 도구.
describe("title", function() {...})
describe("pow", finction() { // 예시
it("use case 설명", function() {...})
it("has a prefix of 39 and a length of 14", function() { // 예시
Ex.1
it("예상 동작이 실제 동작과 일치하지 않을 때 오류가 발생합니다.", function() {
let even = function(num) {
return num%2 === 0;
};
if (even(10) !== true) {
throw new Error("10은 짝수여야 합니다!");
}
});
});
Ex.2
describe("American Express", function() {
// 항상 if/throw 구문으로 오류를 체크하는 것은 귀찮은 일이기 때문에,
// 여기에 도움을 줄 수 있는 함수를 하나 제공했습니다. 입력값이 true가 아닐 경우 에러를 발생시킵니다.
let assert = function(isTrue) {
if (!isTrue) {
throw new Error("Test failed");
}
};
it("has a prefix of 34 and a length of 15", function() {
assert(detectNetwork("343456789012345") === "American Express");
});
Chai
describe("Visa", function() {
let assert = chai.assert;
it("has a prefix of 4 and a length of 13", function() {
assert(detectNetwork("4123456789012") === "Visa");
});
});
describe("MasterCard", function() {
let expect = chai.expect;
it("has a prefix of 51 and a length of 16", function() {
expect(detectNetwork("5112345678901234")).to.equal("MasterCard");
});
describe("Discover", function() {
it("has a prefix of 6011 and a length of 16", function() {
detectNetwork("6011345678901234").should.equal("Discover");
});
});