티스토리 뷰
지난 글에선 테스트의 종류와 단위 테스트, JUnit 없는 단위 테스트 구현을 살펴보았다.
2022.09.07 - [개발/Spring] - [Spring]단위 테스트(Unit Test)
이번 글에선 JUnit에 대해 알아보고, 이전 글에서 작성한 코드에 JUnit을 적용해 보자.
JUnit
JUnit은 스프링에서 지원하는 많은 테스팅 프레임워크 중 하나로, 1997년 Kent Beck에 의해 태어났다.
2022년 기준으로 가장 많이 사용되는 프레임워크이며 다양한 라이브러리를 지원한다.
Spring Boot Initializr에서 Gradle 기반의 프로젝트를 생성하면 기본적으로 테스트 패키지가 생성되며, JUnit을 포함한
testImplementation 'org.springframework.boot:spring-boot-starter-test'
의존성이 build.gradle 안에 자동으로 추가된다.
따라서 별도의 설정 없이 코드 작성을 시작할 수 있다.
Basic Structure of a JUnit Test
JUnit 테스트의 기본 구조는 굉장히 간단하다.
import org.junit.jupiter.api.Test;
public class JunitDefaultStructure {
@Test
public void test1() {
}
@Test
public void test2() {
}
@Test
public void test3() {
}
}
테스트 하고자 하는 메서드에 @Test 애너테이션을 추가한 뒤 테스트 로직을 작성하면 된다.
Using an Assertion Method
Assertion은 특정 포인트에 대한 boolean 값이며, 테스트의 결과를 참/거짓으로 나타내는 논리적 표현이라고 했었다.
이전 글에선 Assertion 로직을 손으로 직접 입력했지만,
JUnit에선 자체적으로 Assertion을 위한 다양한 라이브러리를 지원한다.
하나씩 살펴보자.
- assertEquals()
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class HelloJUnitTest {
@DisplayName("Hello JUnit Test")
@Test
public void assertionTest() {
String expected = "Hello, JUnit";
String actual = "Hello, JUnit";
assertEquals(expected, actual);
}
}
assertEquals()는 두 대상이 일치하는지 검증하는 메서드이다.
위에 말했던 것처럼 @Test를 추가했고 @DisplayName()을 통해 테스트 결과의 이름을 지정한 것을 볼 수 있다.
해당 케이스를 실행하면 아래와 같은 결과를 얻을 수 있다.
만약 두 값이 다르다면 아래와 같이 테스트가 실패하며, 설명과 함께 테스트가 종료된다.
- assertionNotNull()
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class AssertionNotNullTest {
@DisplayName("AssertionNotNull() Test")
@Test
public void assertNotNullTest() {
String currencyName = getCryptoCurrency("ETH");
assertNotNull(currencyName, "should be not null");
}
private String getCryptoCurrency(String unit) {
return CryptoCurrency.map.get(unit);
}
}
assertionNotNull()은 이름 그대로 대상이 null인지 아닌지 검증한다.
대상이 null이 아닐 경우 통과되며, null일 경우 실패한다.
테스트 대상 객체와 실패 메시지까지 입력한 것을 확인할 수 있다.
실행 결과는 CryptoCurrency 클래스에 ETH가 있기 때문에 성공한다.
CryptoCurrency 클래스는 아래와 같이 생성했다.
import java.util.HashMap;
import java.util.Map;
public class CryptoCurrency {
public static Map<String, String> map = new HashMap<>();
static {
map.put("BTC", "Bitcoin");
map.put("ETH", "Ethereum");
map.put("ADA", "ADA");
map.put("POT", "Polkadot");
}
}
- assertThrows()
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class AssertionExceptionTest {
@DisplayName("throws NullPointerException when map.get()")
@Test
public void assertionThrowExceptionTest() {
assertThrows(NullPointerException.class, () -> getCryptoCurrency("XRP"));
}
private String getCryptoCurrency(String unit) {
return CryptoCurrency.map.get(unit).toUpperCase();
}
}
assertThrows()는 대상 메서드 호출시 특정 Exception이 발생하는지 검증한다.
특정 Exception이 발생하면 통과, 그렇지 않으면 실패하게 된다.
위와 같이 발생이 기대되는 예외 클래스(NullPointerException.class)를 입력한 뒤 테스트 대상 메서드를 호출한다.
참고로 대상 메서드를 호출하는 함수형 인터페이스는 JUnit에서 지원하는 Executable이다.
호출된 getCryptoCurrency()는 매개변수를 찾은 뒤 대문자로 전환하려 하지만 반환 값이 null이기 때문에
NullPointerException이 발생하게 된다.
결과는 위와 같이 성공인 것을 확인할 수 있다.
여기서 만약 NullPointerException.class가 아닌 다른 타입의 예외를 지정하면 어떻게 될까?
정답은 위와 같다.
@BeforeEach, @BeforeAll
@BeforeEach와 @BeforeAll은 테스트에 앞서 초기화 등 필요한 전처리를 할 때 사용된다.
- @BeforeEach
public class BeforeEachTest1 {
@BeforeEach
public void init() {
System.out.println("Pre-processing before each test case");
}
@DisplayName("@BeforeEach Test 1")
@Test
public void beforeEachTest1() {
}
@DisplayName("@BeforeEach Test 2")
@Test
public void beforeEachTest2() {
}
}
@BeforeEach 애너테이션이 추가된 메서드는 각 테스트 케이스 실행 직전에 먼저 실행된다.
실행 로그를 보면 두 번 실행된 것을 확인할 수 있다.
public class BeforeEachTest2 {
public Map<String, String > map;
@BeforeEach
public void init() {
map = new HashMap<>();
map.put("BTC", "Bitcoin");
map.put("ETH", "Ethereum");
map.put("ADA", "ADA");
map.put("POT", "Polkadot");
}
@DisplayName("Test case 1")
@Test
public void beforeEachTest1() {
map.put("XRP", "Ripple");
assertDoesNotThrow(() -> getCryptoCurrency("XRP"));
}
@DisplayName("Test case 2")
@Test
public void beforeEachTest2() {
System.out.println(map);
assertDoesNotThrow(() -> getCryptoCurrency("XRP"));
}
private String getCryptoCurrency(String unit) {
return map.get(unit).toUpperCase();
}
}
그렇다면 위와 같은 코드를 실행 시키면 어떻게 될까?
assertDoesNotThrow() 메서드는 예외가 발생하지 않는다고 기대하는 역할을 한다.
여기선 각 테스트 케이스 직전에 map을 초기화하기 때문에 테스트 1에서 추가한 요소가 테스트 2에선 존재하지 않는다.
따라서 위와 같이 테스트 케이스 2는 실패하게 된다.
콘솔에 출력된 요소 안에 XRP가 없는 것도 확인할 수 있다.
- @BeforeAll
public class BeforeAllTest {
public static Map<String, String > map;
@BeforeAll
public static void initAll() {
map = new HashMap<>();
map.put("BTC", "Bitcoin");
map.put("ETH", "Ethereum");
map.put("ADA", "ADA");
map.put("POT", "Polkadot");
map.put("XRP", "Ripple");
System.out.println("Initialize Crypto Currency Map");
}
@DisplayName("Test case 1")
@Test
public void beforeAllTest1() {
assertDoesNotThrow(() -> getCryptoCurrency("XRP"));
}
@DisplayName("Test case 2")
@Test
public void beforeAllTest2() {
assertDoesNotThrow(() -> getCryptoCurrency("ADA"));
}
private String getCryptoCurrency(String unit) {
return map.get(unit).toUpperCase();
}
}
@BeforeAll은 클래스 레벨에서 테스트 실행시 딱 한 번만 실행되는 초기 작업 애너테이션이다.
@BeforeEach와는 다르게 반드시 static Method에만 붙여야 한다는 특징이 있다.
두 개의 실행 결과 모두 성공이며, 콘솔에 메시지가 한 번만 출력된 것을 확인할 수 있다.
방금 알아본 전처리와 마찬가지로 @AfterBefore, @AfterAll을 이용해 테스트의 후처리를 할 수 있으며,
호출 시점 외에 사용 방법은 동일하다.
Assumption
Assumption은 특정 상황에서만 테스트 케이스를 실행할 수 있도록 도와주는 라이브러리이다.
public class AssumptionTest {
@DisplayName("Assumption Test")
@Test
public void assumptionTest() {
// assumeTrue(System.getProperty("os.name").startsWith("Windows"));
assumeTrue(System.getProperty("os.name").startsWith("Mac"));
System.out.println("execute?");
assertTrue(processOnlyMacTask());
}
private boolean processOnlyMacTask() {
return true;
}
}
테스트 케이스를 실행하는 OS에 따라 실행 여부를 결정하는 코드이다.
나는 macOS 환경을 사용하고 있으므로 아래와 같은 결과를 얻을 수 있다.
A Unit Test with JUnit
지난 글에서 JUnit을 적용하지 않고 헬퍼 클래스에 대한 단위 테스트를 진행했다.
public class StampCalculatorTestWithoutJUnit {
public static void main(String[] args) {
calculateStampCountTest();
}
private static void calculateStampCountTest() {
// given
int nowCount = 5;
int earned = 3;
// when
int actual = StampCalculator.calculateStampCount(nowCount, earned);
int expected = 7;
// then
System.out.println(expected == actual);
}
private static void calculateEarnedStampCountTest() { // 추가
// given
Order order = new Order();
OrderCoffee orderCoffee1 = new OrderCoffee();
orderCoffee1.setQuantity(3);
OrderCoffee orderCoffee2 = new OrderCoffee();
orderCoffee2.setQuantity(5);
order.setOrderCoffees(List.of(orderCoffee1, orderCoffee2));
int expected = orderCoffee1.getQuantity() + orderCoffee2.getQuantity();
// when
int actual = StampCalculator.calculateEarnedStampCount(order);
// then
System.out.println(expected == actual);
}
}
스탬프를 합산하는 메서드와 주문 정보에서 스탬프 개수를 얻어내 합산하는 메서드를 검증하는 로직이다.
위와 같은 로직에 위에서 확인한 JUnit을 적용하면 아래와 같이 간결한 코드를 얻을 수 있다.
public class StampCalculatorTest {
@Test
@DisplayName("Test 1: 스탬프 카운트 계산 단위 테스트")
public void calculateStampCountTest() {
int nowCount = 5;
int earned = 3;
int actual = StampCalculator.calculateStampCount(nowCount, earned);
int expected = nowCount + earned;
assertEquals(expected, actual);
}
@Test
@DisplayName("Test 2: 주문 후 누적 스탬프 카운트 계산 단위 테스트")
public void calculateEarnedStampCountTest(){
Order order = new Order();
OrderCoffee orderCoffee1 = new OrderCoffee();
orderCoffee1.setQuantity(2);
OrderCoffee orderCoffee2 = new OrderCoffee();
orderCoffee2.setQuantity(3);
order.setOrderCoffees(List.of(orderCoffee1, orderCoffee2));
int expected = orderCoffee1.getQuantity() + orderCoffee2.getQuantity();
int actual = StampCalculator.calculateEarnedStampCount(order);
assertEquals(expected, actual);
}
}
성공적으로 테스트를 통과하는 것을 확인할 수 있다.
'Java+Spring > Spring' 카테고리의 다른 글
[Spring]Slice Test - Data Access Layer (2) | 2022.09.09 |
---|---|
[Spring]Slice Test - API Layer (2) | 2022.09.08 |
[Spring]Hamcrest를 적용한 단위 테스트 (2) | 2022.09.08 |
[Spring]단위 테스트(Unit Test) (1) | 2022.09.07 |
[Spring]트랜잭션(Transaction) - @Transactional (0) | 2022.09.06 |
[Spring]트랜잭션(Transaction) (0) | 2022.09.05 |
- Total
- Today
- Yesterday
- 스프링
- 남미
- 유럽
- 동적계획법
- RX100M5
- 면접 준비
- 알고리즘
- 백준
- 야경
- 여행
- 세모
- a6000
- 칼이사
- 리스트
- spring
- Algorithm
- 세계일주
- 지지
- 파이썬
- BOJ
- 기술면접
- 중남미
- 스트림
- 유럽여행
- 자바
- Backjoon
- 맛집
- Python
- java
- 세계여행
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 30 |