---
tags: test, tdd
---
TDD 세미나 2부 - TDD
===
# 2부. TDD (Test-driven development)
기존의 개발방식은 `코드작성->테스트작성` 순서였다.
하지만 TDD는 반대로 `테스트작성->코드작성` 순으로 진행한다.
주기를 설명하자면 다음과 같다.
- 테스트 작성 (컴파일에러 발생)
- 컴파일 될만한 간단한 개발
- 테스트 실패
- 리팩터링
- 테스트 성공
## 1. TDD 특징
원칙적으로 테스트없이 production 코드는 작성하지 않음.
그래서 하나의 메소드에 3~4개 씩의 검증을 하게 되고, 커버리지가 저절로 높아짐.
또, 테스트 친화적으로 코드가 작성 되게 되는데, 이말은 커뮤니케이션이 잘 되는 메소드라는 뜻.
### TDD 장점
1. 과도한 설계를 하지 않음
> 일단 assertThat 부터 쓰고봐도 됨
2. 양질의 테스트가 작성됨
3. 테스트 하기 쉬운 코드가 생산 됨
> 개발->테스트 방식보다 테스트가 주도하기 때문
### TDD 단점
1. 많은 공수가 필요함. 중간중간 돌려보는 테스트와 리팩토링이 꽤 많음
2. 사전에 알아야할 지식들도 많고, 숙달하는데 시간이 필요함.
3. test suite 가 부족하면 손가락사이로 빠져나가는 버그 발생
### TDD 는 필수는 아니다
개발관점에서의 장점은 테스트 결과물만 보면 *"높은 개발공수를 들여 잘짜여진 테스트코드를 얻는다"* 정도밖에 없다.
하지만 유지보수 관점에서 읽기 쉬운 테스트코드와 tdd를 진행하면서 저절로 생기는 객체지향적 객체들을 얻는 장점이 있다
## 2. TDD 한 사이클 맛보기
TDD로 sum() 을 클래스화 해서 만들어보자. (1부에서 작성한 코드는 모두 지우자)
아무 구현도 없이 아래 테스트를 작성한다.
```java
@Test
public void sum_테스트(){
int sum = MyCalculator.sum(1,0);
assertThat(sum).isEqualTo(1);
}
```
MyCalculator 클래스가 없어 컴파일 에러가 나기 때문에 Test를 돌릴수 없다.
신택스에러만 빠르게 해결하자.
```java
// MyCalculator 생성.
public class MyCalculator {
}
```
> /src/main/java 에 생성해야 하지만 편의를 위해 /src/test/java 에 생성하겠다.
```java
// sum 메소드 작성
public static int sum(int i, int i1) {
return 0; // stub
}
```
중요한것은 신택스 에러만 빨리 해결하는것 이다. 로직 구현은 리팩토링 단계로 미룬다.
return 0; return 999999; 따위로 마무리 하면 된다.
> 이렇게 테스트를 통과하기 위한 최소한의 값을 작성하는것을 `stubing` 이라고 한다.
테스트를 돌려보면 결과는
```
Expected :1
Actual :0
```
> "너는 1을 기대 하겠지만 결과는 0이야"
당연히 실패한다. sum() 을 `return 0;` 으로 IDE가 만들어주는데로 대충 만들었으니까.
가장 빠르게 성공할수 있는 코드를 넣어 리팩터링 해보자.
```java
public static int sum(int i, int i1) {
return 1;
}
```
> return 1; return false; 와 마찬가지로 stub 이라고 할수 있다.
> stub 은 mock 과 함께 테스트에서 자주 사용되는 용어이다.
다시 테스트 해보자. 초록막대가 보이면서 성공한다.
초록막대를 봤으니 이제 리팩토링을 할수 있다.
### 테스트 강화하기
리팩터링 하기에 앞서, 위 테스트는 뭔가 부족한 감이 있다.
> 불길한 느낌을 감지하는것도 숙련이 필요하다.
Test Suite 를 견고하게 하자.
```java
@Test
public void sum_테스트(){
int sum = MyCalculator.sum(1,0);
assertThat(sum).isEqualTo(1);
int sum1 = MyCalculator.sum(0,1);
assertThat(sum1).isEqualTo(1);
int sum2 = MyCalculator.sum(1,1);
assertThat(sum2).isEqualTo(2);
}
```
> 적은 테스트로 많은 의미를 담을수록 좋다.
>처음부터 위 3셋트를 동시에 만들수도 있다.
이제 테스트를 돌려보면.
```
Expected :2
Actual :1
```
> "너는 2를 기대 하겠지만 결과는 1야"
3번째 에서 통과하지 못했다.
작성한 코드가 잘못된 코드인 것이다. 이제 stubing을 걷어내고 확실한 구현을 하자.
```java
// sum 메소드 작성
public static int sum(int a, int b) {
return a+b;
}
```
> 다시 테스트 해보면 성공한다.
### TDD 맛보기 정리
- TDD 주기를 1사이클 수행했다.
- 1사이클을 작은 보폭으로 진행했다.
- 테스트를 돌려보는데 아무런 거리낌이 없었다.
- production코드를 생산하기 전에 반드시 테스트를 먼저 작성 했다.
- return false; return 0; 처럼 점진적으로 나아갔다.
- MyCalculator 클래스, sum() 메소드, 견고한 테스트 3개를 동시에 얻었다.
## 3. TDD - 기능 추가
더하기와 비슷하게 빼기 기능을 수행하는 `sub()` 를 추가 해보자.
```java
@Test
public void sub_테스트(){
int sub = MyCalculator.sub(1,0);
assertThat(sub).isEqualTo(1);
}
```
> 이번에도 당연히 sub() 을 구현하기 전에 테스트부터 작성 해야 한다.
테스트만 있으니 sub() 가 없다는 컴파일 에러가 발생했다.
IDE의 도움을 받아 빠르게 컴파일만 되게끔 만들자.
이번에도 stubing 을 할까? 이번에는 stub 을 쓰지 않겠다.
```java
// MyCalculator.java
public static int sub(int a, int b) {
return a-b;
}
```
1분 이내의 쉬운 구현이면 그냥 그자리에서 해버려도 된다.
(sum 을 테스트 통과하면서 자신감이 생겼기 때문이기도 하다)
하지만 만약 조금이라도 고민이 필요하다면 Todo list 에 할일로 적어놓고 stub 부터 적용하여 작은 단계로 진행하는것을 추천한다.
> 보폭을 줄이고 늘리고는 본인의 선택이다.
> 어려운 일을 할땐 작은 보폭으로 진행하는게 많은 도움이 될 것이다.
> 큰 보폭만 훈련하면 작은보폭을 하고싶어도 할수 없게된다.
테스트 돌려보면 성공한다.
이번에도 아직 테스트 스윗이 부족해 보인다. 아래처럼 견고하게 해보자.
```java
@Test
public void sub_테스트(){
int sub = MyCalculator.sub(1,0);
assertThat(sub).isEqualTo(1);
int sub1 = MyCalculator.sub(1,2);
assertThat(sub1).isEqualTo(-1);
int sub2 = MyCalculator.sub(2,2);
assertThat(sub2).isEqualTo(0);
}
```
구현이 잘 되어 있어서 테스트가 한방에 성공한다.
## 4. TDD - 기능 변경
요구사항이 변해서 어떤 결과던 0~100 을 벗어날수 없다는것이 추가 되었다.
코드부터 바꾸고 싶겠지만 테스트부터 작성하자.
```java
@Test
public void sum의_결과는_0과100사이(){
assertThat(MyCalculator.sum(-1,-2)).isEqualTo(0); // -3 -> 0
assertThat(MyCalculator.sum(50,51)).isEqualTo(100); // 101 -> 100
}
```
테스트 돌려보자.
```
Expected :0
Actual :-3
```
> 방어코드가 아직 없으므로 테스트는 실패한다.
범위밖 값을 범위내 값으로 바꿔주는 메소드가 하나 있으면 좋을것 같다.
메소드를 만들기 전에 테스트를 만들자.
```java
@Test
public void changeValueWithinMinMax_범위밖값을_범위내값으로(){
int within = MyCalculator.getWithinMinMax(-3);
assertThat(within).isEqualTo(0);
int within2 = MyCalculator.getWithinMinMax(101);
assertThat(within2).isEqualTo(100);
}
```
getWithinMinMax 메소드가 없다는 컴파일에러가 난다.
아래와 같이 IDE를 이용해 빠르게 만들자.
```java
public static int changeValueWithinMinMax(int i) {
return 0; // stub
}
```
테스트 돌려보자. 당연히 실패한다. 리팩터링 하러 가자.
```java
// MyCalculator.java
public static int changeValueWithinMinMax(int i) {
int MAXIMUM = 100;
int MINIMUM = 0;
if(i > MAXIMUM){
return MAXIMUM;
} else if (i < MINIMUM) {
return MINIMUM;
} else {
return i;
}
}
```
> 테스트 돌리면? 성공.
이제 아까 실패했던 sum의_결과는_0과100사이 를 getWithinMinMax() 를 이용해서 쉽게 리팩터링 할수 있다.
```java
public static int sum(int a, int b) {
return changeValueWithinMinMax(a+b);
}
```
이번엔 모든 테스트를 돌려서 성공하는지 확인하자.
- sub_테스트
- sum_테스트
- sum의_결과는_0과100사이
- changeValueWithinMinMax_범위밖값을_범위내값으로
> 모두 초록막대인지 확인하자.
MAXIMUM, MINIMUM 는 상수인데 메소드 안에 있는게 좀 아쉽지만 일단 한사이클은 종료했다.
TDD는 `정상 동작하는 아름다운 코드` 에서, 아름다운은 나중이다.
Todo list 에 추가 하고 다음에 리팩터링하면 된다.
- **MAXIMUM, MINIMUM 은 상수이므로 멤버변수쪽으로 옮기기**
코드가 맘에 들지 않으면 언제든 리팩토링을 하면 된다.
초록막대 (테스트 성공) 를 띄우는 테스트 스윗이 있으므로 자신있게 진행하자.
changeValueWithinMinMax 에 대한 테스트가 남아있으므로, 멤버변수쪽으로 옮기는것은 별도의 테스트케이스를 추가할 필요는 없을것 같다. 실습해보자.
## 실습
### 실습1.
sum과 sub를 직접 만들어보자. (가급적 타이핑하자)
> 이번에는 sub도 sum처럼 stubing 과정을 거쳐서 진행할것
### 실습2.
Todo list 를 해결 해보자.
시간이 남으면 Todo list 에 곱하기 나누기를 추가하고 해결해보자.