Back-End/Spring

프로젝트 중 잘못된 테스트 바로잡기

Meluu_ 2025. 7. 8. 22:53

✔️ 문제


테스트를 하는데 자꾸 이상하게 예측값보다 -1 인 값이 나와서

보니 테스트 코드를 잘 못 짜서 그런 것이였다. 

 

@Transactional과 @BeforeEach를 제대로 사용하지 않았기 때문이였다.

 

 

문제의 코드 

@Slf4j
@SpringBootTest
class FocusTimeJdbcRepositoryTest {

    @Autowired
    FocusTimeJdbcRepository repository;

    @Autowired
    FocusTimeJpaRepository jpaRepository;

    @Autowired
    MemberRepository memberRepository;

    @Autowired
    TransactionTemplate transactionTemplate;

    @PersistenceContext
    EntityManager em;

    @BeforeEach
    void init() {

        log.info("초기화 ");
        Member member = Member.builder().email("abc@aa.cc").nickName(UUID.randomUUID().toString()).socialType(SocialType.KAKAO).socialId("2gjdkl12333").build();
        memberRepository.save(member);

        List<Long> list = new ArrayList<>();
        log.info("중간 초기화 ");

        FocusTime focusTime = jpaRepository.saveFocusTime(member, LocalDate.of(2025, 6, 30), LocalTime.now(), LocalTime.now().plusHours(1)).get();
        FocusTime focusTime1 = jpaRepository.saveFocusTime(member, LocalDate.of(2025, 6, 29), LocalTime.now(), LocalTime.now().plusHours(1)).get();
        FocusTime focusTime2 = jpaRepository.saveFocusTime(member, LocalDate.of(2025, 5, 30), LocalTime.now(), LocalTime.now().plusHours(1)).get();
        FocusTime focusTime3 = jpaRepository.saveFocusTime(member, LocalDate.of(2024, 5, 29), LocalTime.now(), LocalTime.now().plusHours(1)).get();
        FocusTime focusTime4 = jpaRepository.saveFocusTime(member, LocalDate.of(2024, 5, 28), LocalTime.now(), LocalTime.now().plusHours(1)).get();
        log.info("초기화 성공");


    }


    @AfterEach
    void delete() {
        Member member = memberRepository.findBySocialId(SocialType.KAKAO, "2gjdkl12333").get();
        log.info("해당 id 삭제 ={}", member.getId());
        log.info("영속화 ? = {}", em.contains(member));
        memberRepository.deleteMember(member);
        transactionTemplate.execute(status -> {

            return 1;
        });
    }

    @Test
    @Transactional
    @DisplayName("집중 데이터 멤버 ID로 전부 조회 성공 테스트")
    void findAllFocusTime() {

        Member member = memberRepository.findBySocialId(SocialType.KAKAO, "2gjdkl12333").get();
        Member member1 = memberRepository.findMemberById(member.getId()).get();
        log.info("1. 영속화 ? ={}", em.contains(member));
        log.info("2. 영속화 ? ={}", em.contains(member1));

        // given when
        List<FocusTimeResponseDto> allFocusTimes = repository.findAllFocusTimes(member.getId());

        //then
        Assertions.assertThat(allFocusTimes.size()).isEqualTo(5);

    }

    @Test
    @DisplayName("집중 데이터 조회 특정 날짜 테스트")
    void findFocusTime() {

        Member member = memberRepository.findBySocialId(SocialType.KAKAO, "2gjdkl12333").get();
        // given when
        List<FocusTimeResponseDto> focusByYear = repository.findFocusTimesByYearAndMonthAndDay(member.getId(), 2025, null, null);
        List<FocusTimeResponseDto> focusByYearAndMonth = repository.findFocusTimesByYearAndMonthAndDay(member.getId(), 2025, 6, null);
        List<FocusTimeResponseDto> focusByYearAndMonthAndDay = repository.findFocusTimesByYearAndMonthAndDay(member.getId(), 2024, 5, 28);
        List<FocusTimeResponseDto> focus = repository.findFocusTimesByYearAndMonthAndDay(member.getId(), null, null, null);

        //then
        Assertions.assertThat(focusByYear.size()).isEqualTo(3);
        Assertions.assertThat(focusByYearAndMonth.size()).isEqualTo(2);
        Assertions.assertThat(focusByYearAndMonthAndDay.size()).isEqualTo(1);
        Assertions.assertThat(focus.size()).isEqualTo(5);

    }

