0. 이 글을 작성하는 이유
엔티티 테스트 하는 방법에 대해 이게 맞는지는 잘 모르겠으나 내가 하고 있는 방식을 공유하고 정리하기 위함
1. 테스트 프레임워크 및 개발 환경
IntelliJ + spring boot 3.x 버전을 사용하면서 같이 들어있는 junit5를 사용
2. 엔티티의 구조
@Entity
@Getter
public class Bean {
@Id
@Column(name = "bean_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Long price;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "roasters_id")
private Roasters roasters;
private Integer size;
private Long likes;
private String cupnoteList;
private String description;
private String descriptionImageURL;
public Bean(String name, Integer size, Long price, Roasters roasters, String cupnoteList, String description, String descriptionImageURL) {
Assert.hasText(cupnoteList, "컵노트는 반드시 채워야합니다.");
this.name = name;
this.size = size;
this.price = price;
this.roasters = roasters;
this.roasters.addBean(this);
this.likes = 0L;
this.cupnoteList = "#" + cupnoteList;
this.description = description;
this.descriptionImageURL = descriptionImageURL;
}
protected Bean() {
}
// 이하 로직 생략
}
우선 컵노트 리스트를 반드시 채워야 하고 공란으로 들어올 시 에러가 발생하게 되어있다.
Assert문이 이럴 땐 참 좋은 것 같다. IllegalArgumentException을 뱉어준다.
/**
* Assert that the given String contains valid text content; that is, it must not
* be {@code null} and must contain at least one non-whitespace character.
* <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
* @param text the String to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the text does not contain valid text content
* @see StringUtils#hasText
*/
public static void hasText(@Nullable String text, String message) {
if (!StringUtils.hasText(text)) {
throw new IllegalArgumentException(message);
}
}
3. 테스트
테스트의 기본은 given - when - then 순서로 이루어진다.
given에서는 변수를 비롯해 사전에 준비되어야 하는 환경을 구축하는 단계이다.
//given
String name = "케냐&에티오피아";
Roasters roasters = new Roasters("테스트_로스터스");
Long price = 40000L;
String cupnoteList = "호두";
when에서는 실제 수행이 이루어진다. 여기서는 Bean에 대한 실제 엔티티 객체 생성이 되는지 확인을 할 것이므로 간단히 넣어주면 될 것이다.
// when
Bean bean = new Bean(name, 200, price, roasters, cupnoteList, "", "");
then에서는 이 결과에 따른 판정을 진행한다.
이 예시에서는 우리가 만든 객체가 정상적으로 만들어지는지 확인을 진행한다.
// then
Assertions.assertNotNull(bean);
그러면 이제 Repository를 곁들여보자.
JpaRepository를 사용해 저장하고 저장한 객체를 꺼내서 비교하는 테스트를 진행한다.
BeanRepository.java
@Transactional
public void save(Bean bean) {
beanJpaRepository.save(bean);
}
public Bean findBean(Long id) {
return beanJpaRepository.findById(id).orElseThrow(() -> new CustomException(ExceptionType.BEAN_NOT_FOUND));
}
@DisplayName("Bean 레포지토리 테스트")
@SpringBootTest
class BeanRepositoryTest {
보면 @SpringBootTest 어노테이션을 붙여두었다. 현재 진행에서 @SpringBootTest 어노테이션의 역할은 component scan의 목적이다.
이후 우리가 위에서 만든 BeanRepository에 대한 Bean주입을 진행한다.
@DisplayName("Bean 레포지토리 테스트")
@SpringBootTest
class BeanRepositoryTest {
@Autowired
private BeanRepository beanRepository;
이때 약간의 지식을 듣고 오면 질문을 할 수 있다.
@MockBean 사용은 왜 안 해요?
여기서는 할 필요가 없다. Mock이라는 건 말 그대로 모의, 가짜라는 뜻이다. @MockBean 어노테이션을 붙이면 가짜 객체를 만들어 진행을 하기 때문에 여기서는 정말 정상적인 Bean이 필요한 상황인데 테스트가 진행이 안된다.
이 글에서는 한 단계 더 편하게 사용하는 기법을 알려주고 바로 사용하도록 하겠다.
이렇게 되면 우리는 given 단계에서 new Bean()을 통해 하나하나 다 만들어주어야 한다. 근데 1개만 만드는 게 아니라 2개, 여러 개 만들고 테스트할 때마다 약간 달라지거나 계속 주입을 해야 한다면?
우리는 Fixture 패턴을 사용하면 된다.
Fixture라는 말을 그대로 직역하면 고정된 것이라는 뜻이다. 미리 이 Bean들에 대해 고정해 두고 꺼내서 사용하기만 하면 된다는 것이다.
물론 이때 전제조건은 위에서 Bean에 대한 객체 생성이 정상적으로 이루어지는 테스트가 끝난 후 진행되어야 한다.
public class BeanFixtures {
public static Bean createBean(String name, Roasters roasters) {
return new Bean(name, 200, 14500L, roasters, "#호두#라즈베리", "아무튼 존맛탱입니다.", "aws.amuURL.link");
}
public static Bean createBean(){
return new Bean("기본이름", 200, 14500L, new Roasters("그 로스터스"), "#호두#라즈베리", "아무튼 존맛탱입니다.", "aws.amuURL.link");
}
public static List<Bean> createBeanList(){
List<Bean> beanList = new ArrayList<>();
beanList.add(createBean());
beanList.add(createBean("전쟁결코전쟁", new Roasters("한국어딘가")));
beanList.add(createBean("원두이름", new Roasters("그 원두")));
return beanList;
}
}
@DisplayName("Bean 레포지토리 테스트")
@SpringBootTest
class BeanRepositoryTest {
@Autowired
private BeanRepository beanRepository;
private Bean bean1 = BeanFixtures.createBean();
private Bean bean2 = BeanFixtures.createBean("테스트원두", new Roasters("아이딜"));
private List<Bean> beanList = BeanFixtures.createBeanList();
이제 given단계에서 하나하나 잡아줘야 하는 부분이 편해졌다. 실제 테스트를 진행해 보도록 하자.
@Test
public void 단건_저장_테스트() throws Exception{
// when
beanRepository.save(bean1);
Bean findBean = beanRepository.findBean(bean1.getId());
// then
Assertions.assertEquals(bean1.getName(), findBean.getName());
}
이제 왜 위에서 @DisplayName을 지정해 줬는지 알 수 있다. 테스트 종류가 많아지는 경우 각 테스트의 카테고리를 구분하기 위함이다.
4. 마치며
정말 테스트에 대한 최소한 내용만을 가지고 설명을 진행했다. 여기서 개념을 더 확장해 나가면서 TDD에 대한 공부를 계속해서 진행해야겠다.
'JAVA > spring' 카테고리의 다른 글
Spring Cloud Netflix Zuul을 바라보며 (1) | 2023.10.28 |
---|---|
Spring Security - CSRF를 disable?(간단, 요약) (0) | 2023.08.11 |
Spring + discord webhook 연동하기 (2) | 2023.07.13 |
spring + slack webhook 연동하기 (0) | 2023.07.12 |
SpringBoot 3.x 버전에서 junit5 를 사용해서 테스트하는데 왜 RunWith가 먹히지 않고 필요가 없는가? (0) | 2023.07.07 |