자동화된 테스트는 추가 작업에 사용될 것이며 실제 프로젝트에서도 널리 사용됩니다.
함수를 작성할 때 일반적으로 함수가 수행해야 하는 작업, 즉 어떤 매개변수가 어떤 결과를 제공하는지 상상할 수 있습니다.
개발 중에 함수를 실행하고 결과를 예상한 결과와 비교하여 함수를 확인할 수 있습니다. 예를 들어 콘솔에서 할 수 있습니다.
뭔가 잘못되면 코드를 수정하고, 다시 실행하고, 결과를 확인하는 등 작동할 때까지 계속합니다.
그러나 이러한 수동 "재실행"은 불완전합니다.
수동으로 다시 실행하여 코드를 테스트할 때 뭔가를 놓치기 쉽습니다.
예를 들어, 함수 f
생성하고 있습니다. 몇 가지 코드를 작성하여 테스트했습니다. f(1)
작동하지만 f(2)
작동하지 않습니다. 코드를 수정하면 이제 f(2)
작동합니다. 완전해 보이죠? 하지만 f(1)
다시 테스트하는 것을 잊었습니다. 이로 인해 오류가 발생할 수 있습니다.
그것은 매우 전형적입니다. 우리는 무언가를 개발할 때 가능한 많은 사용 사례를 염두에 둡니다. 그러나 프로그래머가 모든 변경 후에 수동으로 모든 항목을 확인하기를 기대하기는 어렵습니다. 따라서 한 가지를 고치고 다른 것을 깨뜨리는 것이 쉬워집니다.
자동화된 테스트는 코드 외에 테스트가 별도로 작성된다는 의미입니다. 그들은 다양한 방식으로 우리의 기능을 실행하고 결과를 예상과 비교합니다.
행동 중심 개발(Behavior Driven Development), 줄여서 BDD라는 기술부터 시작해 보겠습니다.
BDD는 테스트, 문서화, 예제라는 세 가지 기능을 하나로 통합한 것입니다.
BDD를 이해하기 위해 실제 개발 사례를 살펴보겠습니다.
x
정수 n
제곱으로 올리는 함수 pow(x, n)
만들고 싶다고 가정해 보겠습니다. 우리는 n≥0
이라고 가정합니다.
해당 작업은 단지 예일 뿐입니다. JavaScript에는 이를 수행할 수 있는 **
연산자가 있지만 여기서는 더 복잡한 작업에도 적용할 수 있는 개발 흐름에 집중합니다.
pow
코드를 작성하기 전에 함수가 수행해야 하는 작업을 상상하고 설명할 수 있습니다.
이러한 설명을 사양 또는 줄여서 사양이라고 하며 다음과 같이 사용 사례에 대한 설명과 테스트를 포함합니다.
explain("pow", function() { it("n제곱으로 올립니다.", function() { 주장.equal(pow(2, 3), 8); }); });
사양에는 위에서 볼 수 있는 세 가지 주요 구성 요소가 있습니다.
describe("title", function() { ... })
우리가 설명하는 기능은 무엇입니까? 우리의 경우에는 pow
함수를 설명합니다. "작업자"를 it
하는 데 사용됩니다.
it("use case description", function() { ... })
it
에서 우리는 사람이 읽을 수 있는 방식으로 특정 사용 사례를 설명하고 두 번째 인수는 이를 테스트하는 함수입니다.
assert.equal(value1, value2)
구현이 올바른 경우 it
내부의 코드는 오류 없이 실행되어야 합니다.
assert.*
함수는 pow
예상대로 작동하는지 확인하는 데 사용됩니다. 바로 여기에서 우리는 그 중 하나( assert.equal
)를 사용하고 있습니다. 이는 인수를 비교하고 동일하지 않으면 오류를 생성합니다. 여기서는 pow(2, 3)
의 결과가 8
과 같은지 확인합니다. 나중에 추가할 다른 유형의 비교 및 확인도 있습니다.
사양을 실행할 수 있으며 it
블록에 지정된 테스트를 실행합니다. 나중에 살펴보겠습니다.
개발 흐름은 일반적으로 다음과 같습니다.
가장 기본적인 기능에 대한 테스트를 포함하여 초기 사양이 작성되었습니다.
초기 구현이 생성됩니다.
작동하는지 확인하기 위해 사양을 실행하는 테스트 프레임워크 Mocha(자세한 내용은 곧 제공)를 실행합니다. 기능이 완전하지 않은 동안 오류가 표시됩니다. 모든 것이 작동할 때까지 수정합니다.
이제 테스트를 통해 작동하는 초기 구현이 완료되었습니다.
우리는 사양에 더 많은 사용 사례를 추가합니다. 아마도 아직 구현에서 지원되지 않을 수도 있습니다. 테스트가 실패하기 시작합니다.
3으로 이동하여 테스트에서 오류가 발생하지 않을 때까지 구현을 업데이트합니다.
기능이 준비될 때까지 3~6단계를 반복합니다.
그래서 개발은 반복적 입니다. 우리는 사양을 작성하고 구현하고 테스트가 통과하는지 확인한 다음 더 많은 테스트를 작성하고 작동하는지 확인합니다. 마지막에는 작동하는 구현과 테스트가 모두 완료됩니다.
실제 사례에서 이러한 개발 흐름을 살펴보겠습니다.
첫 번째 단계는 이미 완료되었습니다. pow
에 대한 초기 사양이 있습니다. 이제 구현을 하기 전에 몇 가지 JavaScript 라이브러리를 사용하여 테스트를 실행하고 작동하는지 확인하겠습니다(모두 실패합니다).
여기 튜토리얼에서는 테스트를 위해 다음 JavaScript 라이브러리를 사용합니다.
Mocha – 핵심 프레임워크: describe
및 it
를 실행하는 기본 기능을 포함한 일반적인 테스트 기능을 제공합니다.
Chai – 많은 주장이 있는 라이브러리. 다양한 단언문을 사용할 수 있으므로 현재는 단지 assert.equal
만 필요합니다.
Sinon – 함수를 감시하고 내장 함수 등을 에뮬레이트하는 라이브러리입니다. 나중에 훨씬 더 필요할 것입니다.
이러한 라이브러리는 브라우저 내 테스트와 서버 측 테스트 모두에 적합합니다. 여기서는 브라우저 변형을 고려해 보겠습니다.
다음 프레임워크와 pow
사양이 포함된 전체 HTML 페이지:
<!DOCTYPE HTML> <html> <머리> <!-- 결과를 표시하려면 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> <스크립트> mocha.setup('bdd'); // 최소한의 설정 </script> <!-- 차이 추가 --> <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script> <스크립트> // chai에는 많은 것들이 있습니다. Assert를 전역으로 만들어 보겠습니다. 주장하자 = chai.assert; </script> </head> <본문> <스크립트> 함수 pow(x, n) { /* 함수 코드가 작성됩니다. 지금은 비어 있습니다 */ } </script> <!-- 테스트가 포함된 스크립트(설명, 설명...) --> <script src="test.js"></script> <!-- id="mocha"인 요소에는 테스트 결과가 포함됩니다 --> <div id="mocha"></div> <!-- 테스트를 실행하세요! --> <스크립트> 모카.run(); </script> </body> </html>
페이지는 다섯 부분으로 나눌 수 있습니다:
<head>
– 테스트를 위한 타사 라이브러리 및 스타일을 추가합니다.
우리의 경우에는 테스트할 함수가 있는 <script>
– pow
에 대한 코드가 포함되어 있습니다.
테스트 - 우리의 경우 위에서 describe("pow", ...)
이 있는 외부 스크립트 test.js
입니다.
HTML 요소 <div id="mocha">
는 Mocha에서 결과를 출력하는 데 사용됩니다.
테스트는 mocha.run()
명령으로 시작됩니다.
결과:
현재로서는 테스트가 실패하고 오류가 발생했습니다. 그것은 논리적입니다: pow
에 빈 함수 코드가 있으므로 pow(2,3)
8
대신 undefined
반환합니다.
앞으로는 다양한 테스트를 쉽게 자동 실행할 수 있게 해주는 카르마(karma) 및 기타 기능과 같은 더 높은 수준의 테스트 실행기가 있다는 점에 유의하세요.
테스트를 통과하기 위해 pow
를 간단하게 구현해 보겠습니다.
함수 pow(x, n) { 8을 반환합니다. // :) 우리는 속인다! }
와, 이제 작동합니다!
우리가 한 일은 확실히 속임수입니다. 함수가 작동하지 않습니다. pow(3,4)
계산하려고 시도하면 잘못된 결과가 나오지만 테스트는 통과합니다.
…그러나 상황은 매우 일반적이며 실제로 발생합니다. 테스트는 통과했지만 기능이 잘못 작동합니다. 우리의 사양은 불완전합니다. 더 많은 사용 사례를 추가해야 합니다.
pow(3, 4) = 81
인지 확인하기 위해 테스트를 하나 더 추가해 보겠습니다.
여기에서 테스트를 구성하는 두 가지 방법 중 하나를 선택할 수 있습니다.
첫 번째 변형 - 동일한 it
에 하나 이상의 assert
추가합니다.
explain("pow", function() { it("n제곱으로 올립니다.", function() { 주장.equal(pow(2, 3), 8); 주장.equal(pow(3, 4), 81); }); });
두 번째 - 두 가지 테스트를 수행합니다.
explain("pow", function() { it("2의 3제곱은 8입니다.", function() { 주장.equal(pow(2, 3), 8); }); it("3의 4제곱은 81입니다.", function() { 주장.equal(pow(3, 4), 81); }); });
주요 차이점은 assert
오류를 트리거하면 it
블록이 즉시 종료된다는 것입니다. 따라서 첫 번째 변형에서는 첫 번째 assert
실패하면 두 번째 assert
의 결과를 볼 수 없습니다.
테스트를 별도로 만드는 것은 무슨 일이 일어나고 있는지에 대한 더 많은 정보를 얻는 데 유용하므로 두 번째 변형이 더 좋습니다.
그 외에도 따라야 할 규칙이 하나 더 있습니다.
한 번의 테스트로 한 가지를 확인합니다.
테스트를 보고 두 개의 독립적인 검사가 있으면 이를 더 간단한 두 개의 검사로 분할하는 것이 좋습니다.
그럼 두 번째 변형을 계속해 보겠습니다.
결과:
예상했던 대로 두 번째 테스트는 실패했습니다. 물론, 우리 함수는 항상 8
반환하지만, assert
는 81
기대합니다.
테스트를 통과하기 위해 좀 더 실제적인 내용을 작성해 보겠습니다.
함수 pow(x, n) { 결과 = 1로 둡니다. for (let i = 0; i < n; i++) { 결과 *= x; } 결과 반환; }
함수가 제대로 작동하는지 확인하기 위해 더 많은 값을 테스트해 보겠습니다. 블록 it
수동으로 작성하는 대신 다음 for
생성할 수 있습니다.
explain("pow", function() { 함수 makeTest(x) { 예상 = x * x * x; it(`${x}의 3승은 ${예상}입니다`, function() { Assert.equal(pow(x, 3), 예상); }); } for (x = 1; x <= 5; x++) { makeTest(x); } });
결과:
우리는 더 많은 테스트를 추가할 예정입니다. 하지만 그 전에 도우미 함수 makeTest
와 for
함께 그룹화되어야 한다는 점을 알아두세요. 다른 테스트에서는 makeTest
필요하지 않으며 for
:에서만 필요합니다. 일반적인 작업은 pow
주어진 거듭제곱으로 어떻게 증가하는지 확인하는 것입니다.
그룹화는 중첩된 describe
으로 수행됩니다.
explain("pow", function() { explain("x를 3제곱합니다", function() { 함수 makeTest(x) { 예상 = x * x * x; it(`${x}의 3승은 ${예상}입니다`, function() { Assert.equal(pow(x, 3), 예상); }); } for (x = 1; x <= 5; x++) { makeTest(x); } }); // ... 여기에 따라야 할 더 많은 테스트가 설명되어 있으며 추가될 수 있습니다. });
중첩된 describe
테스트의 새로운 "하위 그룹"을 정의합니다. 출력에서 제목이 붙은 들여쓰기를 볼 수 있습니다.
앞으로는 더 많은 it
추가하고 자체 도우미 기능을 사용하여 최상위 수준에서 describe
할 수 있지만 makeTest
표시되지 않습니다.
before/after
및 beforeEach/afterEach
테스트를 실행하기 전/후에 실행되는 before/after
함수와 it
전 /후에 실행되는 beforeEach/afterEach
함수를 설정할 수 있습니다.
예를 들어:
explain("테스트", function() { before(() => Alert("테스트 시작 – 모든 테스트 전")); after(() => Alert("테스트 완료 – 모든 테스트가 끝난 후")); beforeEach(() => Alert("테스트 전 – 테스트 입력")); afterEach(() => Alert("테스트 후 – 테스트 종료")); it('테스트 1', () => 경고(1)); it('테스트 2', () => 경고(2)); });
실행 순서는 다음과 같습니다.
테스트 시작 – 모든 테스트 전(이전) 테스트 전 – 테스트 입력(beforeEach) 1 테스트 후 – 테스트 종료(afterEach) 테스트 전 – 테스트 입력(beforeEach) 2 테스트 후 – 테스트 종료(afterEach) 테스트 완료 – 모든 테스트 후(후)
샌드박스에서 예제를 엽니다.
일반적으로 beforeEach/afterEach
및 before/after
초기화를 수행하고, 카운터를 0으로 설정하거나 테스트(또는 테스트 그룹) 간에 다른 작업을 수행하는 데 사용됩니다.
pow
의 기본 기능이 완성되었습니다. 개발의 첫 번째 반복이 완료되었습니다. 축하하고 샴페인을 마셨다면 계속해서 개선해 나가겠습니다.
말했듯이, 함수 pow(x, n)
양의 정수 값 n
과 함께 작동하도록 되어 있습니다.
수학적 오류를 표시하기 위해 JavaScript 함수는 일반적으로 NaN
반환합니다. n
의 유효하지 않은 값에 대해서도 동일한 작업을 수행해 보겠습니다.
먼저 사양(!)에 동작을 추가해 보겠습니다.
explain("pow", function() { // ... it("음수 n의 경우 결과는 NaN입니다.", function() { 주장.isNaN(pow(2, -1)); }); it("정수가 아닌 n의 경우 결과는 NaN입니다.", function() { 주장.isNaN(pow(2, 1.5)); }); });
새로운 테스트 결과:
새로 추가된 테스트는 실패합니다. 우리 구현이 이를 지원하지 않기 때문입니다. 이것이 BDD가 수행되는 방식입니다. 먼저 실패한 테스트를 작성한 다음 이를 구현합니다.
기타 주장
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
인지 확인합니다.
…전체 목록은 문서에 있습니다.
따라서 pow
에 몇 줄을 추가해야 합니다.
함수 pow(x, n) { (n < 0)인 경우 NaN을 반환합니다. if (Math.round(n) != n)은 NaN을 반환합니다. 결과 = 1로 둡니다. for (let i = 0; i < n; i++) { 결과 *= x; } 결과 반환; }
이제 작동하고 모든 테스트가 통과되었습니다.
샌드박스에서 전체 최종 예제를 엽니다.
BDD에서는 사양이 먼저 나온 다음 구현이 이어집니다. 마지막에는 사양과 코드가 모두 있습니다.
사양은 세 가지 방법으로 사용될 수 있습니다.
테스트 로서 – 코드가 올바르게 작동하는지 보장합니다.
문서 로 – describe
제목은 기능이 수행하는 작업을 알려 it
.
예제 – 테스트는 실제로 함수가 어떻게 사용될 수 있는지 보여주는 작업 예제입니다.
사양을 사용하면 함수를 처음부터 안전하게 개선하고 변경하고 다시 작성하여 여전히 제대로 작동하는지 확인할 수 있습니다.
이는 기능이 여러 곳에서 사용되는 대규모 프로젝트에서 특히 중요합니다. 이러한 기능을 변경할 때 해당 기능을 사용하는 모든 장소가 여전히 제대로 작동하는지 수동으로 확인할 방법이 없습니다.
테스트가 없으면 사람들은 두 가지 방법을 사용할 수 있습니다.
무슨 일이 있어도 변경을 수행합니다. 그리고 우리는 수동으로 무언가를 확인하지 못했기 때문에 사용자는 버그를 만나게 됩니다.
또는 테스트가 없기 때문에 오류에 대한 처벌이 가혹하면 사람들은 그러한 기능을 수정하는 것을 두려워하게 되고 코드가 구식이 되어 아무도 코드에 들어가고 싶어하지 않습니다. 개발에 좋지 않습니다.
자동 테스트는 이러한 문제를 방지하는 데 도움이 됩니다!
프로젝트가 테스트로 덮여 있다면 그런 문제는 없습니다. 변경 후에는 몇 초 만에 테스트를 실행하고 많은 확인 사항을 확인할 수 있습니다.
게다가 잘 테스트된 코드는 더 나은 아키텍처를 갖습니다.
당연히 자동 테스트된 코드는 수정하고 개선하기가 더 쉽기 때문입니다. 하지만 또 다른 이유도 있습니다.
테스트를 작성하려면 모든 함수에 명확하게 설명된 작업, 잘 정의된 입력 및 출력이 포함되도록 코드를 구성해야 합니다. 이는 처음부터 좋은 아키텍처를 의미합니다.
현실에서는 때로는 그렇게 쉽지 않습니다. 때로는 실제 코드가 어떻게 동작해야 하는지 명확하지 않기 때문에 실제 코드 전에 사양을 작성하는 것이 어렵습니다. 그러나 일반적으로 테스트 작성은 개발을 더 빠르고 안정적으로 만듭니다.
튜토리얼 후반부에서는 내장된 테스트를 통해 많은 작업을 수행하게 됩니다. 그럼 좀 더 실용적인 예시를 보시죠.
테스트를 작성하려면 좋은 JavaScript 지식이 필요합니다. 하지만 우리는 이제 막 배우기 시작했습니다. 따라서 모든 것을 정리하기 위해 지금은 테스트를 작성할 필요가 없지만 이 장에서보다 조금 더 복잡하더라도 이미 읽을 수 있을 것입니다.
중요도: 5
아래 pow
테스트에서 잘못된 점은 무엇인가요?
it("x를 n제곱합니다.", function() { x = 5라고 하자; 결과 = x로 두십시오; 주장.equal(pow(x, 1), 결과); 결과 *= x; 주장.equal(pow(x, 2), 결과); 결과 *= x; 주장.equal(pow(x, 3), 결과); });
PS 구문론적으로 테스트는 정확하며 통과되었습니다.
이 테스트는 개발자가 테스트를 작성할 때 만나는 유혹 중 하나를 보여줍니다.
여기에 있는 것은 실제로 3개의 테스트이지만 3개의 어설션이 있는 단일 함수로 구성되어 있습니다.
때로는 이런 식으로 작성하는 것이 더 쉽지만 오류가 발생하면 무엇이 잘못되었는지 훨씬 덜 명확해집니다.
복잡한 실행 흐름 중에 오류가 발생하면 그 시점의 데이터를 파악해야 합니다. 실제로 테스트를 디버깅 해야 합니다.
명확하게 작성된 입력 및 출력을 사용하여 테스트를 여러 it
블록으로 나누는 것이 훨씬 더 좋습니다.
이와 같이:
explain("x를 n만큼 거듭제곱합니다", function() { it("5의 1제곱은 5와 같습니다.", function() { 주장.equal(pow(5, 1), 5); }); it("5의 2제곱은 25와 같습니다.", function() { 주장.equal(pow(5, 2), 25); }); it("5의 3제곱은 125와 같습니다.", function() { 주장.equal(pow(5, 3), 125); }); });
우리는 단일 it
describe
과 it
블록 그룹으로 대체했습니다. 이제 뭔가 실패하면 데이터가 무엇인지 명확하게 알 수 있습니다.
또한 it
대신 it.only
를 작성하여 단일 테스트를 분리하고 독립형 모드에서 실행할 수 있습니다.
explain("x를 n만큼 거듭제곱합니다", function() { it("5의 1제곱은 5와 같습니다.", function() { 주장.equal(pow(5, 1), 5); }); // Mocha는 이 블록만 실행합니다. it.only("5의 2제곱은 25와 같습니다.", function() { 주장.equal(pow(5, 2), 25); }); it("5의 3제곱은 125와 같습니다.", function() { 주장.equal(pow(5, 3), 125); }); });