Skip to content

Commit

Permalink
docs: 테스트 정리 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
jmxx219 committed May 1, 2024
1 parent d6036f5 commit 06d9b39
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 226 deletions.
71 changes: 0 additions & 71 deletions Testing/2. 단위 테스트.md

This file was deleted.

243 changes: 243 additions & 0 deletions Testing/2. 단위 테스트와 TDD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
## 2. 단위 테스트와 TDD

<br>

### 목차
- [단위 테스트(Unit Test)](#단위-테스트(Unit-Test))
- [TDD(Test Driven Development)](#TDD(Test-Driven-Development))
- [테스트는 [문서]다.](#테스트는-[문서]다.)

<br>
<br>

## [단위 테스트(Unit Test)](#목차)

> 자동화된 테스트 기법
> - 단위 테스트
> - 통합 테스트
> - 인수 테스트

### 단위 테스트
- **작은** 코드 단위를 **독립적**으로 검증하는 테스트
- 작은 코드 단위: 클래스 or 메서드 단위
- 외부에 의존하지 않기 때문에 검증 속도가 빠르고 안정적이다.

<br/>

#### JUnit5
- XUnit: 단위 테스트를 위한 테스트 프레임워크
- Java에서는 `JUnit`, .Net에서는 Nunit 등등

<br/>

#### AssertJ

- 테스트 코드 작성을 원활하게 돕는 테스트 라이브러리
- 풍부한 API 및 메서드 체이닝 지원



<br>

### 테스트 케이스 세분화하기

- `한 종류의 음료 여러 잔을 한 번에 담는 기능` 요구 사항 추가 되었을 때 항상 자신에게 혹은 기획자 또는 디자이너에게 질문을 할 수 있어야 한다.
- 질문하기: 이 요구사항이 실제 내가 구현할 때 그 요구사항과 정획히 맞아떨어지는가? 암무적이거나 아직 드러나지 않은 요구사항이 있는가?
- 위의 요구사항은 해피케이스에 대한 내용만 존재하고, 예외 케이스에 대한 요구사항은 나와있지 않다(암묵적이다).
- 테스트 케이스를 세분화하고, 경계값이 존재하면 **경계값**에서 테스트를 하는 것이 중요하다.
- `해피 케이스(요구사항을 그대로 만족하는 테스트 케이스)`, `예외 케이스(그 외 케이스)`
- 이런 케이스들을 테스트할 때는 `경계값 테스트`가 중요하다.
- 경계값 테스트: 범위(이상, 이하, 초과, 미만), 구간, 날짜 등

<br>

### 테스트하기 어려운 영역을 구분하고 분리하기

- `가게 운영 시간(10:00 ~ 22:00)외에는 주문을 생성할 수 없다`가 요구 사항에 추가 되었을 때
- 주문을 생성하는 함수 안에서 현재 시간을 받아온다면, 해당 기능에 대한 테스트 코드는 테스트를 수행하는 시간에 따라 결과가 다르게 나온다.
- `현재 시간`이 테스트를 어렵게 만드는 요소로 파악된다면, 현재 시간을 파라미터로 받아오도록 수정해보자
- 파라미터에 테스트하고자 하는 시간(가게 오픈 시간의 경계값)을 넣음으로써 언제든지 원하는 테스트를 수행할 수 있다.
- 결국 테스트하기 어려운 영역을 외부로 분리(파라미터로 받기)하자
- 외부로 분리할수록 테스트 가능한 코드는 많아진다.

> 테스트하고자 하는 것은 **어떤 시간이 주어졌을 때, 시간 범위 안에 이 시간이 들어오는 것이 중요한 것**이지 현재 시간이 중요한 것은 아니다. 따라서 테스트 코드 상에서 원하는 값을 넣어줄 수 있도록 설계를 변경하는 것이 중요하다.
<br>

#### 테스트하기 어려운 영역

- 관측할 때마다 다른 값에 의존하는 코드 -> input 역할
- 현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등
- 외부 세계에 영향을 주는 코드 -> output 역할
- 표준 출력, 메시지 발송, 데이터베이스에 기록하기 등

<br>

#### 테스트하기 쉬운 영역(순수함수)

- 같은 입력에는 항상 같은 결과
- 외부 세상과 단절된 형태
- 테스트하기 쉬운 코드


<br>
<br>

## [TDD(Test Driven Development)](#목차)

> [TDD 참고](https://github.com/jmxx219/CS-Study/blob/main/etc/TDD.md)
<br>

- 프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론
- 지금까지는 프로덕션 코드를 먼저 만들고 테스트 코드를 작성해왔는데 이 순서를 변경하는 것
- TDD 개발 주기
- `RED` : 실패하는 테스트 작성
- 아직 구현부(프로덕션 코드)가 없기 떄문에 실패함
- `GREEN` : 테스트를 통과하기 위한 최소한의 코딩
- 빠른 시일 내에 구현부를 작성하여 통과하기
- `REFACTOR` : 구현 코드 개선. 테스트 통과 유지
- 구현 코드 개선 및 테스트 통과 유지

<br>

### TDD로 테스트 만들기

1. 테스트 코드를 먼저 작성한다.
```Java
class CafeKioskTest{
...
@Test
void calclulateTotalPrice(){
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
Latte latte = new Latte();

cafeKiosk.add(americano);
cafeKiosk.add(latte);

int totalPrice = cafeKiosk.calculateTotalPrice();

assertThat(totalPrice).isEqualTo(8500);
}
}
```
2. 메서드가 컴파일 되도록 최소한의 코드만 작성한다.
```Java
public class CafeKiosk{
// 1단계: 최소한의 컴파일이 되도록 최소한의 코드 작성
public int caculateTotalPrice(){
return 0;
}
}
```
3. 빨간불이 뜬다. → `RED` 상태
4. 빠른 시간 내에 초록불이 뜨게 만든다. → `GREEN` 상태
```Java
public class CafeKiosk{
// 2단계: 빠른 시간 안에 초록불이 뜨게 만들기
public int caculateTotalPrice(){
return 8500;
}
}
```
5. 리팩토링을 한다. → `REFACTOR` 하기
```Java
public class CafeKiosk{
// 3단계: 리팩토링 하기
public int caculateTotalPrice(){
int totalPrice = 0;
for(Beverage beverage : beverage)
totalPrice += beverage.getPrice();
return totalPrice;
}
}
```
- 구현분를 완전히 개편하여도 테스트를 통과한다. → 과감한 리팩토링 가능
```Java
public class CafeKiosk{
public int caculateTotalPrice() {
return beverages.stream().mapToInt(b -> b.getPrice()).sum();
}
}
```


<br/>

#### 선 기능 구현 후, 테스트 작성의 단점
- 테스트 자체의 누락 가능성
- 특정 테스트 케이스(해피 케이스)만 검증할 가능성
- 잘못된 구현을 다소 늦게 발견할 가능성


<br/>

#### 선 테스트 작성 후, 기능 구현의 장점

- 복잡도가 낮은, 테스트 가능한 코드로 구현할 수 있게 한다.
- 테스트를 먼저 작성하면, 테스트하기 위한 구조를 고민하게 되어 테스트하기 어려운 영역을 미리 분리할 수 있다.
- 쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해준다.
- 구현에 대한 빠른 피드백을 받을 수 있다.
- 과감한 리팩토링이 가능해진다.


> TDD는 클라이언트 관점에서 우리의 프로덕션 코드를 피드백해주는 Test Driven
> `RED` → `GREEN` → `REFACTOR`를 이용하여 TDD를 구현한다.


<br>
<br>

## [테스트는 [문서]다.](#목차)


### 문서

- 프로덕션 기능을 설명하는 테스트 코드 문서
- 다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완
- 어느 한 사람이 과거에 경험했던 고민의 결과물을 팀 차원으로 승격시켜서, 모두의 자산으로 공유할 수 있다.
- 테스트 코드가 팀 자산으로 공유할 수 있다는 장점이 존재한다.

> 우리는 항상 팀으로 일하기 때문에 내가 작성한 코드, 테스트를 다른 팀원이 이해할 수 있도록 문서로 잘 정리해야 한다.

<br>

### DisplayName은 섬세하게

> 테스트 함수가 하는 일을 더 상세하게 알려줄 수 있다.


- 명사의 나열보다 문장으로 작성하기
- `~ 테스트`로 끝나는 설명은 지양하자
- 테스트 **행위**에 대한 **결과**까지 기술하기
- 도메인 용어를 사용하여 한 층 추상화된 내용을 담기
- 메서드 자체의 관점보다 도메인 정책 관점으로 작성하기
- ex) `우리 카페 키오스크의 영업 시작 시간`이라는 도메인 용어를 사용하기
- 테스트의 현상을 중점으로 기술하지 말 것

<br>

### BDD(Behavior Driven Development) 스타일로 작성하기

- TDD에서 파생된 개발 방법
- 함수 단위의 테스트에 집중하기보다 **시나리오에 기반한 테스트케이스(TC)** 자체에 집중하여 테스트한다.
- 개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 추상화 수준(레벨)을 권장한다.

<br>

#### Given / When / Then

- Given : 시나리오 진행에 필요한 모든 준비 과정(객체, 값, 상태 등)
- When: 시나리오 행동 진행
- Then: 시나리오 진행에 대한 결과 명시, 검증

> 어떤 환경에서(Given) 어떤 행동을 진행했을 때(When), 어떤 상태 변화가 일어난다(Then).
> 이렇게 정리해두면 DisplayName을 더 명확하게 작성할 수 있다.

<br>

> `settings → gradle → Run tests using : Intellij IDEA`를 통해 테스트 코드를 실행하면 실행명이 `@DisplayName`으로 나온다.


Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Spring & JPA 기반 테스트
## 3. Spring & JPA 기반 테스트

### 레이어드 아키텍쳐(Layered Architecture)

Expand Down
Loading

0 comments on commit 06d9b39

Please sign in to comment.