Programming/TDD

01-TDD 이론 및 패턴 소개

류시명 2022. 1. 16. 23:37

인프런 김정환 님 견고한 JS 소프트웨어 만들기 강의 노트 입니다.

1. 아리송한 상황들

console.log = 14;
console.log() // error.

에러가 발생한다. console.log에 14를 할당한 순간부터 함수가 아니기 때문이다.

문제는 이런 에러가 런타임에서 발생한다는 것이다.

'1' + 1 = '11'
10 - '11' = -1
'2' * 3 = 6
'1' / 10 = 0.1
'1' % 10 = 1
1 + '2' + 3 * 4 = '1212'

위 코드처럼 결과 예측이 어려울 때도 있다.

number와 string타입의 숫자를 사칙연산하게 되면,

+ 연산자일 때는 문자열로 합쳐지고,

그외 연산자에서는 그대로 number타입에서처럼 계산이 된다.

이는 타입이 모호하기 때문이다.

자바스크립트에는 위처럼 에러가 런타임에서 발생하고, 동적 타입을 갖고 있기 때문에

테스트를 이용해 에러를 미연에 방지하는 것이 최선이다.

이런 문제를 극복할 방법으로 테스트 주도 개발을 하자.

ps. FP, TDD, Static type은 모두 자바스크립트의 태생적 단점을 극복하기 위한 노력이 아닐까

2. 몇 가지 테스트 개념들

단위 테스트(Unit Test)와 테스트 주도 개발(TDD)에 대해 알아보자

단위 테스트(Unit Test)

  • 단위(Unit)
    • 특정 조건에서 어떻게 작동해야 할 지 정의한 것
    • 특정 input에 따라 특정 output을 내놓는 것
    • 주로 함수로 표현

즉 단위 테스트는 쉽게 말하면 함수를 테스트하는 것이다.

단위 테스트는 준비(arrange), 실행(act), 단언(assert) 패턴을 따른다.

input을 준비하고(arrange)

input을 전달해 함수를 실행하고(act))

결과를 단언(검증)하는 단계를 거친다.(assert)

테스트 주도 개발(Test Driven Development)

단위 테스트는 테스트 주도 개발의 첫 번째 단계이다.

테스트 주도 개발(TDD)이란 기능하는 함수를 바로 코딩하는 것이 아니라 기능을 검증하는 테스트 코드를 먼저 작성하고 그 테스트에 부합하는 코드를 작성한 뒤 테스트 코드를 기준삼아 안정적인 리팩토링을 거듭하여 코드의 품질을 보장하는 개발 방식을 말한다.

TDD는 테스트 코드 작성(Red), 테스트 코드에 부합하는 코드 작성(Green), 리팩토링(Blue)의 순환을 통해 이루어진다.

TDD의 장점과 테스트하기 쉬운코드

리팩토링이란 적절한 추상화, 중복 제거, 확장성 상승, 결합도 감소, 응집도 상승 등을 통해 코드의 기능은 같게, 성능(효율이나 가독성)은 더 좋게 하는 것으로 코드의 품질을 올리는 데 매우 중요하다.

어떤 기능을 리팩토링할 때 가장 우려되는 점은 리팩토링한 코드가 하기 전 기능 및 로직을 제대로 수행할지 알 수 없다는 점이다.

테스트 코드를 통해 이런 우려를 없애고 안전성을 보장받으면서 코드의 품질을 향상시킬 수 있다는 점이 TDD의 장점이다.

이런 TDD를 반복하다보면 테스트하기 쉬운 코드가 나오게 되며,

TDD를 잘하기 위해서 테스트하기 쉬운 코드를 설계하게 될 것이다.

테스트하기 쉬운 코드(함수)란 하나의 기능만 하는, 즉 결합도는 낮고 응집도는 높은 코드이다.

이는 관심사의 분리가 잘 이루어져야 작성 가능하다.

3. 설치 1

대표적인 자바스크립트 테스트 프레임 워크

자바스크립트 테스트 프레임워크는 jasmine, moca, jest 이 세 가지가 유명하다.

테스트 개념만 잘 익히면 하나만 제대로 해도 나머지 프레임워크를 새로 배울 때 문제가 없다.

강의에서는 jasmine(자스민)을 이용한다.

Jasmine 설치

자스민 설치 방법은 두 가지가 있다.

  • 스탠드얼론(standalone)
    • 모든 자스민 코드를 브라우저에 올려서 실행
    • 간단히 실행 결과를 볼 수 있음
    • 실무에선 별로 사용되지 않음
  • 카르마(karma)와 함께 설치하는 방법
    • 명령어 등을 통한 자동화
    • 실무에서 많이 사용됨.

