-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
244 additions
and
226 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`으로 나온다. | ||
|
||
|
2 changes: 1 addition & 1 deletion
2
Testing/5. Spring & JPA 기반 테스트.md → Testing/3. Spring & JPA 기반 테스트.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
## Spring & JPA 기반 테스트 | ||
## 3. Spring & JPA 기반 테스트 | ||
|
||
### 레이어드 아키텍쳐(Layered Architecture) | ||
|
||
|
Oops, something went wrong.