    @Test
    @DisplayName("집중 데이터 삭제 특정 날짜 테스트")
    void deleteByDate() {

        // given
        Member member = memberRepository.findBySocialId(SocialType.KAKAO, "2gjdkl12333").get();

        //when
//        repository.deleteFocusTimeByYearAndMonthAndDay(member.getId(), 2025, null, null);
        repository.deleteFocusTimeByYearAndMonthAndDay(member.getId(), 2025, 5, 30);
        List<FocusTimeResponseDto> allFocusTimes = repository.findAllFocusTimes(member.getId());


        //then
//        Assertions.assertThat(allFocusTimes.size()).isEqualTo(2);
        Assertions.assertThat(allFocusTimes.size()).isEqualTo(4);

    }

}

 

나눠서 살펴보면

 

@BeforeEach, @AfterEach

    @AfterEach
    void delete() {
        Member member = memberRepository.findBySocialId(SocialType.KAKAO, "2gjdkl12333").get();
        log.info("해당 id 삭제 ={}", member.getId());
        log.info("영속화 ? = {}", em.contains(member));
        memberRepository.deleteMember(member);
        transactionTemplate.execute(status -> {

            return 1;
        });
    }

 

@AfterEach는 사실 필요가없었다.

 

트랜잭션 안에서 실행된다면 @BeforeEach다음 해당 메서드 실행후 

롤백하기때문에 

@BeforeEach에서 저장한 데이터도 알아서 날라간다.

 

 

 

 

@Transactional

    @Test
    @DisplayName("집중 데이터 삭제 특정 날짜 테스트")
    void deleteByDate() {

        // given
        Member member = memberRepository.findBySocialId(SocialType.KAKAO, "2gjdkl12333").get();

        //when
//        repository.deleteFocusTimeByYearAndMonthAndDay(member.getId(), 2025, null, null);
        repository.deleteFocusTimeByYearAndMonthAndDay(member.getId(), 2025, 5, 30);
        List<FocusTimeResponseDto> allFocusTimes = repository.findAllFocusTimes(member.getId());


        //then
//        Assertions.assertThat(allFocusTimes.size()).isEqualTo(2);
        Assertions.assertThat(allFocusTimes.size()).isEqualTo(4);

    }

 

삭제인데 트랜잭션을 안걸어서 롤백되지 않는다.

 

테스트는 순서대로 실행되지 않는다. 때문에 해당 테스트가 먼저 실행될시

삭제가 반영되어 다른 테스트에 영향을 준다. 

 

따라서 해당 테스트는 @Transactional로 감싸 롤백되게 해준다. 

 

 

최종 수정 코드

package zypt.zyptapiserver.repository;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Commit;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import zypt.zyptapiserver.domain.FocusTime;
import zypt.zyptapiserver.domain.Member;
import zypt.zyptapiserver.domain.dto.FocusTimeDto;
import zypt.zyptapiserver.domain.dto.FocusTimeResponseDto;
import zypt.zyptapiserver.domain.enums.SocialType;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Slf4j
@SpringBootTest
class FocusTimeJdbcRepositoryTest {

    @Autowired
    FocusTimeJdbcRepository repository;

    @Autowired
    FocusTimeJpaRepository jpaRepository;

    @Autowired
    MemberRepository memberRepository;

    @Autowired
    TransactionTemplate transactionTemplate;

    @PersistenceContext
    EntityManager em;

    @BeforeEach
    void init() {

        log.info("초기화 ");
        Member member = Member.builder().email("abc@aa.cc").nickName(UUID.randomUUID().toString()).socialType(SocialType.KAKAO).socialId("2gjdkl12333").build();
        memberRepository.save(member);

        log.info("member 영속화 ? = {}", em.contains(member));
        log.info("중간 초기화 ");

        jpaRepository.saveFocusTime(member, LocalDate.of(2025, 6, 30), LocalTime.now(), LocalTime.now().plusHours(1)).get();
        jpaRepository.saveFocusTime(member, LocalDate.of(2025, 6, 29), LocalTime.now(), LocalTime.now().plusHours(1)).get();
        jpaRepository.saveFocusTime(member, LocalDate.of(2025, 5, 30), LocalTime.now(), LocalTime.now().plusHours(1)).get();
        jpaRepository.saveFocusTime(member, LocalDate.of(2024, 5, 29), LocalTime.now(), LocalTime.now().plusHours(1)).get();
        jpaRepository.saveFocusTime(member, LocalDate.of(2024, 5, 28), LocalTime.now(), LocalTime.now().plusHours(1)).get();
        log.info("초기화 성공");

    }

