테스팅(Testing)
작성한 코드에 대한 원하는 결과가 있을 것입니다. 예상 결과를 확인하기 위해 브라우저에서 해당 기능을 사용해볼 것입니다. 또한 이러한 예상 결과를 가지고 애플리케이션을 테스트하게 될 것입니다. 만약 테스트가 성공적이라면 다음 코드 작업으로 넘어갈 것이고, 만족하지 못한다면 코드를 수정하게 될 것입니다.
이러한 테스트 과정을 브라우저를 통해 모두 수동으로 테스트할 필요는 없습니다. 테스트를 자동화하게 되면 코드를 변경할 때마다 자동화된 테스트는 실행되어 다시 수동으로 테스트하지 않고도 애플리케이션에 영향을 주는지 확인할 수 있습니다.
자동화된 테스트는 코드의 큰 변경 사항이나 문제를 볼 수 있고, 애플리케이션의 전혀 다른 곳인 A에서 변경되어 생긴 문제를 즉시 확인할 수 있다는 장점이 있습니다.
자동화된 테스트를 하는 이유
- 모든 코드를 수동으로 테스트하지 않고 즉시 확인하기 위해서입니다.
에러가 발생했을 때 잘 작성된 테스트가 있다면 에러에 대해 즉시 답을 얻을 수 있게 됩니다.
- 시간을 절약할 수 있습니다.
수동으로 전체 애플리케이션을 반복 테스트할 필요가 없기 때문에 시간을 절약할 수 있습니다.
- 사전 문제 및 버그에 대한 생각을 할 수 있습니다.
테스트 작성 시 어떤 테스트를 작성하고 무엇을 위한 테스트를 할 것인지 대해 생각하고 명확한 테스트를 만들어야 하기 때문에 문제 및 버그를 생각할 수 있습니다.
- 빌드 워크플로우에 테스트를 통합할 수 있습니다.
Github에 commit을 실행하면 자동으로 워크플로우가 트리거 되어 코드가 서버의 클라우드에서 테스트가 됩니다. 해당 테스트가 성공하게 되면 자동으로 배포가 됩니다. 따라서 매우 복잡한 배포 체인을 빌드할 수 있으며 이때 테스트는 유효한 코드를 배포하는데 필수입니다.
- 복잡한 의존성을 분리할 수 있습니다.
분리 가능한 모듈식 코드를 작성해야 테스트가 쉬워집니다. 이로 인해 코드로 작업하는 것은 보다 쉬워짐과 동시에 코드의 개선 또한 결과로 나타나게 될 것입니다.
- 향상된 질의 코드를 작성할 수 있습니다.
특정한 패턴을 따라야 하거나 특정 방식으로 코드를 작성해야 하기 때문에 혹은 테스팅을 쉽게 만들기 위해 더 좋은 코드를 작성할 것입니다.
테스트의 종류
- 유닛 테스트
애플리케이션의 분리된 단위로 하나를 테스트하는 것을 유닛 테스트라고 합니다. 예를 들어 입력을 받아 반환하는 함수를 테스트하는 것입니다. 해당 함수는 입력을 받아 반환하는 함수이며, 해당 기능에 대해서만 기능하기 때문에 분리된 함수입니다. 이러한 분리된 목적이 명확한 함수를 테스트하는 것을 유닛 테스트라고 합니다.
- 통합 테스트
다른 코드에 의존하는 코드를 테스트하는 것을 통합 테스트라고 합니다. 의존성을 가진 단위에 대한 테스트가 있습니다. 더 이상 분리된 코드 조각이 아닌 다른 함수를 호출하는 함수가 있다고 가정해 보겠습니다. 따라서 테스트하는 함수는 다른 함수의 결과에 따라 달라지게 됩니다. 이때 단일 단위 외에도 기능을 또 다른 기능에 통합하는 것을 테스트합니다.
- E2E 테스트
테스트 가능한 전체 흐름이나 사용자 인터페이스의 경우 즉, 전체 애플리케이션 또는 애플리케이션의 일부에서 대해 테스트하는 것을 E2E 테스트라고 합니다. 브라우저에서 수동으로 원하는 작업을 하면서도 특정한 단계를 실행하는 자동화된 코드를 작성하여 예상한 결과를 얻는지 확인합니다.
이러한 테스트는 다양한 복잡성을 가지는데 유닛 테스트는 단위가 작고, 결과로 얻을 것을 예상하기 쉽기 때문에 상대적으로 작성하기가 쉽습니다. 의존성을 추가할수록 복잡성을 올라갑니다. 오류가 의존성 때문인지 의존성을 사용하는 함수 때문인지 정확히 무엇이 오류를 발생시키는지 점점 구분하기 어려워지기 때문입니다. E2E 테스트는 가장 복잡한 테스트입니다. 실행되기 전 단계를 정의해야 하고 무엇을 테스트하고 예상할지 명확하게 생각해야 하기 때문입니다.
이러한 복잡성 때문에 테스트를 작성하는 빈도는 서로 다릅니다. 보통을 유닛 테스트를 많이 사용하며 애플리케이션의 모든 단위를 테스트해서 잘 작동하면 전체 애플리케이션도 작동하리라 예상할 수 있기 때문입니다. 일부 통합 테스트의 경우 두 개의 개별로 실행되는 단위를 통합하여 실행하기 위해 사용됩니다. 즉, E2E 테스트는 수가 적고, 애플리케이션의 단계나 흐름은 브라우저에서 테스트되고 자동화 방식으로 이루어집니다.
테스트 설정
- 테스트 러너 (Test Runner)
테스트를 실행하고 결과를 요약해서 결과에 대한 출력을 제공합니다. 흔히 사용하는 테스트 러너로는 Mocha가 있습니다.
- 어서션 라이브러리(Assertion Library)
테스트 논리와 예상 결과를 정의합니다. 어서션 라이브러리는 도구를 제공하여 예상 및 테스트의 일부로써 확인하고 싶은 비교 및 조건 등을 정의합니다. 흔히 사용하는 어서션 라이브러리로는 Chai가 있습니다.
- 헤드리스 브라우저(Headless Browser)
E2E 테스트를 위해 헤드리스 브라우저가 필요한 경우가 있습니다. 헤드리스 브라우저는 수동으로 클릭하지 않아도 되고 브라우저 API와 DOM 등을 사용할 수 있으며 코드에서 분석하기 때문에 사용자 인터페이스는 필요하지 않습니다. 헤드리스 브라우저로는 Puppeteer를 사용할 수 있습니다.
*해당 게시글은 테스트 러너, 어서션 라이브러리 모두 Jest를 사용합니다.
Jest
Jest
By ensuring your tests have unique global state, Jest can reliably run tests in parallel. To make things quick, Jest runs previously failed tests first and re-organizes runs based on how long test files take.
jestjs.io
Jestjs.io에서 Getting Started를 선택해 공식 자료를 보면 많은 사용 예제를 확인할 수 있습니다.
npm install --save-dev jest
우선 터미널에 위의 명령어를 입력하여 Jest를 설치해 줍니다.
설치한 Jest를 사용해 유닛 테스트를 진행하겠습니다.
exports.generateText = (name, age) => {
// Returns output text
return `${name} (${age} years old)`;
};
함수 generateText는 의존성이 없는 함수입니다. 단순하게 2개의 입력을 받아 반환하고 있습니다.
테스트를 위해 별도의 파일을 만들어주었으며, Jest는 자동으로. test와. spec를 포함한 파일을 탐지하여 실행하게 됩니다. 해당 파일 안에서 함수 generateText의 유닛 테스트를 작성하겠습니다.
const { generateText } = require("./util");
우선 테스트할 함수를 가져옵니다. 해당 방법으로 test 할 함수를 가져오는 것이 Jest를 이용한 테스트에 import 하는 네이티브 방법입니다.
*Jest의 경우 import를 지원하지 않기 때문에 require을 사용해 함수를 가져왔습니다.
test("should output name and age", ()=>{
const text = generateText("Max", 29)
})
test 함수를 작성합니다. test 함수의 경우 테스트를 실행할 때 사용되는 함수이며, 첫 번째 인수로는 테스트에 대한 정의를 전달하고 두 번째 인수로는 함수를 전달합니다. 전달하는 함수는 Jest가 테스트를 위해 실행하게 됩니다.
함수 generateText에 인수로 이름과 나이를 전달하여 반환하는 값을 text에 담아주었으며 text의 결과로 "Max (29 years old)"를 기대할 수 있습니다.
*test 함수의 경우 테스트 러너가 정의한 함수입니다.
test("should output name and age", () => {
const text = generateText("rati", 5);
expect(text).toBe("Max (29 years old)");
});
Jest가 제공하는 expect 함수에 비교하고 싶은 내용을 전달하고. toBe()의 인수로 기대하는 값을 전달합니다.
*expect 함수의 경우 어서션 라이브러리가 제공한 함수입니다.
Jest를 실행하기 위해서는 package.json을 주정해주어야 합니다.
test 스크립트를 jest로 변경하고 npm test를 실행하게 되면 아래의 테스트 결과를 확인할 수 있습니다.
하나의 테스트가 통과를 했고, 첫 번째 인수로 전달한 테스트 설명이 출력되는 것을 보고 통과한 테스트가 무엇인지 알 수 있습니다.
기대하는 값을 변경하고 다시 실행시키면 실패한 테스트가 나타나게 됩니다. 테스트를 확인해보면 해당 테스트는 이름과 나이를 반환하는 함수의 테스트이며 예상한 값과 결과 값을 확인할 수 있고 나온 테스트 결과를 보고 코드를 수정을 할 수 있습니다.
test("should output name and age", () => {
const text = generateText("Max", 29);
expect(text).toBe("Max (29 years old)");
const text2 = generateText("Rati", 5); // 이중 체크
expect(text2).toBe("Rati (5 years old)");
});
// 입력의 값이 없을 경우 테스트
test("should output data-less text", () => {
const text = generateText("", null);
expect(text).toBe(" (null years old)");
});
해당 유닛 테스트를 이중으로 테스트하고 반대의 경우인 입력이 없을 경우의 유닛 테스트를 하여 보다 정확한 테스트가 될 수 있도록 해주었습니다.
통합 테스트
const addUser = () => {
// Fetches the user input, creates a new HTML element based on it
// and appends the element to the DOM
const newUserNameInput = document.querySelector('input#name');
const newUserAgeInput = document.querySelector('input#age');
if (
!validateInput(newUserNameInput.value, true, false) ||
!validateInput(newUserAgeInput.value, false, true)
) {
return;
}
const userList = document.querySelector('.user-list');
const outputText = generateText(
newUserNameInput.value,
newUserAgeInput.value
);
const element = createElement('li', outputText, 'user-item');
userList.appendChild(element);
};
함수 addUser의 경우 내부에 다른 함수를 많이 사용하여 의존성이 높으며, 해당 함수는 입력이 없고 반환을 하지 않는 대신 DOM을 추가하는 함수입니다.
*함수 addUser는 요소 몇 가지를 선택해서 입력을 검사하고 해당 입력을 기반으로 텍스트를 생성합니다.
exports.checkAndGenerate = (name, age) => {
// 입력값 검사
if (!validateInput(name, true, false) || !validateInput(age, false, true)) {
return false;
}
return generateText(name, age);
};
통합 테스트를 위해 해당 함수의 모듈화를 진행합니다. 함수 addUser에 입력값 검사하는 로직을 잘라서 함수 checkAndGenerate로 옮겨주고 앞서 유닛 테스트에 사용한 함수 generateText에 입력값을 전달하여 반환하도록 만들어줍니다.
기존의 함수 addUser도 위와 같이 수정해 줍니다.
const { generateText, checkAndGenerate } = require("./util");
test("should generate a valid text output", () => {
const text = checkAndGenerate("Max", 29);
expect(text).toBe("Max (29 years old)");
});
테스트를 위해 해당 테스트가 어떤 테스트인지에 대한 적절한 설명과 함수를 test 함수에 전달하고 기댓값을 전달합니다. 앞서 시행한 유닛테스트와 같아 보이지만 현재 하는 테스트는 함수가 더 많은 일을 하고 있습니다. 유효성 검사가 실패할 수도 있기 때문입니다.
예시 코드의 통합 테스트의 경우 함수 generateText가 별도의 유닛 테스트를 진행하고 있습니다. 이건 테스트를 작성하는 방법이기도 한데 가능한 제일 작은 레벨까지 자세하게 살펴보고 모든 유닛에 대해 유닛 테스트를 작성합니다. 통합 테스트는 실제로 유닛 테스트에 의존하며 해당 유닛이 잘 작동하는지 확인해야 합니다.
여기까지 하고 test를 실행하면 아래와 같은 결과를 확인할 수 있습니다.
3개의 테스트가 정상적으로 통과했습니다.
모든 유닛 테스트가 통과한 상황에서 해당 유닛들을 조합하는 과정에서 문제가 생길 수 있습니다. 이때 통합 테스트를 진행해 줍니다.
E2E 테스트
npm install -save-dev puppeteer
E2E 테스트를 위해 puppeteer를 설치해 줍니다.
const puppeteer = require("puppeteer");
test("should click around", async () => {
// 브라우저 구축
// launch()의 옵션 전달
// 프로미스 반환
const browser = await puppeteer.launch({
headless: false, // 사용자 인터페이스로도 브라우저를 실행할 수 있게 합니다.
slowMo: 80, // 자동화된 작업의 처리 속도를 늦춰줍니다.
args: ["--window-size=1920, 1080"], // --window-sizes는 너비와 높이를 설정합니다.
});
});
const puppeteer = require("puppeteer");
test("should click around", async () => {
// 브라우저 구축
// launch()의 옵션 전달
// 프로미스 반환
const browser = await puppeteer.launch({
headless: false, // 사용자 인터페이스로도 브라우저를 실행할 수 있게 합니다.
slowMo: 80, // 자동화된 작업의 처리 속도를 늦춰줍니다.
args: ["--window-size=1920, 1080"], // --window-sizes는 너비와 높이를 설정합니다.
});
const page = await browser.newPage() // 페이지 객체 생성
});
newPage를 통해 페이지 객체를 생성할 수 있습니다.
const puppeteer = require("puppeteer");
test("should click around", async () => {
// 브라우저 구축
// launch()의 옵션 전달
// 프로미스 반환
const browser = await puppeteer.launch({
headless: false, // 사용자 인터페이스로도 브라우저를 실행할 수 있게 합니다.
slowMo: 80, // 자동화된 작업의 처리 속도를 늦춰줍니다.
args: ["--window-size=1920, 1080"], // --window-sizes는 너비와 높이를 설정합니다.
});
const page = await browser.newPage(); // 페이지 객체 생성
await page.goto("http://127.0.0.1:5500/index.html"); // 어떤 페이지를 불러올건지 전달
});
이때 입력에 대한 추가 테스트를 작성하고 싶다면 아래와 같이 코드를 작성하여 진행합니다.
click으로 선택자를 전달하고 type로 헤드리스 브라우저에 명령을 전달합니다. 위의 경우 Input을 클릭하고 해당 input에 설정한 입력값이 들어가게 되고 버튼을 클릭하게 됩니다.
*test 함수의 세 번째 인수로는 테스트 시간입니다.
Evaluate JavaScript | Puppeteer
Puppeteer allows evaluating JavaScript functions in the context of the page
pptr.dev
'JavaScript' 카테고리의 다른 글
[Movie App Project ] JS로 Store 구현하기 (0) | 2022.12.30 |
---|---|
HTTP (0) | 2022.12.25 |
함수 (2) | 2022.12.25 |
Closure (0) | 2022.12.25 |
클래스 (0) | 2022.12.24 |