TEST/JUnit

단위 테스트 방법 [part 3 : 리포지토리(Repository)]

블로그 주인장 2023. 11. 8.

적합한 테스트 가이드

레이어(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 패턴

  1. Given 구문 : 테스트에서 사용할 Product 엔티티를 만든다.
  2. When 구문 : 생성된 엔티티를 기반으로 save() 메서드를 호출해서 테스트를 진행한다.
  3. 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 어노테이션을 활용하면 스프링의 모든 설정을 가져오고, 빈 객체도 전체를 스캔하기 때문에 의존성 주입에 대해 고민할 필요 없이 테스트가 가능하다.
  • 다만 테스트의 속도가 느리므로, 다른 방법으로 테스트 할 수 있다면 대안을 고려해보는 것이 좋다.

 

반응형

댓글