참고: https://jasmine.github.io/pages/getting_started.html

강의 목적이니까 간단히 스탠드얼론으로 진행한다.

현재 이 강의는 2.7.0 버전을 기준으로 하고 있다.

설치: https://github.com/jasmine/jasmine/releases

에사 jasmine-standalone-2.7.0.zip를 클릭하여 압축 파일을 다운로드하고 압축 해제한다.

압축 풀린 폴더에서 SpecRunner.html이라는 파일 클릭하면

샘플 테스트 코드의 결과가 보여지는데, 이 SpecRunner.html 파일이 테스트 러너이다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Jasmine Spec Runner v2.7.0</title>

  <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.7.0/jasmine_favicon.png">
  <link rel="stylesheet" href="lib/jasmine-2.7.0/jasmine.css">

  <!-- 자스민 라이브러리 파일 -->
  <script src="lib/jasmine-2.7.0/jasmine.js"></script>
  <script src="lib/jasmine-2.7.0/jasmine-html.js"></script>
  <script src="lib/jasmine-2.7.0/boot.js"></script>

  <!-- 소스코드 -->
  <script src="src/Player.js"></script>
  <script src="src/Song.js"></script>

  <!-- 테스트코드 -->
  <script src="spec/SpecHelper.js"></script>
  <script src="spec/PlayerSpec.js"></script>

</head>

<body>
</body>
</html>

Test Runner(테스트 러너)

테스트 러너란 테스트 코드를 실행하는 파일을 말한다.

스탠드 얼론으로 설치하면 위와 같은 HTML파일이 테스트 러너가 되고

카르마를 이용하면 카르마가 테스트 러너가 된다.

강의 저장소

https://github.com/jeonghwan-kim/lecture-develop-fe-with-tdd

위 레파지토리의 코드를 clone 받고 진행한다.

$ git checkout -f install-jasmine

해당 강의는 목차별로 branch로 나뉘어 있다.

설치는 install-jasmine 브랜치를 이용한다.

참고

4. 설치 2

index.html

sample 코드

<html>
  <head>
    <meta charset="UTF-8">

    <link rel="shortcut icon" type="image/png" href="../jasmine/lib/jasmine-2.6.4/jasmine_favicon.png">
    <link rel="stylesheet" type="text/css" href="../jasmine/lib/jasmine-2.6.4/jasmine.css">

    <script type="text/javascript" src="../jasmine/lib/jasmine-2.6.4/jasmine.js"></script>
    <script type="text/javascript" src="../jasmine/lib/jasmine-2.6.4/jasmine-html.js"></script>
    <script type="text/javascript" src="../jasmine/lib/jasmine-2.6.4/boot.js"></script>
  </head>
  <body>

  <script>
    describe('hello world', ()=> { // 테스트 스윗: 테스트 유닛들의 모음 
      it('true is true', ()=> { // 테스트 유닛: 테스트 단위
        expect(true).toBe(true) // 매쳐: 검증자 
      })
    })
  </script>

  </body>
</html>

테스트 기본 구성

  1. 테스트 꾸러미(Test Suite)
  2. 테스트 스펙(Test Spec)
  3. 기대식과 매쳐
  4. 스파이

1. 테스트 꾸러미(Test Suite)

테스트 케이스 모음으로 describe('테스트 설명', 테스트 구현함수) 형태로 구현된다.

함수 하나하나를 테스트할 때 사용한다.

2. 테스트 스펙(Test Spec)

어떤 특정 케이스에서 함수의 기능을 확인하며 it('테스트 설명', 기대식을 가진 테스트 구현 함수) 형태로 구현된다.

3. 기대식과 매쳐

함수의 결과값과 기대되는 값을 설정한다.

expect(결과값).toBe(기대값)의 형태로 구현한다.

위 index.html에서는 true는 true여야 한다.는 의미다.

4. 스파이

스파이는 이후 강의에서 소개하며 spyOn(감시할 객체, 감시할 메소드)의 형태고 구현한다.

5. 테스트할 수 없는 코드 1

통상 우리가 작성하는 코드는 테스트하기 어렵다.

<button onclick="counter++; countDisplay()">증가</button>
<span id="counter-display">0</span>
<script>
  var counter = 0;

  function countDisplay() {
    var el = document.getElementById('counter-display');
    el.innerHTML = counter;
  }
