JUnit이란?
JUnit은 자바에서 단위 테스트를 작성하고 실행하기 위한 프레임워크이다. Spring Boot에서는 JUnit 5 (JUnit Jupiter)를 사용한다.
먼저 JUnit5에서 자주 사용되는 테스트 검증 라이브러리인 Assertions에 대해서 간단히 알아보고 실제 코드와 함께 테스트 하는 과정을 이어나가보자.
JUnit에서 제공하는 다양한 Assertion 메서드를 사용하여 예상 결과와 실제 결과를 비교할 수 있다.
테스트는 생산성을 높이고 애플리케이션의 품질 보장을 위해선 필수적으로 거쳐야 하는 단계이다.
JUnit의 주요 어노테이션들은 크게
- @Test : 테스트 메서드 정의
- @BeforeEach: 각 테스트 메서드 실행 전에 실행될 메서드를 정의한다.
- @AfterEach: 각 테스트 메서드 실행 후에 실행될 메서드를 정의한다.
- @BeforeAll: 모든 테스트 메서드 실행 전에 한 번만 실행될 메서드를 정의한다.
- @AfterAll: 모든 테스트 메서드 실행 후 한 번만 실행될 메서드를 정의한다.
- @Nested: 중첩 테스트 클래스를 정의하여 관련 테스트를 그룹화한다.
이렇게 있다.
간단하게 코드로 예시 코드를 통해 어떨 때 사용하는지 짚어보고 넘어가보자.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MyServiceTest {
@Test
void testAddition() {
assertEquals(5, 2+3);
}
}
이렇게 @Test를 붙이고 실행하면 2+3의 결과가 5인지 확인하는 단순한 테스트를 거치고 테스트가 성공하면 아무런 오류 없이 통과, 실패하면 JUnit이 실패 이유를 출력한다.
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class MyServiceTest {
@BeforeEach
void setUp() {
// 각 테스트 메서드가 실행되기 전에 호출됨
System.out.println("Set up");
}
@Test
void testMethod1() {
System.out.println("Test method 1");
}
@Test
void testMethod2() {
System.out.println("Test method 2");
}
@AfterEach
void tearDown() {
// 각 테스트 메서드가 실행된 후에 호출됨
System.out.println("Tear down");
}
}
이 코드를 실행하면
Set up
Test method 1
Tear down
Set up
Test method 2
Tear down
이런 결과값이 나온다.
@BeforeEach 메서드 setUp이 호출되어 "Set up"을 출력하고
testMethod1이 실행되어 메서드의 메시지를 출력,
그리고 @AfterEach 메서드 tearDown이 호출되어 "Tear down"을 출력한다.
이 순서가 각 테스트 메서드에 대해 반복하여 수행된다,
유용하게 사용되는 Assertion 메서드
assertEquals(expected, actual)
- 두 값이 동일한지 검증
assertNotEquals(expected, actual)
- 두 값이 동일하지 않은지 검증
assertTrue(condition)
- 조건이 True인지 검증
assertFalse(condition)
- 조건이 false인지 검증
assertNull(object)
- 객체가 null인지 검증
assertNotNull(object)
- 객체가 null이 아닌지 검증
assertThrows(expectedType, executable)
- 특정 예외가 발생하는지 검증
assertThat(actual, matcher)
- 값이 특정 조건을 만족하는지 검
코드로 보는 테스트 사용 예시
앞으로 설명할 코드들은 Spring 컨테이너에서 같은 타입의 여러 빈을 조회할 대 발생할 수 있는 문제를 다루며, 이를 해결하는 방법을 보여주는 JUnit 테스트 코드이다. 단계별로 분석을 해보자.
※ 예시 코드
package hello.core.beanfind;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다")
void findBeanByTypeDuplicate() {
// MemberRepository bean = ac.getBean(MemberRepository.class);
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(MemberRepository.class));
}
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다")
void findBeanByName() {
MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType() {
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for(String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " + beansOfType.get(key));
}
System.out.println("beansOfType = " + beansOfType);
assertThat(beansOfType.size()).isEqualTo(2);
}
@Configuration
static class SameBeanConfig { //내부 클래스 썼으므로 이 안에서만 쓰겠다라는 소리임
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
}
1. AnnotationConfigApplicationContext 컨텍스트 생성
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
- AnnotationConfigApplicationContext는 Spring의 컨테이너이다.
- 애플리케이션의 구성 정보를 제공하는 클래스인 내가 만든 SameBeanConfig를 받아서 컨텍스트를 생성한다.
- SameBeanConfig를 통해서 빈들을 관리한다고 생각하면 된다.
2. findBeanByTypeDuplicate()
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다")
void findBeanByTypeDuplicate() {
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(MemberRepository.class));
}
- 특정 타입으로 조회할 때 동일한 타입의 빈이 여러 개 있으면 오류를 발생하는지 확인하기 위함이다.
- ac.getBean(MemberRepository.class) 메서드를 호출하면 MemberRepository 타입의 빈을 찾으려 하지만 동일한 타입의 빈이 2개 존재하기 때문에 NoUniqueBeanDefinitionException이라는 오류가 발생한다.
- assertThrows()는 지정한 예외인 NoUniqueBeanDefinitionException이 발생하는지 검증한다.
정리하자면 타입으로만 빈을 조회할 때 중복 빈이 있을 경우 예외가 발생하는 것을 보여준다.
3. findBeanByName()
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다")
void findBeanByName() {
MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
- 타입으로 조회할 때 중복된 빈이 있으면 이름을 사용해서 빈을 명확하게 보여줄 수 있음을 보여주기 위함이다.
- ac.getBean("memberRepository1", MemberRepository.class)는 memberRepository1이라는 이름의 빈을 조회한다.
- 이 빈은 SameBeanConfig 클래스의 첫 번째 @Bean 메서드에서 정의한 빈이다.
- 테스트는 이름을 지정하면 중복된 빈 문제를 해결할 수 있음을 보여준다.
4. findAllBeanByType()
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType() {
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " + beansOfType.get(key));
}
System.out.println("beansOfType = " + beansOfType);
assertThat(beansOfType.size()).isEqualTo(2);
}
- 특정 타입에 해당하는 모든 빈을 조회하는 방법을 설명
- ac.getBeansOfType(MemberRepository.class)는 MemberRepository 타입의 모든 빈을 Map<String, MemberRepository> 형식으로 반환한다.
- Map의 키는 빈의 이름이고, 값은 빈 객체이다.
- 출력문을 통해 조회된 모든 빈의 이름과 인스턴스를 확인하고, beansOfType.size()가 2인지 확인하는 것으로 두 개의 MemberRepository 빈이 등록되어 있음을 확인한다.
5. SameBeanConfig
@Configuration
static class SameBeanConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
- 테스트에서 사용할 Spring 빈들을 정의하는 설정 클래스
- @Configuration 어노테이션이 붙은 이 클래스는 Spring이 인식하는 Java 기반 설정 클래스이다.
- @Bean 어노테이션이 붙은 메서드들은 Spring 컨테이너에 의해 관리되는 빈을 생성한다.
- memberRepository1()과 memberRepository2()은 모두 MemoryMemberRepository 타입의 빈을 반환한다.
즉, 동일한 타입의 두 개 빈이 등록되어 있다.
이상으로 Spring에서 타입으로 빈을 조회할 때 발생하는 문제와 해결 방법을 보여주는 예시를 통해 JUnit 테스트 연습을 해보았다.
'Backend > Spring Boot' 카테고리의 다른 글
싱글톤 패턴 (0) | 2024.10.14 |
---|---|
스프링 컨테이너와 스프링 빈 (1) | 2024.10.13 |
코드로 알아보는 생성자 주입(DI) feat. 리팩토링 (5) | 2024.10.05 |
[Spring] 스프링 빈(Bean)이란? (1) | 2024.09.10 |
lombok이란? (3) | 2024.09.03 |