TDD 란?
Test Driven Development(테스트 주도 개발, TDD)
테스트를 주도로 개발을 하는 개발 방식으로, TDD 의 단계는 다음과 같다.
1 단계
실패하는 테스트를 만든다.
2 단계
테스트가 성공하도록 프로덕션 코드를 구현한다.
3 단계
프로덕션 코드와 테스트 코드를 리펙토링한다.
그림으로 표현하면 다음과 같다.
TDD 원칙
- 원칙 1 - 실패하는 단위 테스트를 작성할 때까지 프로덕션 코드(production code)를 작성하지 않는다.
- 원칙 2 - 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
- 원칙 3 - 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.
⌨️TDD 해보기
다음 간단한 프로그램을 TDD 방식으로 구현해보자
예제 소개
** 문자열 계산기**
- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환
(예: “” => 0, "1,2" => 3, "1,2,3" => 6, “1,2:3” => 6)- 문자열 계산기에 숫자 이외의 값 또는 음수를 전달하는 경우 RuntimeException 예외를 throw 한다.
0. 기능 구현 리스트
TDD 에 익숙하지 않은 사람이라면 어디서 어떻게 시작해야 할지 막막할 수 있다.
그럴 땐 먼저 기능 구현 리스트를 세심하게 작성해보는 것을 추천한다.
- String 으로 문자열이 입력되면 쉼표( , ) 나 콜론( : ) 기준으로 split 하여 String[] 로 반환하는 기능
- 가지고 있는 원소들의 합을 구하는 기능
- 문자열에 숫자 이외의 값이 있으면 예외처리를 하는 기능
- 문자열에 음수가 있으면 예외처리를 하는 기능
...
개발 환경에 대한 참고사항
- Idle : Intellij
- Build 도구 : Gradle
Test 기능을 사용하기 위해 다음과 같이 dependencies 를 설정해 준다.
dependencies {
testCompile('org.junit.jupiter:junit-jupiter:5.6.0')
testCompile('org.assertj:assertj-core:3.15.0')
}
|
그렇다면 이제 본격적으로 기능 구현 리스트의 가장 위에 있는 split 하는 기능을 TDD 방식으로 구현해 보자.
1. 실패하는 테스트 만들기
만들고자 하는 Split의 기능은 String 이 들어왔을때 콤마( , ) 혹은 콜론( : ) 기준으로 나누어 String[] 으로 반환하는 기능이다.
먼저 콤마 기준으로 나누는 기능을 먼저 만들어보겠다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package domin;
import domain.StringCalculator;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
public class StringCalculatorTest {
@Test
void split() {
String input = "1,2,3";
String[] result = new String[]{"1","2","3"};
assertThat(StringCalculator.split(input)).isEqualTo(result);
}
}
|
이렇게 하니 split 이라는 함수가 StringCalculator 에 없어서 컴파일 에러가 뜨는것을 확인할 수 있다.
컴파일에는 성공하는 테스트를 만들어야 하기 때문에 StringCalculator 에도 split 함수를 만들어 주었다.
1
2
3
4
5
6
7
8
|
package domain;
public class StringCalculator {
public static String[] split(String input) {
return null;
}
}
|
이 상태에서 테스트를 실행하면.... 당연히 테스트에 실패하고 만다.
혹시라도 다음과 같은 에러가 발생한다면 인텔리제이 설정에 들어가서 표시한 부분을 gradle에서 인텔리제이로 변경해주면 된다.
> Task :testClasses
> Task :test FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':test'.
2. 테스트 성공 시키기
이제 실패하는 테스트를 만들었으니 TDD 사이클대로 테스트를 성공하도록 만들고 리펙토링하는 작업을 다음 글에서 해보자!
1
2
3
4
5
6
7
8
9
|
package domain;
public class StringCalculator {
public static String[] split(String input) {
String[] result = input.split(",");
return result;
}
}
|
다음과 같이 프로덕션 코드를 수정해주니 테스트가 정상적으로 통과하였다.
3. 리펙토링
1
2
3
4
5
6
7
8
|
package domain;
public class StringCalculator {
public static String[] split(String input) {
return input.split(",");
}
}
|
다시 실패하는 테스트 만들기
콤마( : ) 기준으로도 split을 해야하기 때문에 다른 케이스를 추가해보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package domin;
import domain.StringCalculator;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
public class StringCalculatorTest {
@Test
void split() {
String input = "1,2,3";
String[] result = new String[]{"1","2","3"};
assertThat(StringCalculator.split(input)).isEqualTo(result);
input = "1,2:3";
assertThat(StringCalculator.split(input)).isEqualTo(result);
}
}
|
위의 코드로 테스트를 실행하면 역시나 또 다시 실패한다.
그럼 프로덕션 코드를 수정한다.
1
2
3
4
5
6
7
|
package domain;
public class StringCalculator {
public static String[] split(String input) {
return input.split(",|:");
}
}
|
4. 다시 실패하는 테스트만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package domin;
import domain.StringCalculator;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
public class StringCalculatorTest {
@DisplayName("',' 나 ':' 기준으로 split")
@Test
void split() {
String input = "1,2,3";
String[] result = new String[]{"1","2","3"};
assertThat(StringCalculator.split(input)).isEqualTo(result);
input = "1,2:3";
assertThat(StringCalculator.split(input)).isEqualTo(result);
}
@DisplayName("입력된 배열의 합 구하기")
@Test
void sum() {
String[] input = new String[]{"1","2","3"};
assertThat(StringCalculator.sum(input)).isEqualTo(6);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
package domain;
public class StringCalculator {
public static String[] split(String input) {
return input.split(",|:");
}
public static int sum(String[] numbers) {
return 0;
}
}
|
테스트 코드도 틈틈이 리펙토링 해주어야 한다.
다시 테스트가 통과하도록 프로덕션 코드 수정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package domain;
public class StringCalculator {
public static String[] split(String input) {
return input.split(",|:");
}
public static int sum(String[] numbers) {
int result = 0;
for (String number : numbers) {
result += Integer.parseInt(number);
}
return result;
}
}
|
5. 반복
위와 같은 사이클을 반복하며 메소드를 하나씩 구현해 나가면 된다.
🧐TDD의 효과
- 설계적 측면
- 메서드의 input, output이 정해져야 테스트가 가능하므로 개발자로 하여금 구체적인 설계를 생각하도록 한다.
- 테스트 자체가 어려울때 설계를 재검토하고 코드를 더 단순하게 리펙토링하게 만드는 효과가 있다.
- 빠른 피드백을 얻을 수 있다. (ex. 버그를 일찍 발견할 수 있다.)
- 테스트 코드 작성 능력이 향상 된다.
- 테스트 코드 자체가 해당 API 에 대한 문서화가 된다.
- 회귀 테스트 역할을 해준다.
'기타' 카테고리의 다른 글
AWS Beanstalk로 spring프로젝트 배포 정리 (0) | 2023.06.24 |
---|---|
트래픽을 상승을 위한 구글 SEO 완전 정복 (1) | 2021.02.09 |
얕고 넓은 데이터베이스 지식 (0) | 2021.01.24 |
jenkins 설치 시 플러그인 설치오류 (Feat. docker) (0) | 2020.07.23 |