인프런 커뮤니티 질문&답변

이찬원님의 프로필 이미지

작성한 질문수

실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발

@Transactional을 사용한 테스트에 대해서 질문드립니다

해결된 질문

작성

·

104

0

안녕하세요 영한님 강의 정말 잘 듣고 있습니다. ㅎㅎ

@Transactional에 대해서 궁금한 점이 생겨서 질문드립니다.

@Test
@Transactional
public void 상품주문() throws Exception {
    //given
    Member member = createMember();

    Book book = createBook("시골 JPA", 10000, 10);

    int orderCount = 2;

    //when
    Long orderId = orderService.order(member.getId(), book.getId(), orderCount);

    //then
    Order getOrder = orderRepository.findOne(orderId);

    assertEquals("상품 주문시 상태는 ORDER", OrderStatus.ORDER, getOrder.getStatus());
    assertEquals("주문한 상품 종류 수가 정확해야 한다.", 1, getOrder.getOrderItems().size());
    assertEquals("주문 가격은 가격 * 수량이다.", 10000 * orderCount, getOrder.getTotalPrice());
    assertEquals("주문 수량만큼 재고가 줄어야 한다.", 8, book.getStockQuantity());
}

해당 코드는 강의에서 작성한 테스트 코드 입니다.

지만 저는 지금까지 @Transactional을 테스트 코드에 작성하지 않는 것이 좋다고 알고 있었습니다. 그 이유는 @Transactional을 깜빡하고 작성하지 않는 문제를 테스트 코드에서 잡아 낼 수 있고, 테스트 트랜잭션의 롤백 정책으로 인해 실제 로직과 다르게 작동하기 때문입니다.

아래는 제가 @Transactional을 제거하고 코드를 작성해 보았습니다.

@Test
public void 상품주문() throws Exception {
    //given
    Member member = createMember();

    Book book = createBook("시골 JPA", 10000, 10);

    int orderCount = 2;

    //when
    Long orderId = orderService.order(member.getId(), book.getId(), orderCount);

    //then
    Order getOrder = orderRepository.findOne(orderId);

    assertEquals("상품 주문시 상태는 ORDER", OrderStatus.ORDER, getOrder.getStatus());
    assertEquals("주문한 상품 종류 수가 정확해야 한다.", 1, getOrder.getOrderItems().size());
    assertEquals("주문 가격은 가격 * 수량이다.", 10000 * orderCount, getOrder.getTotalPrice());

    //Transactional이 없기 때문에 객체를 다시 불러와서 조회
    Item findBook = itemRepository.findOne(book.getId());
    assertEquals("주문 수량만큼 재고가 줄어야 한다.", 8, findBook.getStockQuantity());
}

private Book createBook(String name, int price, int stockQuantity) {
    Book book = new Book();
    book.setName(name);
    book.setPrice(price);
    book.setStockQuantity(stockQuantity);
    itemService.saveItem(book); // service에서 저장
    return book;
}

private Member createMember() {
    Member member = new Member();
    member.setName("회원1");
    member.setAddress(new Address("서울", "강가", "123-123"));
    memberService.join(member); // service에서 저장
    return member;
}

@Repository
public class OrderRepository {

    private final EntityManager em;

    public OrderRepository(EntityManager em) {
        this.em = em;
    }

    // fetch join으로 LazyLoding 문제 해결
    public Order findOne(Long id) {
        return em.createQuery(
                        "select o from Order o join fetch o.orderItems where o.id = :id",
                        Order.class
                )
                .setParameter("id", id)
                .getSingleResult();
    }

위와 같이 코드를 수정해서 @Transactional이 없어도 테스트를 통과하도록 변경하였습니다. 하지만 아래의 코드는 모든 객체를 테스트 작성자가 신경써야 하고 JPA가 관리하는 여러 가지 기능들을 사용하지 못하게 됩니다. 또한 rollback 기능을 사용하지 못해 테스트 작성자가 일일이 DB에 저장된 값을 지워줘야 합니다.

영한님은 이와 같은 상황에서는 어떤 선택이 좋다고 생각하는지 궁금합니다.

답변 2

3

안녕하세요. 이찬원님, 공식 서포터즈 OMG입니다.

테스트에서 @Transactional 사용여부에 대해 작년에 핫한 주제였었죠 😀

아래 첨부한 링크는 영한님과도 친분이 있으시고 인프런의 지식공유자이신 토비님의 답변 중 영한님의 말씀을 인용하신 부분이 있어 첨부하였습니다 😀

영한님께서는 실용적인 관점에서와 주의할점을 염두한 상태에서 사용하는 것을 선호하시는 것 같네요^^

image.png

 

https://www.inflearn.com/community/questions/792383

위에 까지는 테스트에서 @Transactional을 사용하자! 의 주장이였다면

아래 링크는 사용하지 않는 것을 권장하는 인프런의 CTO이신 이동욱님의 의견인 링크입니다.

image.png

 

https://jojoldu.tistory.com/761

사용하자는 의견과 사용하지 말자는 의견을 보시고 본인의 생각을 정립하시면 좋겠네요 😀

감사합니다.

0

안녕하세요, 인프런 AI 인턴입니다.

이찬원님의 질문에 대해, @Transactional을 사용한 테스트 코드와 관련하여 유사한 답변을 찾았습니다. 질문하신 내용은 테스트에 @Transactional을 사용하는 것의 장단점과 관련된 것으로 보입니다.

김영한 강사님은 “영한님 롤백과 관련해서 질문이있습니다.”라는 질문에 대한 답변에서 테스트 코드에서의 @Transactional 사용에 대해 설명하셨습니다. 이 답변에서는 @Transactional 어노테이션을 테스트 코드에 사용할 때 발생할 수 있는 행동(롤백)과, 이 때문에 실제 애플리케이션과는 다르게 동작할 수 있는 부분을 설명하고 있습니다. 테스트가 실행된 후 실제 DB에 데이터가 남지 않도록 롤백이 일어나는 과정과 이를 어떻게 활용할 수 있는지에 대한 설명이 포함되어 있습니다.

해당 답변에서는, 테스트 코드의 실행 완료 후 자동으로 데이터를 롤백하는 것이 테스트 환경에서는 유리할 수 있음을 언급하고 있습니다. 실제 환경과의 차이점을 이해하고 합리적으로 활용하는 것이 중요하다고 설명하고 있습니다.

테스트 코드 작성에 있어서 @Transactional의 사용 여부는 테스트의 목적과 환경에 따라 결정될 수 있으며, 각각의 접근 방식이 가진 장단점을 이해하고 적절히 활용하는 것이 중요합니다. 실제 로직과 똑같이 동작하는 환경을 테스트하고자 한다면, @Transactional 없이 테스트하는 방법을 선택할 수 있습니다. 하지만, 데이터의 일관성과 테스트의 독립성을 유지하기 위해서 @Transactional을 사용하여 롤백하는 방법도 유효한 선택입니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.