> For the complete documentation index, see [llms.txt](https://kukingclass.gitbook.io/unittesting/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kukingclass.gitbook.io/unittesting/1-./1.md).

# 1장. 단위 테스트 목표

1장 단위테스트의 목표

### 단위테스트 현황

단위테스트를 적용해야 하는지는 논쟁 거리조차 아니다. (이미 필수이기 때문에)

문제는 **좋은** 단위테스트를 작성하려면 어떻게 해야하냐는 것이다.

### 단위테스트의 목표

단위테스트를 진행하는 목적은 지속 가능한 프로젝트를 만들기 위함이다. 4장에서 다시 언급되지만 지속 가능한 성장이 가능한 메커니즘은 회귀 없이 주기적으로 코드를 리팩터링하고 새로운 기능을 추가할 수 있는 것이다. 테스트 유무에 따라 시간이 흐르면서 프로젝트 진행 속도가 현저히 차이날 수 있다.

이유는 소프트웨어 **엔트로피** (무질서도) 때문이다. 소프트웨어 시스템 내의 무질서는 코드에서 비롯된다. 코드베이스에서 변경이 발생하면 무질서도가 증가한다. 적절한 정리와 리팩터링 없이는 시스템이 감당하지 못할 정도로 복잡해지고 무질서해진다. 그리고 테스트를 통해 이러한 경향성을 뒤집을 수 있다.

테스트는 시스템의 안전망 역할을 하며 회귀를 방지하는 역할을 수행한다.

> 회귀 (Regression) 본 책에서 회귀라는 용어는 버그와 동의어이다. 코드 변경 후 기능이 원하는대로 동작하지 않는 것을 의미

#### 좋은 테스트와 좋지 않은 테스트를 가르는 요인

테스트코드를 작성하는것 만으로는 충분하지 않다. 좋지 않은 테스트코드는 아예 없는것 보다는 낫지만 시간이 흐르면서 시스템의 무질서도가 증가하는것을 막지 못한다. 다음은 일반적인 테스트코드 작성 가이드이다.

* 베이스코드를 리팩터링하면 테스트코드도 리팩터링 하라
* 코드의 수정이 발생하면 테스트를 실행하라
* 테스트가 잘못된 경고를 발생시키면 (거짓 양성) 처리하라
* 테스트코드를 읽는데 시간을 투자하라

간과하기 쉬운 사실은 테스트코드 또한 코드라는 것이다. 코드는 자산이 아니라 책임이다. 따라서 테스트코드의 양을 늘리는데 집중하면 잠재적인 버그를 발생시킬 가능성이 있고 유지보수 비용이 증가한다.

### 테스트 커버리지

커버리지 지표는 테스트 스위트 (suite, 스위트룸의 스위트) 가 베이스코드를 얼마나 실행하는지를 백분율로 나타낸 값을 말한다. 일반적으로 커버리지가 높을수록 좋다고 생각되지만 그렇게 간단한 문제는 아니다. 커버리지 지표는 좋은 부정지표이지만 좋지 않은 긍정지표이기 때문이다.

> 부정지표 vs 긍정지표 본 책에서 부정지표와 긍정지표라는 표현이 자주 등장한다. 말 그대로 부정지표는 좋지 못함을 나타내는 지표이고, 긍정지표는 좋음을 나타내는 지표이다. 테스트 커버리지는 좋은 부정지표이지만 좋지 않은 긍정지표이다. 이 말의 뜻은 커버리지가 낮으면 문제가 있다 (테스트코드가 너무 적다)는 것을 보여주지만 커버리지가 높다고 해서 긍정적인 지표로 해석하기는 어렵다는 의미이다.

많이 사용하는 커버리지 지표로 코드 커버리지와 브랜치 커버리지가 있다.

#### 코드 커버리지

코드 커버리지는 테스트코드가 실행한 제품 코드 수 대비 전체 제품 코드 수의 비율이다.

$$
코드,커버리지, = , \frac {실행,라인,수}{전체,라인,수}
$$

```java
public boolean isStringLong(String inputStr) {
    if (inputStr.length() > 5) {
        return true;
    }
    return false;
}

@Test
void isStringLong() {
    boolean result = isStringLong("abc");
    assertThat(result)
            .isEqualTo(false);
}
```

테스트하기를 원하는 `isStringLong(String)` 메서드의 라인 수는 중괄호 포함 4줄이다. 아래 테스트코드를 실행하면 true를 리턴하는 라인을 제외한 모든 라인을 통과한다. 따라서 이 테스트코드의 커버리지는 3/4로 75%이다. 만약 `isStringLong(String)` 메서드를 리팩토링하여 한 줄로 만들면 어떻게 될까?

```java
public boolean isStringLong(String inputStr) {
    return inputStr.length() > 5;
}
```

이제 메서드는 한 줄로 줄었고 테스트코드는 모든 라인을 통과하여 코드 커버리지가 100%가 되었다. 그러나 테스트가 검증하는 결과 개수는 여전히 하나 뿐이다.

#### 분기 커버리지

분기 커버리지는 분기가 존재하는 테스트 대상 코드의 제어문을 통과한 수 대비 전체 제어문의 수의 비율이다.

$$
분기,커버리지, = , \frac {실행,분기,수}{전체,분기,수}
$$

```java
public boolean isStringLong(String inputStr) {
    return inputStr.length() > 5;
}

@Test
void isStringLong() {
    boolean result = isStringLong("abc");
    assertThat(result)
            .isEqualTo(false);
}
```

`isStringLong(String)` 메서드에서 발생 가능한 분기는 입력 문자열의 길이가 5보다 큰 경우와 그렇지 않은 경우이다. `isStringLong()` 메서드에서는 길이가 5보다 작은 경우만 테스트하고 있으므로 이 경우 분기 커버리지는 50%이다.

#### 커버리지 지표의 문제점

그렇다면 코드 커버리지와 분기 커버리지 만으로 테스트 스위트의 품질을 높일 수 있을까? 그렇지 못하다.

우선 커버리지를 100% 만족시킨다고 해도 테스트코드가 ***발생 가능한 모든 결과를 검증했다고 보증할 수 없***&#xB2E4;. 예를 들어 베이스 코드에서 동작을 수행하고 값을 리턴해주는것 이외에 부수적으로 다른 동작을 수행한다고 하자.

```java
private boolean wasLastStringLong;
    
public boolean isStringLong(String inputStr) {
    boolean result = inputStr.length() > 5;
    wasLastStringLong = result;    // 부수적인 동작
    return result;                 // 리턴하는 결과값
}

@Test
void isStringLong() {
    boolean result = isStringLong("abc");
    assertThat(result)
            .isEqualTo(false);     // 테스트코드는 리턴값만 검증
}

@Test
void noValidationTest() {
    boolean result1 = isStringLong("abc");
    boolean result2 = isStringLong("abcdef");
}
```

위 코드에서 `isStringLong(String)` 메서드는 `wasLastStringLong` 이라는 변수를 갱신하는 작업도 수행하고 있다. 하지만 `isStringLong()` 에서 해당 작업은 검증하지 않고 있다.

다음으로 `noValidationTest()` 테스트는 `isStringLong(String)` 의 코드 커버리지와 분기 커버리지를 100% 만족하고 있지만 검증하는 로직이 없다. 아무것도 검증하지 않기 때문에 의미가 없는 테스트코드이다.

이런 문제가 없도록 테스트코드를 잘 작성한다고 해도 문제가 있다. ***외부 라이브러리의 코드 경로를 고려할 수 없***&#xAE30; 때문이다.

표준 라이브러리를 포함해 직접 작성하지 않은 외부 라이브러리를 사용하는 경우 해당 라이브러리 구현에 의해 발생할 수 있는 수많은 예외 케이스가 있을 수 있다. 우리의 단위테스트 코드에서 이런 모든 예외 상황을 다루고 있는지 확신할 방법이 없다.

물론 단위테스트에서 외부코드의 모든 경로를 고려해서는 안되지만, 어찌되었든 커버리지 지표만으로 테스트가 철저한지는 알 수 없다.

#### 특정 커버리지 숫자를 목표로

앞서 말했듯이 커버리지 지표는 좋은 부정지표이자 나쁜 긍정지표이다. 커버리지가 60% 미만으로 낮다면 문제의 징후이지만 90% \~ 100%라 할 지라도 긍정지표는 아니다. 오히려 너무 높은 지표는 개발자에게 짐을 지우고 프로젝트 완성도에도 도움이 되지 못한다. 70% 정도의 적절한 비율을 목표로 해도 충분하다.

### 성공적인 테스트 스위트의 특성

#### 개발 주기에 통합

테스트는 개발 주기에 통합되어 있어야 한다. 코드에 변경이 생기면 즉각 테스트를 실행해야 한다.

#### 코드베이스에서 중요한 부분이 대상

모든 코드를 동일한 비중으로 테스트할 필요가 없다. 시스템의 도메인 영역에 가까운 비즈니스 로직 부분은 철저한 검증이 필요하다. 나머지 부분은 간략하게 검증해도 좋다.

#### 최소의 유지비로 최대의 가치를 끌어냄

좋은 단위테스트는 최소한의 유지비를 들여 최대의 가치를 얻는 테스트이다. 이를 위해서는 가치있는 테스트와 그렇지 못한 테스트를 식별할 수 있어야 하며 무엇보다 가치있는 테스트를 작성할 수 있어야 한다.