</script>

위와 같은 코드를 보자.

html 부분과 script부분으로 나누어 볼 수 있다.

어떤 문제가 있을까?

6. 테스트할 수 없는 코드 2

  1. 관심사가 분리되지 않음
  2. 재사용성이 떨어짐

1. 관심사가 분리되지 않음

클릭 이벤트 처리기를 인라인 형태로 정의되어 있다.

한 줄인데 counter를 증가시키는 것과 countDisplay함수를 실행시키는 두 가지 일을 하고 있다.

<button onclick="counter++; countDisplay()">증가</button>

소프트웨어 작성 규칙 중에는 단일책임원칙이라는 게 있다.

하나의 코드는 한 가지 역할만 해야 한다.

2. 재사용성이 떨어짐

counter라는 전역 변수 사용은 최대한 최대한 지양해야 한다.

전역 변수는 다른 함수와 충돌할 가능성이 높기 때문이다.

또한 횟수를 표시하는 span의 id를 displayCount 함수에서 하드코딩해서 넣어주고 있다.

이러면 다른 셀렉터 혹은 다른 id에 displayCount 함수를 사용할 수 없게 된다.

코드는 open - close 규칙, 즉 확장에는 열려있고 변경에는 닫혀있어야 한다.

어떻게 하면 테스트할 수 있을까?

  1. 코드를 UI에서 완전히 분리
    • HTML에서 JS 코드를 떼어내면 비즈니스 로직만 테스트할 수 있음
  2. 자바스크립트를 별도 파일로 분리
    • 다른 곳에서도 재사용할 수 있고 테스트성도 좋아짐

7. 잠깐, 모듈 패턴

하나의 문제에는 여러가지 해결방법이 있다.

그중 가장 좋은 방법을 찾아 사용하는 것이 개발자의 덕목이다.

오랜 시간에 걸쳐 여러 개발자들이 많이 사용하는 최적화된 방법을 디자인 패턴이라고 한다.

그중 하나가 모듈 패턴이다

모듈 패턴?

모듈패턴이란 함수로 데이터를 감추고, 모듈 API를 담고 있는 객체를 반환하는 형태

자바스크립트에서 가장 많이 사용되는 패턴이다.

임의 모듈 패턴과 즉시실행함수 모듈 패턴이 있다.

1. 임의 모듈 패턴

// 이름 공간으로 활용
var App = App || {};
var God = {
  makeName: function() {
    return '홍길동';
  }
}

// 이름공간에 함수를 추가한다. 의존성 있는 God 함수를주입
App.Person = function(God) {
  var name = God.makeName(); 

  // API를 노출한다.
  return {
    getName: function() { return name },
    setName: function(newName) { name = newName }
  }
}

// 아래와 같이 사용
var person = App.Person(God);
person.getName() // 홍길동

2. 즉시 실행 함수(IIFE) 모듈 패턴(싱글톤 인스턴스가 됨)

var App = App || {};

var God = {
  makeName: function() {
    return '홍길동';
  }
}

App.Person = (function() {
  var name = '';

  return {
    getName: function(God) { 
      name = name || God.makeName();
      return name;
    },

    setName: function(newName) { 
      name = newName 
    }
  }
})(); // 함수 선언 즉시 실행한다. 싱글톤이다.

// 아래와 같이 사용한다.
App.Person.getName(God); 

객체가 여러 개 필요할 때는 임의 모듈 객체, 단 하나의 객체만 필요할 때는 즉시실행함수 모듈패턴을 사용한다.

모듈 생성 원칙

모듈 패턴을 잘 사용하기 위한 규칙

1. 단일 책임 원칙

단일 책임 원칙에 따라 모듈은 한 가지 역할만 해야 한다.

하나의 역할에만 집중하여 만들면 사이드 이펙트도 적고

테스트하기도 쉬운 모듈을 만들 수 있다.

2. 의존성 주입

모듈 자신이 사용할 객체가 있다면 의존성 주입 형태로 제공한다.

또는 팩토리 주입형태로 제공한다.

테스트하기도 쉽다.

모듈 하나로 모든 기능을 만들 수 없다.

여러가지 역할을 하는 각각의 모듈들이 만들어지고 이에 따라

모듈 간의 의존성이 형성된다. (Person은 name을 만들기 위헤 God이 필요하다.)

이때 의존하는 모듈을 인자로 넘겨주는(주입) 경우가 많다.

강의에서는 임의 모듈 패턴을 사용한다.

반응형