# 대체 자바스크립트 테스트가 뭘까요 > **원문:** [**Kent C. Dodds**](https://twitter.com/kentcdodds)**, "**[**But really, what is a JavaScript test?**](https://kentcdodds.com/blog/but-really-what-is-a-javascript-test)**"** 소프트웨어를 테스트하는 이유? 너무 많은데요. 그중에서도 제가 생각하는 두 가지 이유는 아래와 같습니다. 1. 테스트는 작업 속도를 높여 소프트웨어를 더 빠르게 개발하고 2. 변경 사항을 적용할 때 기존 코드를 망가뜨리지 않는다는 것을 보장합니다. 그렇다면 여기서 몇 가지 질문을 던져 볼게요 (이 질문들은 트위터 투표입니다). * [**자바스크립트 테스트를 작성한 경험이 있나요?**](https://twitter.com/kentcdodds/status/942625485829181441) * [**자바스크립트 테스팅 프레임워크를 사용한 경험이 있나요?**](https://twitter.com/kentcdodds/status/942625486638759938) * [**자바스크립트 테스팅 프레임워크를 밑바닥부터 설정한 경험이 있나요?**](https://twitter.com/kentcdodds/status/942625487511154688) * [**테스팅 프레임워크를 충분히 이해하여 직접 구현이 가능한 수준인가요? (매우 간단한 것이라도?)**](https://twitter.com/kentcdodds/status/942625489348280320) 이 글의 목표는 여러분 모두가 마지막 질문에 "네"라고 대답할 수 있도록 만드는 것입니다. 따라서 자바스크립트에서 테스트가 실제로 무엇인지에 관하여 여러분이 기본적인 이해를 가지고 더 나은 테스트를 작성하도록 도울 것입니다. 이제 간단한 `math.js` 모듈이 내보내는 두 함수에 관하여 테스트를 작성해 보겠습니다. ```javascript const sum = (a, b) => a + b const subtract = (a, b) => a - b module.exports = {sum, subtract} ``` **참고할 수 있는** [**깃허브 리포**](https://github.com/kentcdodds/js-test-example)**도 만들어 두었습니다** 🐙😸 ## **1단계** 아래는 제가 생각하는 가장 기초적인 형태의 테스트입니다. ```javascript // basic-test.js const actual = true const expected = false if (actual !== expected) { throw new Error(`${actual} is not ${expected}`) } ``` 테스트 코드를 실행하려면 `node basic-test.js`를 실행하세요! 이게 테스트입니다! 🎉 **테스트는 무언가의 실제 결과가 예상 결과와 일치하지 않을 때 오류를 던지는 코드입니다.** (브라우저 이벤트를 발생시키기 전 컴포넌트가 문서에 렌더링되어야 하거나 데이터베이스에 사용자가 존재해야 하는 등의) 먼저 설정되어야 하는 일부 상태에 의존하는 코드를 다루는 경우 테스트는 더 복잡해질 수 있습니다. 하지만 `math.js` 모듈에 있는 함수처럼 "순수 함수" (주어진 입력에 항상 동일한 결과를 반환하고 주변 환경의 상태를 변경하지 않는 함수)는 테스트하기에 비교적 용이합니다. `actual !== expected`**라고 나온 부분은 "어서션(assertion)"이라고 부릅니다.** 어서션은 코드에서 어떤 것이 반드시 특정 값이어야 하거나 음... 특정 테스트를 통과해야 한다는 방식입니다. `actual`이 정규표현식과 일치하거나, 특정 길이의 배열이거나, 여러 다양한 경우도 어서션이 될 수 있어요. 핵심은 어서션이 실패하면 오류를 던진다는 것입니다. `math.js` 함수를 위한 가장 기본적인 테스트는 아래와 같습니다. ```javascript // 1.js const {sum, subtract} = require('./math') let result, expected result = sum(3, 7) expected = 10 if (result !== expected) { throw new Error(`${result} is not equal to ${expected}`) } result = subtract(7, 3) expected = 4 if (result !== expected) { throw new Error(`${result} is not equal to ${expected}`) } ``` 잘하셨어요! 테스트를 `node`로 실행하면 오류 없이 명령어가 종료됩니다. 이제 `+`를 `-`로 변경하여 `sum` 함수를 망가뜨리고 다시 실행해 보겠습니다. ```plaintext $ node 1.js /Users/kdodds/Desktop/js-test-example/1.js:8 throw new Error(`${result} is not equal to ${expected}`) ^ Error: -4 is not equal to 10 at Object. (/Users/kdodds/Desktop/js-test-example/1.js:8:9) at Module._compile (module.js:635:30) at Object.Module._extensions..js (module.js:646:10) at Module.load (module.js:554:32) at tryModuleLoad (module.js:497:12) at Function.Module._load (module.js:489:3) at Function.Module.runMain (module.js:676:10) at startup (bootstrap_node.js:187:16) at bootstrap_node.js:608:3 ``` 멋집니다! 우리는 이미 기본 테스트로 이득을 보고 있어요! 자동화된 테스트를 망가뜨리지 않고는 `sum` 함수를 망가뜨릴 수 없어요! 성공입니다! **테스팅 프레임워크 (또는 어서션 라이브러리)에서 가장 중요한 부분 하나는 오류 메시지가 얼마나 유용한지입니다.** 테스트가 실패할 때 우리는 보통 먼저 오류 메시지를 봅니다. 오류 메시지에서 근본적인 문제가 무엇인지를 알 수 없다면 코드를 몇 분 동안 살펴보아야만 잘못된 이유를 이해할 수 있습니다. 사용 중인 프레임워크에서 제공하는 어서션을 얼마나 잘 이해하고 사용하는지에 따라 오류메시지의 질이 많이 달라집니다. ## **2단계** 위에서 우리가 했던 것처럼 어서션을 만들기 위한 [`assert` **모듈**](https://nodejs.org/api/assert.html#assert_assert)이 실제로 노드에 존재한다는 것을 알고 있나요 🤔? 노드에서 제공하는 모듈을 사용하도록 테스트를 리팩터링해 봅시다! ```javascript // 2.js const assert = require('assert') const {sum, subtract} = require('./math') let result, expected result = sum(3, 7) expected = 10 assert.strictEqual(result, expected) result = subtract(7, 3) expected = 4 assert.strictEqual(result, expected) ``` 좋아요! 여전히 테스트 모듈이네요. 우리가 이전에 했던 것과 기능적으로 동일합니다. 유일한 차이는 오류 메시지예요. ```plaintext $ node 2.js assert.js:42 throw new errors.AssertionError({ ^ AssertionError [ERR_ASSERTION]: -4 === 10 at Object. (/Users/kdodds/Desktop/js-test-example/2.js:8:8) at Module._compile (module.js:635:30) at Object.Module._extensions..js (module.js:646:10) at Module.load (module.js:554:32) at tryModuleLoad (module.js:497:12) at Function.Module._load (module.js:489:3) at Function.Module.runMain (module.js:676:10) at startup (bootstrap_node.js:187:16) at bootstrap_node.js:608:3 ``` 우리가 던진 에러가 더 이상 코드의 어떤 내용도 포함하지 않는다는 것을 알 수 있네요. 유감이지만... 😦 계속 진행해 보겠습니다. ## **3단계** 자, 이제 우리가 직접 간단한 테스팅 "프레임워크"와 어서션 라이브러리를 작성합시다. 어서션 라이브러리부터 시작할게요. 노드에 내장된 `assert` 모듈 대신에 `expect`라는 이름의 라이브러리를 만들어 볼 거예요. 아래는 변경 사항에 맞게 리팩터링된 테스트입니다. ```javascript // 3.js const {sum, subtract} = require('./math') let result, expected result = sum(3, 7) expected = 10 expect(result).toBe(expected) result = subtract(7, 3) expected = 4 expect(result).toBe(expected) function expect(actual) { return { toBe(expected) { if (actual !== expected) { throw new Error(`${actual} is not equal to ${expected}`) } }, } } ``` 멋지네요. 이제 우리는 반환된 객체에 (`toMatchRegex`나 `toHaveLength` 같은) 다수의 어서션을 추가할 수 있습니다. 오! 이제 다시 오류 메시지가 나오네요. ```plaintext $ node 3.js /Users/kdodds/Desktop/js-test-example/3.js:17 throw new Error(`${actual} is not equal to ${expected}`) ^ Error: -4 is not equal to 10 at Object.toBe (/Users/kdodds/Desktop/js-test-example/3.js:17:15) at Object. (/Users/kdodds/Desktop/js-test-example/3.js:7:16) at Module._compile (module.js:635:30) at Object.Module._extensions..js (module.js:646:10) at Module.load (module.js:554:32) at tryModuleLoad (module.js:497:12) at Function.Module._load (module.js:489:3) at Function.Module.runMain (module.js:676:10) at startup (bootstrap_node.js:187:16) at bootstrap_node.js:608:3 ``` 그래요. 상황이 좋아 보입니다. ## **4단계** 그런데 말이죠. 여기도 문제가 있어요 😖... 어떻게 이 오류 메시지로 `sum` 함수가 망가졌다는 것을 알 수 있죠? `subtract` 모듈일 수도 있잖아요. 게다가 테스트 소스가 테스트를 (시각적으로나 다른 방면으로) 잘 분리하지 못하는군요. 분리가 잘 이루어지도록 헬퍼 함수를 작성해 봅시다. ```javascript // 4.js const {sum, subtract} = require('./math') test('sum adds numbers', () => { const result = sum(3, 7) const expected = 10 expect(result).toBe(expected) }) test('subtract subtracts numbers', () => { const result = subtract(7, 3) const expected = 4 expect(result).toBe(expected) }) function test(title, callback) { try { callback() console.log(`✓ ${title}`) } catch (error) { console.error(`✕ ${title}`) console.error(error) } } function expect(actual) { return { toBe(expected) { if (actual !== expected) { throw new Error(`${actual} is not equal to ${expected}`) } }, } } ``` 이제 주어진 테스트와 관련된 모든 내용을 "test" 콜백 함수 안에 넣고 해당 테스트에 이름을 붙일 수 있습니다. 그런 다음 `test` 함수를 사용하여 더 유용한 오류 메시지를 제공할 뿐만 아니라 (첫 번째 오류가 발생해도 중단되지 않고) 파일의 모든 테스트를 실행할 수 있습니다! 이제 결과는 아래와 같아요. ```plaintext $ node 4.js ✕ sum adds numbers Error: -4 is not equal to 10 at Object.toBe (/Users/kdodds/Desktop/js-test-example/4.js:29:15) at test (/Users/kdodds/Desktop/js-test-example/4.js:6:18) at test (/Users/kdodds/Desktop/js-test-example/4.js:17:5) at Object. (/Users/kdodds/Desktop/js-test-example/4.js:3:1) at Module._compile (module.js:635:30) at Object.Module._extensions..js (module.js:646:10) at Module.load (module.js:554:32) at tryModuleLoad (module.js:497:12) at Function.Module._load (module.js:489:3) at Function.Module.runMain (module.js:676:10) ✓ subtract subtracts numbers ``` 좋습니다! 이제 오류 메시지 자체와 테스트 제목을 볼 수 있어서 어떤 것을 고칠지 알 수 있겠네요. ## **5단계** 이제 남은 할 일은 모든 테스트 파일을 찾아서 실행할 [**CLI 도구 작성**](https://kentcdodds.com/blog/tips-for-making-a-cli-based-tool-with-node)뿐입니다! 처음에는 상당히 간단하지만 이 위에 추가할 수 있는 것이 엄~청 많답니다. 😅 여기서 우리는 테스팅 프레임워크와 테스트 러너를 구축하고 있는데요. 다행히도 이미 구축된 다수의 테스팅 도구들이 있습니다! 수많은 도구들을 사용해 보았고 모두 훌륭했습니다. 그럼에도 [**제스트(Jest)**](https://facebook.github.io/jest) 🃏만큼 제 사용 사례와 가장 부합하는 도구는 찾지 못했습니다. 제스트는 놀라운 도구입니다 ([**제스트를 더 알아보세요**](http://kcd.im/egghead-jest)). 따라서 자체 프레임워크를 구축하는 대신에 테스트 파일이 제스트와 함께 작동하도록 변경하려고 하는데... 우연히도 이미 그렇게 설정되어 있네요! 우리는 `test`와 `expect`의 자체 구현만 제거해 주면 됩니다. 왜냐하면 제스트가 이들을 테스트에 전역 객체로 포함하기 때문이죠! 이제 아래와 같은 모습으로 보입니다. ```javascript // 5.js const {sum, subtract} = require('./math') test('sum adds numbers', () => { const result = sum(3, 7) const expected = 10 expect(result).toBe(expected) }) test('subtract subtracts numbers', () => { const result = subtract(7, 3) const expected = 4 expect(result).toBe(expected) }) ``` 해당 파일을 제스트로 실행하면 결과는 아래와 같은 모습입니다. ```plaintext $ jest FAIL ./5.js ✕ sum adds numbers (5ms) ✓ subtract subtracts numbers (1ms) ● sum adds numbers expect(received).toBe(expected) Expected value to be (using Object.is): 10 Received: -4 4 | const result = sum(3, 7) 5 | const expected = 10 > 6 | expect(result).toBe(expected) 7 | }) 8 | 9 | test('subtract subtracts numbers', () => { at Object..test (5.js:6:18) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 passed, 2 total Snapshots: 0 total Time: 0.6s, estimated 1s Ran all test suites. ``` 텍스트로는 구별하기가 어렵지만 결과는 컬러 코딩됩니다. 아래는 결과 이미지입니다. ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709277635583/0215e43a-1fc1-4bae-ab3f-6934cbbda0f3.webp align="center") 제스트는 컬러 코딩으로 관련 부분을 식별하는 데 정말 도움이 됩니다 😀 **오류가 발생한 코드도 보여 주죠! 이제야 오류 메시지가 유용해지네요!** ## **결론** 그래서 자바스크립트 테스트가 뭘까요? 자바스크립트 테스트란 단지 어떤 상태를 설정하고, 어떤 행동을 수행하고, 새로운 상태를 위한 어서션을 만드는 코드입니다. 여기서는 `beforeEach`나 `describe` 같은 [**일반적인 프레임워크 헬퍼 함수**](https://facebook.github.io/jest/docs/en/api.html)는 다루지 않았고 `toMatchObject`나 `toContain` 같이 추가할 수 있는 [**어서션**](https://jestjs.io/docs/expect)도 훨씬 더 많습니다. 그렇지만 이 글을 통하여 여러분이 자바스크립트 테스팅의 기본 개념에 관한 발상을 얻기를 바랍니다. 도움이 되기를 바라며! 행운을 빌어요! 👍