개발 기술/개발 이야기

TDD에 대해 알아보자! (with. jest)

by GicoMomg 2022. 3. 19.

👀 이번시간에는 TDD는 무엇이며, TDD 중에서 단위테스트도 직접 해보자!

1. TDD(Test Driven Development)?

1) TDD란?

  • TDD(테스트 주도 개발)은 말 그대로 개발보다 테스트를 우선 실행하는 걸 말한다.
  • 그림1은 기존 개발 프로세스로 설계 → 개발 → 테스트 순으로 진행된다.
  • 만약 테스트 결과 실패를 하게 되면 설계를 수정하고 개발 코드를 변경하므로 비용이 높다.
  • 하지만 그에 비해 그림2(TDD)는 개발자는 먼저, 요구사항에 맞는 테스트 케이스를 작성한다.
  • 그 다음, 테스트 케이스를 통과를 위한 코드를 작성하고, 해당 코드를 리팩토링하는 방식이다.



2) TDD의 장점

(1) 개발 목적에 집중이 가능하다.

💡 내가 작성하는 코드에 대한 이해, 문제 구체화가 가능하다.

  • 개발자는 설계 → 개발 → 테스트를 거치는 기존 프로세스가 아닌, 설계 → 테스트 → 개발 프로세스를 거친다.
  • 그렇기 때문에 테스트케이스를 작성할 때, 본인에게 주어진 문제를 구체적으로 이해해야한다.
  • 이러한 이해는 곧, 본인이 어떤 목적으로 해당 코드를 작성하는지 알 수 있게 한다.

(2) 비용을 감소시킬 수 있다.

💡 설계 수정으로 인한 비용을 줄일 수 있다.

  • 앞서 살펴본 그림을 보면, 기존 프로세스는 테스트가 실패가 하면 설계부터 다시 시작해야한다.
  • 이는 시간, 인력 등에 대한 비용이 증가를 초래한다.
  • 하지만, 그에 비해 TDD는 개발자의 구체적인 문제 이해를 바탕으로 테스트 케이스를 우선 작성한다.
  • 그렇기 때문에 개발상 문제가 발생하더라도, 재설계보다는 테스트코드 리팩토링 정도에 그칠 수 있다.
  • 이는 개발에 앞서 완성도 높은 설계가 진행했기에, 큰 비용 증가를 방지할 수 있다.

(3) 재사용 가능한 코드를 만들 수 있다.

💡 TDD는 코드의 재사용성을 보장한다.

  • TDD를 우선 진행하기 위해서는 우리는 기능을 모듈화 해야한다.
  • 모듈화는 코드는 재사용성이 높일 뿐 아니라, 만약 코드에 문제가 생겨 제거를 해도 전체 소프트웨어 대한 영향이 적다.



3) TDD 기법 종류

(1) 수동 테스트

높은 실행 비용, 사용자 경험에 기반한 테스트

  • QA 담당자가 UI를 활용하여 각 기능을 테스트한다.
  • 사람이 테스트하기 때문에 사용자와 유사한 경험으로 테스트가 가능하다.
  • 이 테스트를 위해서는 모든 구현이 완료되어 있어야 한다.
  • 사람이 진행하기 때문에 실행비용이 높고 QA 담당자의 역량에 따라 결과 변동이 크다.
  • 결과 변동을 줄일 수 있는 방법으로는 QA 명세를 작성하는 방법도 존재한다.

(2) 테스트 자동화

낮은 실행 비용, 개발자 역량에 영향을 받는 테스트

  • 사람이 테스트하지 않고 시스템이 코드를 검증한다.
  • 테스트 코드 작성을 위한 비용이 발생하지만 수동 테스트에 비해 신뢰도는 높다.
  • 단, 테스트 코드 작성&유지보수의 경우 개발자의 역량에 크게 영향을 받는다.

(3) 인수 테스트

높은 비용, 높은 신뢰도를 가진 테스트

  • 배치된 시스템을 대상으로 테스트가 진행된다.
  • 전체 시스템에 이상이 없는지 검증하기에 신뢰도가 매우 높다.
  • 단, 코드 작성, 관리, 테스트까지에 들어가는 비용이 높다.
  • 또한 전체 시스템을 테스트하기에, 개발자 입장에서는 피드백의 질이 떨어질 수 있다. (문제를 파악할 수는 있지만, 개발한 코드에 문제인지 전체 시스템에 문제인지 원인을 찾기 어려움)

(4) 단위 테스트

