들어가며.
개발을 하면서 TDD 라는 용어는 한 번쯤은 들어봤을거라 생각하는데요. 이번에 프리코스 2주차를 시작하면서 JUnit 5와 AssertJ 를 이용하여 개발을 진행해보라는 요구 사항을 접하게 되었습니다. 말로는 많이 들어봤지만 개발이 바쁘다보니 실제로 적용해본 적은 없는데요. 이번 기회에 TDD 에 대해 정리하고 JUnit 과 AssertJ 에 익숙해지고자 작성하게 되었습니다.
TDD 에 대한 간략한 소개.
TDD 는 Test Driven Development 의 약자로 켄트백이 1999년 익스트림 프로그래미의 일부로 제안하며 알려졌습니다. 지금은 많은 분들이 익스트림 프로그래밍은 뭔지 몰라도 TDD 는 알 만큼 유명해졌는데요. TDD 는 동작하는 코드를 작성하기 이전에 테스트를 먼저 작성하고, 그 테스트를 통과하는 코드를 작성함으로써 테스트된 동작하는 코드를 얻는 개발 방법입니다.
TDD 는 세 가지 단계를 한 사이클로 돌게 됩니다.
- 테스트를 작성한다. (빨간 막대)
- 실행 가능하게 만든다. (초록 막대)
- 올바르게 만든다.
흔히 red - green - reactoring cycle 이라고 하는데요. 컴파일조차 되지 않는 코드를 대상으로 먼저 테스트를 작성하고, 그 다음 테스트 코드가 컴파일되고 실행까지 되도록 코드를 작성한 후 테스트가 통과되면 테스트의 보호 아래 코드를 다듬는 것입니다.
단위 테스트의 중요성과 특징.
# 중요성
단위 테스트를 작성해야 하는 이유는 정말 많은데 그 중 핵심적인 이유 세 가지가 있다.
- 코드를 수정하거나 기능을 추가할 때 수시로 빠르게 검증 할 수 있다.
- 리팩토링 시에 안정성을 확보할 수 있다.
- 개발 및 테스팅에 대한 시간과 비용을 절감할 수 있다.
# 특징
그렇다고 무작정 테스트를 작성하는 것은 좋지 않다. 좋은 테스트의 특징은 FIRST 라는 5가지 규칙을 따른다.
- Fast (빠르게) : 테스트는 빠르게 동작하여 자주 돌릴 수 있어야 한다.
- independent (독립) : 각각의 테스트는 독립적이며 서로 의존해서는 안 된다.
- Repeatable (반복) : 어느 환경에서도 반복 가능해야 한다.
- Self-Validating (검증) : 테스트는 성공 또는 실패로 boolean 값으로 결과를 내어 자체적으로 검증되어야 한다.
- Timely (적시) : 테스트는 실제 코드를 구현하기 직전에 구현해야 한다.
테스트 주도 개발 이유와 방법.
이유는 위에서 언급한 단위 테스트의 중요성과 비슷하다. TDD 의 궁극적인 목표는 작동하는 깔끔한 코드를 작성하는 것이 그 목표이다. TDD 의 개발 단계에는 리팩토링이 있는데, 이 리팩토링 과정을 거치면서 중복된 코드들이 제거되고 복잡한 코드는 깔끔하게 정리된다. 처음에는 귀찮고 개발이 느리다고 느낄 수 있지만 장기적으로 본다면 개발 비용을 아껴주게 된다. 그리고 그 중에서도 실패 테스트부터 작성해야한다. 즉, 순차적으로 실패하는 테스트를 먼저 작성하고, 오직 테스트가 실패할 경우에만 새로운 코드를 작성해야 한다.
현실적으로 프로덕션 코드를 작성하고 나면 그 뒤에 테스트 코드를 작성하는 과정은 너무나도 귀찮게 느껴질 것이다. 이미 개발이 완료되었기에 심리적으로 꺼려지기도 하며 완성된 코드에서 발생하는 테스트의 경우의 수가 너무나 많기 때문이다.
# 방법
TDD 개발의 방법론의 프로그래밍 순서는 매우 단순하다.
- 실패하는 작은 단위 테스트를 작성한다.
- 빨리 테스트를 통과하기 위한 프로덕션 코드를 작성한다. 이를 위해 가짜 구현 등을 작성할 수도 있다.
- 테스트 코드를 작성한다. 실패 테스트가 없을 경우에만 성공 테스트를 작성한다.
- 새로운 테스트 코드를 통과하기 위해 프로덕션 코드를 추가 / 수정 한다.
- 1 ~ 4 단계를 반복하여 테스트를 케이스를 작성한다.
- 개발된 코드에 대해 리팩토링을 진행한다.
위 방법과 개발에 대한 접근 방법은 망나니개발자 님의 블로그를 참고했다.
# 접근 방법
1) 가짜로 구현하기
실패하는 테스트를 가장 빠르게 구현하는 방법은 아무 상수 값이나 반환하도록 하는 것이다. 그리고 테스트가 통과하면 단계적으로 상수를 변수로 변형한다.
@Test
public void 곱하기테스트() {
// given
// when
final int result = multiply(2, 3);
// then
assertThat(result).isEqualTo(6);
}
해당 테스트를 통과하는 가장 빠른 방법은 6을 반환하는 것이다.
public int multiply(final int num1, final int num2) {
return 6;
}
가짜 구현으로 개발하면 두 가지 효과를 얻을 수 있다.
- 빨간 막대와 초록 막대 상태는 완전 다르기에 어느 위치인지 알고 거기부터 리팩토링해 나갈 수 있다.
- 구체적인 예에서 일반화로 바꾸면서 불필요한 고민으로 혼동되는 일을 예방할 수 있다.
2) 삼각 측량
삼각 측량은 테스트 주도로 추상화된 과정을 일반화하는 과정이다. 삼각 측량 방법은 테스트 예시가 2개 이상일 때에만 추상화를 한다.
@Test
public void 곱하기테스트() {
// given
// when
final int result1 = multiply(2, 3);
final int result2 = multiply(4, 7);
// then
assertThat(result1).isEqualTo(6);
assertThat(result2).isEqualTo(28);
}
3) 명백하게 구현하기
명백하게 구현하는 방법은 가짜 구현이나 삼각 측량 방법을 사용하지 않고 바로 정답을 구현하는 방법이다. 매우 쉬운 구현 단계에서는 이 방법이 시간을 절약할 수 있다.
public int multiply(final int num1, final int num2) {
return num1 * num2;
}
'시리즈 > 소프트웨어 공학' 카테고리의 다른 글
[OOP] 객체 지향 프로그래밍에 대해 알아보자 ① (0) | 2025.01.02 |
---|---|
[OOP] Getter, Setter 를 지양하는 이유에 대해서 ① (0) | 2024.11.04 |