    @Test
    @Transactional
    @DisplayName("집중 데이터 멤버 ID로 전부 조회 성공 테스트")
    void findAllFocusTime() {

        Member member = memberRepository.findBySocialId(SocialType.KAKAO, "2gjdkl12333").get();

        // given when
        List<FocusTimeResponseDto> allFocusTimes = repository.findAllFocusTimes(member.getId());

        //then
        Assertions.assertThat(allFocusTimes.size()).isEqualTo(5);

    }

    @Test
    @Transactional
    @DisplayName("집중 데이터 조회 특정 날짜 테스트")
    void findFocusTime() {

        Member member = memberRepository.findBySocialId(SocialType.KAKAO, "2gjdkl12333").get();
        // given when
        List<FocusTimeResponseDto> focusByYear = repository.findFocusTimesByYearAndMonthAndDay(member.getId(), 2025, null, null);
        List<FocusTimeResponseDto> focusByYearAndMonth = repository.findFocusTimesByYearAndMonthAndDay(member.getId(), 2025, 6, null);
        List<FocusTimeResponseDto> focusByYearAndMonthAndDay = repository.findFocusTimesByYearAndMonthAndDay(member.getId(), 2024, 5, 28);
        List<FocusTimeResponseDto> focus = repository.findFocusTimesByYearAndMonthAndDay(member.getId(), null, null, null);
        for (FocusTimeResponseDto allFocusTime : focusByYear) {
            log.info("ft = {}", allFocusTime.getCreateDate());
        }
        //then
        Assertions.assertThat(focusByYear.size()).isEqualTo(3);
        Assertions.assertThat(focusByYearAndMonth.size()).isEqualTo(2);
        Assertions.assertThat(focusByYearAndMonthAndDay.size()).isEqualTo(1);
        Assertions.assertThat(focus.size()).isEqualTo(5);

    }

    @Test
    @Transactional
    @DisplayName("집중 데이터 삭제 특정 날짜 테스트")
    void deleteByDate() {

        // given
        Member member = memberRepository.findBySocialId(SocialType.KAKAO, "2gjdkl12333").get();

        //when
//        repository.deleteFocusTimeByYearAndMonthAndDay(member.getId(), 2025, null, null);
        repository.deleteFocusTimeByYearAndMonthAndDay(member.getId(), 2025, 5, 30);
        List<FocusTimeResponseDto> allFocusTimes = repository.findAllFocusTimes(member.getId());


        //then
//        Assertions.assertThat(allFocusTimes.size()).isEqualTo(2);
        Assertions.assertThat(allFocusTimes.size()).isEqualTo(4);

    }

}

 

생각 정리

무지성 트랜잭션 시도, @BeforeEach, @AfterEach의 특성을 생각하지 않은 점이 테스트 실패의 원인이였다.

테스트에서 트랜잭션의 원리를 다시 생각하고 (끝나면 롤백)

트랜잭션 전파의 영향을 다시 생각하게 되는 계기가 되었다.

 

 

 

테스트 메서드에 트랜잭션이 있다면

BeforeEach, AfterEach에 트랜잭션을 안걸어도

영속화가 유지되고

 

테스트 메서드에 트랜잭션이 없다면

BeforeEach, AfterEach에 트랜잭션이 안걸렸으면

영속화 유지가 안된다.

 

이에 대해 생각해본 결과

AOP의 특성때문이다.

 

트랜잭션이 있는 메서드 호출시 

TestClass가 AOP걸려 

트랜잭션 시작 후 

해당 테스트 클래스의 메서드를 시작할 것이다.

 

즉, 트랜잭션 시작 -> @BeforeEach -> run() -> @AfterEach -> 트랜잭션 종료

 

이런식이기에 당연히 영속화가 유지된다.