낮은 비용, 높은 피드백을 보장하는 테스트

  • 가장 작은 단위(메소드, 함수)별로 테스트를 진행한다.
  • 단위별로 진행하기에 테스트 작성, 관리, 실행 비용이 낮은 편이다.
  • 단위별로 테스트 하기에, 개발자 입장에서는 피드백의 질이 높아진다.
  • 단 인수 테스트와 달리 전체 시스템의 이상여부는 판단하기 어렵다.



4) TDD, 단위 테스트 해보기

💡 앞서 우리는 여러 테스트 기법을 알아보았는데, 이 중 jest를 이용하여 단위테스트를 해보자!

(1) 기본 설정하기

  • npm init을 먼저 해준다.
npm init

  • 테스트 도구인 jest, 랜덤문자를 생성하는 @faker-js/faker를 설치한다.
npm --save-dev jest 
npm --save-dev @faker-js/faker  

  • npm testjest를 실행하도록 해주었다.
// package.json

"scripts": {
  "test": "jest --watch"
},

(2) 구현 목적 확인하기

🗣 “hello, 문자” 문장에서 문자를 마스킹 문자(*)로 변경해주세요

  • 함수는 두개의 인자(문장, 특정 문자)가 필요하다.
  • 문장에서 특정문자를 마스킹 문자로 변경해야 한다.

(3-1) 테스트 케이스 작성하기, 나열하는 방법

  • 구현 목적에 맞는 여러 테스트 케이스를 나열식으로 작성하는 방법이다.
// index.test.js

const sut = require("./index");

test('sut transform "hello world", "world" to "hello *****"', () => {
  const result = sut("hello world", "world"); 
  expect(result).toBe("hello *****");
});

test('sut transform "hello apple", "apple" to "hello *****"', () => {
  const result = sut("hello apple", "apple"); 
  expect(result).toBe("hello *****");
});

test('sut transform "hello bee", "bee" to "hello ***"', () => {
  const result = sut("hello bee", "bee"); 
  expect(result).toBe("hello ***");
});

(3-2) 테스트 케이스 작성하기, 반복문 쓰는 방법

  • 앞선 테스트 케이스를 나열식이 아닌 반복문으로 변경한 코드이다.
  • 반복문을 쓰면, 코드 길이는 줄어들지만 테스트가 실패한 경우에 대한 입력값을 알 수 없다.
  • 또한, 우선 실행된 테스트가 실패하면, 그 이후 경우에 대한 테스트가 불가하다.
test('sut correctly works"', () => {
  for (const source of [["hello world", "world"], ["hello apple", "apple"], ["hello bee", "bee"]]) {
    const result = sut(source[0], source[1]);
    const expected = `hello ${"*".repeat(source[1].length)}`;
    expect(result).toBe(expected);
  }
});

(3-3) 테스트 케이스 작성하기, ParameterizedTest

  • ParameterizedTest는 하나의 테스트 메소드로 여러 개의 파라미터에 대해서 테스트 가능하다.
  • 코드 중복은 줄이되 테스트 품질 보장 가능하다.
test.each`
  source             | bannedWord | expected
  ${"hello world"}   | ${"world"} | ${"hello *****"}
  ${"hello apple"}   | ${"apple"} | ${"hello *****"}
  ${"hello bee"}     | ${"bee"}   | ${"hello ***"}
`('sut transforms "$source", "$bannedWord" to "$expected"', ({ source, bannedWord, expected }) => {
const result = sut(source, bannedWord);
  expect(result).toBe(expected);
});

(3-4) 테스트 케이스 작성하기, 랜덤값

  • 이번에는 코드 중복을 줄이고 랜덤값을 테스트할 수 있게 변경해보았다.
  • 랜덤값의 경우, 앞서 설치한 faker 라이브러리를 이용하였다.
describe("given banned word", () => {
  const bannedWord = faker.lorem.word();
  const source = `hello ${bannedWord}`;
  const expected = `hello ${"*".repeat(bannedWord.length)}`;

  test(`${bannedWord} then invoke sut then it returns ${expected}`, () => {
    const result = sut(source, bannedWord);
    expect(result).toBe(expected);
  });
});

(4) 개발하기

  • 앞선 테스트 케이스에 따라 함수를 만들고 재테스트를 해보았다.
  • 만약 해당 코드의 테스트 결과를 보고 싶다면, 이 링크를 보자 🙂
// index.js

function refinetText(s, text) {
  s = s.replace(text, "*".repeat(text.length));
  return s;
}

module.exports = refinetText;

반응형

댓글