적합한 테스트 가이드
레이어(layer)별로 사용하기 적합한 방식의 테스트 가이드에 대해 알아보겠습니다.
그 중에 '리포지토리 객체'를 테스트하는 방법을 알아보겠습니다.
리포지토리 객체의 테스트
- 리포지토리는 개발자가 구현하는 레이어 중에서 가장 데이터베이스와 가깝다
- JpaRepository를 상속받아 기본적인 쿼리 메서드를 사용할 수 있다.
📌 리포지토리 객체의 테스트 코드 작성 시 고려할 사항
- findById(), save() 같은 기본 메서드에 대한 테스트는 큰 의미가 없다.
- 리포지토리의 기본 메서드는 테스트 검증을 마치고 제공된 것이기 때문이다.
- 데이터베이스의 연동 여부는 테스트 시에 고려해볼 사항이다.
- 데이터베이스를 연동한 테스트는 테스트 데이터를 제거하는 코드까지 작성하는 것이 좋다.
- 테스트 데이터의 적재를 신경 써야하는 테스트 환경이라면 잘못된 테스트 코드가 실행되면서 발생할 수 있는 사이드 이펙트를 고려해서 데이터베이스 연동 없이 테스트하는 편이 좋을 수도 있다.
H2 Database 의존성 추가
Maven(pom.xml)
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
gradle(build.gradle)
dependencies {
runtimeOnly 'com.h2database:h2'
}
H2 DB를 사용한 테스트 코드
데이터베이스 저장 테스트
@DataJpaTest
class ProductRepositoryTest {
@Autowired
private ProductRepository productRepository;
@Test
void save() {
//given
Product product = Product.builder()
.name("pen").
price(1000).
stock(1000).
build();
//when
Product savedProduct = this.productRepository.save(product);
//then
assertEquals(product.getName(), savedProduct.getName());
assertEquals(product.getPrice(), savedProduct.getPrice());
assertEquals(product.getStock(), savedProduct.getStock());
}
}
@DataJpaTest
- JPA와 관련된 설정만 로드해서 테스트를 진행한다.
- 기본적으로 @Transactional 어노테이션을 포함하고 있어 테스트 코드가 종료되면 자동으로 데이터베이스의 롤백이 진행된다.
- 기본값으로 임베디드 데이터베이스를 사용한다.
- 다른 데이터베이스를 사용하려면 별도의 설정을 거쳐 사용 가능하다.
Given-When-Then 패턴
- Given 구문 : 테스트에서 사용할 Product 엔티티를 만든다.
- When 구문 : 생성된 엔티티를 기반으로 save() 메서드를 호출해서 테스트를 진행한다.
- Then 구문 : 정상적인 테스트가 이뤄졌는지 체크하기 위해 save() 메서드의 리턴객체와 given() 에서 생성된 엔티티 객체의 값이 일치하는지 assertEquals() 메서드를 통해 검증한다.
데이터베이스 조회 테스트
@DataJpaTest
class ProductRepositoryTest {
@Autowired
private ProductRepository productRepository;
@Test
void selectTest() {
//given
Product product = Product.builder()
.name("pen")
.price(1000)
.stock(1000)
.build();
Product savedProduct = this.productRepository.saveAndFlush(product);
//when
Product foundProduct
= this.productRepository.findById(savedProduct.getNumber()).get();
//then
assertEquals(product.getName(), savedProduct.getName());
assertEquals(product.getPrice(), savedProduct.getPrice());
assertEquals(product.getStock(), savedProduct.getStock());
}
}
- 데이터베이스 조회 테스트를 위해서는 먼저 데이터베이스에 테스트 데이터를 추가해야한다.
- Given 절에 객체를 데이터베이스에 저장하는 작업을 수행한다.
- 조회 메서드를 호출해서 테스트를 진행하고 이후 코드에서 데이터를 비교하며 검증을 수행한다.
테스트 데이터베이스 변경을 위한 어노테이션 추가
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ProductRepositoryTest {
@Autowired
private ProductRepository productRepository;
@Test
void save() {
//given
Product product = Product.builder()
.name("pen")
.price(1000)
.stock(1000)
.build();
//when
Product savedProduct = this.productRepository.save(product);
//then
assertEquals(product.getName(), savedProduct.getName());
assertEquals(product.getPrice(), savedProduct.getPrice());
assertEquals(product.getStock(), savedProduct.getStock());
}
}
- 기존에 사용하던 데이터베이스(MariaDB)에서 테스트하기 위해선 별도의 설정이 필요하다
- @AutoConfigureTestDatabase 어노테이션의 값을 조정하는 작업을 수행한다.
- replace의 속성의 기본값은 Replace.ANY 이고, 이 경우 임베디드 메모리 데이터베이스를 사용한다.
- Replace.NONE 으로 변경 하면 애플리케이션에서 실제로 사용하는 데이터베이스로 테스트가 가능하다.
@SpringBootTest 어노테이션을 활용한 테스
@SpringBootTest
public class ProductRepositoryTest2 {
@Autowired
ProductRepository productRepository;
@Test
void basicCRUDTest() {
/* create */
//given
Product product = Product.builder()
.name("pen")
.price(1000)
.stock(1000)
.build();
//when
Product savedProduct = this.productRepository.save(product);
//then
Assertions.assertThat(savedProduct.getNumber())
.isEqualTo(product.getNumber());
Assertions.assertThat(savedProduct.getName())
.isEqualTo(product.getName());
Assertions.assertThat(savedProduct.getPrice())
.isEqualTo(product.getPrice());
Assertions.assertThat(savedProduct.getStock())
.isEqualTo(product.getStock());
/* read */
//when
Product selectedProduct
= this.productRepository.findById(savedProduct.getNumber()).get();
//then
Assertions.assertThat(savedProduct.getNumber())
.isEqualTo(product.getNumber());
Assertions.assertThat(savedProduct.getName())
.isEqualTo(product.getName());
Assertions.assertThat(savedProduct.getPrice())
.isEqualTo(product.getPrice());
Assertions.assertThat(savedProduct.getStock())
.isEqualTo(product.getStock());
/* update */
//when
Product foundProduct
= this.productRepository.findById(selectedProduct.getNumber())
.orElseThrow(RuntimeException::new);
foundProduct.setName("장난감");
Product updatedProduct = this.productRepository.save(foundProduct);
//then
assertEquals(updatedProduct.getName(), "장난감");
/* delete */
//when
this.productRepository.delete(updatedProduct);
//then
assertFalse(this.productRepository
.findById(selectedProduct.getNumber()).isPresent());
}
}
- 해당 예제는 CRUD의 모든 기능을 한 테스트 코드에 작성한 것이다.
- 기본 메서드를 테스트 하기 때문에, Given 구문을 한 번만 사용하여 전체 테스트에 활용한 것이다.
- @SpringBootTest 어노테이션을 활용하면 스프링의 모든 설정을 가져오고, 빈 객체도 전체를 스캔하기 때문에 의존성 주입에 대해 고민할 필요 없이 테스트가 가능하다.
- 다만 테스트의 속도가 느리므로, 다른 방법으로 테스트 할 수 있다면 대안을 고려해보는 것이 좋다.
반응형
'TEST > JUnit' 카테고리의 다른 글
[Mockito] @WebMvcTest 401/403 에러 처리하기 (0) | 2023.12.20 |
---|---|
단위 테스트 방법 [part 2 : 서비스(Service)] (0) | 2023.11.08 |
단위 테스트 방법 [part 1 : 컨트롤러(Controller)] (0) | 2023.11.08 |
댓글