테스트와 TDD(Test Driven Development)
Test란?테스트란 개발된 코드가 기대한 대로 동작하는지 검증하는 일련의 과정이다.테스트를 통해 버그를 사전에 방지하고, 코드의 신뢰성과 유지보수성을 높일 수 있다.테스트에는 유닛 테스트
constant1601.tistory.com
지난번에는 테스트란 무엇인지 그리고 TDD는 무엇인지에 대해 알아봤다.
이번에는 JUnit, AssertJ, Mockito가 각각 무엇이고 이걸 사용해서 어떻게 테스트
코드를 작성하는지 알아보자.
JUnit과 AssertJ
JUnit은 자바 애플리케이션의 단위 테스트를 작성하고 실행하기 위한 대표적인 테스트
프레임 워크이다. @DisplayName, @BeforeEach, @AfterEach 등의 어노테이션을 통해
테스트 실행 흐름을 제어할 수 있고, assertEquals, assertTrue 등의 메서드를 사용하여 결과를
검증할 수 있다.
하지만 JUnit만으로는 다양한 조건을 표현하거나, 복잡한 객체나 컬렉션을 검증할 때 가독성이 떨어지고
불편한 점이 있다. 이런 불편함을 해결하기 위해 사용하는 것이 AssertJ다.
AssertJ란?
AssertJ는 JUnit과 함께 사용하는 검증 라이브러리로, 테스트 결과를 더 읽기 쉽게, 더 편하게 검증할
수 있도록 도와준다. 마치 자연어처럼 테스트를 작성할 수 있기 때문에 가독성과 유지보수성이 크게 향상
된다.
왜 AssertJ를 쓸까?
JUnit에서 기본으로 제공하는 'org.junit.jupiter.api.Assertions' 도 있는데 굳이
AssertJ를 쓰는 이유는 기본 Assertions보다 AssertJ를 사용하는 게 여러모로 편리하기 때문이다.
JUnit의 기본 Assertions는 간단한 검증에서는 유용해도 복잡한 조건이나, 컬렉션, 문자열 패턴,
예외 메시지 검증 등에서는 표현력이 부족하다.
// JUnit의 기본 Assertions
assertEquals(a, b);
// AssertJ 사용
assertThat(a).isEqualTo(b);
a와 b가 같은지 비교하는 아주 간단한 테스트 코드에서도 보면 알 수 있듯
기본 Assertions는 2개의 값 중 어느 것이 실제값이고, 어느 것이 예상 값인지 유추하기
어렵다. 하지만 AssertJ를 이용한 코드를 보면 실제값인 a가 예상값인 b와 동일한지를
확인하는 코드라는 것을 자연스럽게 이해할 수 있다.
이 외에도 다양한 타입을 지원, 테스트 실패 시 어떤 값이 틀렸는지 출력, 메서드 체이닝 가능
등 AssertJ를 쓰는 것이 기본 Assertions를 쓰는 것보다 더 편리하고 가독성도 좋기 때문에
AssertJ를 사용한다.
JUnit5 공식 문서에서도 서드파티 라이브러리인 AssertJ의 사용을 권장하고 있다.
JUnit5와 AssertJ를 사용한 테스트 코드 예제
public class CouponCodeGenerator {
public List<String> generate(final int money) {
if (!isValidMoney(money)) {
throw new RuntimeException("올바른 금액이 아닙니다.");
}
return generateCodes();
}
private boolean isValidMoney(final int money) {
return money == 1000;
}
private List<String> generateCodes() {
Set<String> codes = new HashSet<>();
Random random = new Random();
while (codes.size() < 5) {
String code = IntStream.range(0, 8)
.mapToObj(i -> String.valueOf((char) ('A' + random.nextInt(26))))
.collect(Collectors.joining());
codes.add(code);
}
return new ArrayList<>(codes);
}
}
위와 같이 1000원을 지불하면 대문자 알파벳으로 이루어진 8자리 쿠폰코드를 5개 생성해 주는 클래스가
있다고 가정하고 테스트 코드를 작성해 보자.
given, when, then 패턴
@DisplayName("테스트 이름")
@Test
void exampleTest(){
// given
테스트 하기 위한 준비: 객체 생성, mock 세팅, 변수 선언 등
// when
테스트 대상 메서드를 호출하는 부분
// then
예상한 결과가 나오는지 확인: assertThat() 등 사용
}
테스트 코드를 작성할 때는 일반적으로 given, when, then 패턴을 사용하는데 그 이유는 테스트 목적이 분명
해지고 가독성이 향상되기 때문이다. 이 테스트는 어떤 전제를 가지고 어떤 행위를 했더니 어떤 결과가 나왔다.
이런 식으로 어떤 목적을 가지고 테스트를 했는지가 명확히 보인다. 따라서 테스트 코드를 읽는 사람이 쉽게
구조를 파악하고 이해할 수 있다.
@Test는 JUnit에서 제공하는 애노테이션으로 해당 메서드가 테스트임을
JUnit에게 알려주는 역할을 하고, JUnit이 이 애노테이션을 보고 해당 메서드를 자동으로 실행한다.
@DisplayName 애노테이션은 테스트 설명을 표시할 때 사용한다.
import static org.assertj.core.api.Assertions.*;
class CouponCodeGeneratorTest {
private final CouponCodeGenerator generator = new CouponCodeGenerator();
@Test
@DisplayName("1000원을 지불하면 5개의 쿠폰 코드가 생성된다.")
void generateCouponCodes_success() {
// given
int money = 1000;
// when
List<String> result = generator.generate(money);
// then
assertThat(result)
.hasSize(5)
.allSatisfy(code -> {
assertThat(code).hasSize(8);
assertThat(code).matches("[A-Z]{8}");
});
}
@Test
@DisplayName("금액이 1000원이 아닐 경우 예외가 발생한다.")
void generateCouponCodes_invalidMoney() {
// given
int money = 500;
// when & then
assertThatThrownBy(() -> generator.generate(money))
.isInstanceOf(RuntimeException.class)
.hasMessage("올바른 금액이 아닙니다.");
}
@Test
@DisplayName("쿠폰 코드는 모두 중복되지 않아야 한다.")
void generateCouponCodes_areUnique() {
// given
int money = 1000;
// when
List<String> codes = generator.generate(money);
// then
assertThat(codes.stream().distinct().count()).isEqualTo(codes.size());
}
}
given, when, then 패턴에 맞춰 테스트 코드를 작성해 봤다.
만약 JUnit의 기본 Assertions를 사용했다면 위와 같이 stream을 사용한다거나,
메서드 체이닝을 사용해서 가독성 좋게 테스트 코드를 작성하는 것이 불가능했을 것이다.
AssertJ를 통해 테스트 코드에 필요한 다양한 메서드를 통해 편리하고 가독성 좋은 테스트 코드를
작성할 수 있었다. 위와 같이 테스트를 실행했을 때 전부 통과하면 테스트를 성공적으로 수행한 것이다.
참고
https://assertj.github.io/doc/#overview-what-is-assertj
https://mangkyu.tistory.com/144
'Practice' 카테고리의 다른 글
HTTPS 적용하기(feat. SSL For Free) (0) | 2025.03.08 |
---|---|
서버 도메인 붙이기 (0) | 2025.03.07 |
AWS에서 백엔드 서버와 데이터 베이스 서버 연결하기 (0) | 2025.03.06 |
프론트엔드 백엔드 서로 다른 서버로 수동 배포하고 연결하기(feat. CORS해결) (0) | 2025.03.05 |
프론트엔드가 포함된 백엔드 프로젝트 수동 배포하기 (0) | 2025.03.05 |