본문 바로가기
Spring/junit5 테스트

junit 테스트 코드(3/5) - 단위 테스트(RepositoryLayer)

by 옹알이옹 2023. 7. 31.

 

 1. Repository 계층의 단위 테스트 역할

Repository 계층의 단위 테스트의 역할은 매우 간단하다.
Service 계층의 테스트 처럼 Mock 객체를 사용할 필요 없이
Repository를 의존 주입 받아 호출 하고 예상한 결과가 도출 되는지만 확인하면 끝이다.

 2. Repository 단위 테스트의 의존 주입

Repository 테스트에서는 JpaRepository를 상속받아 그 안의 메서드를 사용하기 때문에 어찌 되었든
의존주입을 받아야만 한다.
하지만 앞서 설명했듯 단위 테스트에서는 Spring 컨테이너를 사용하면 안된다.
그렇기 때문에 다른 방법을 통해 Repository 객체를 의존 주입 받아 그 안의 함수를 호출해야한다.

위의 문제를 해결하기 위해 SpringBoot에서 @DataJpaTest라는 어노테이션을 제공한다.

해당 어노테이션은 Repository 계층에서의 단위 테스트를 위한 기능, 즉 Repository 객체를 의존주입받을 수 있게 해준다.

@Autowired
private BoardRepository boardRepository;

@DataJpaTest를 사용하면 위의 코드와 같이 Repository 객체를 의존주입받을 수 있게 해준다.

주의해야 할 점은 의존주입을 해주는 것이 아니라 할 수 있게 해주는 것이다. 그렇기 때문에 @Autowired를 사용해야 한다.

 

위의 작업을 한 뒤 boardRepository의 save를 호출하면 콘솔에 아래와 같이 실제 쿼리가 실행이 된 것을 확인할 수 있다,

이미지 없음

그렇다면 저것을 보고 테스트 코드인데 실제 DB에 데이터가 들어가면 안 되는 거 아닌가 라는 의문을 가질 수 있다.
결론은 저장이 되지 않는다. Junit을 통한 테스트에서 Select을 제외한 모든 쿼리는 RoleBack 처리가 된다.

 

 3. 코드 작성 예시

@DataJpaTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)    //  임베디드 데이터 베이스를 사용 안한다는 선언.
class BoardRepositoryTest {

    @Autowired
    private BoardRepository boardRepository;

    private static Board board;
    private static Member member;

    @BeforeEach // 테스트 실행 전 실행
    void setup(){
        System.err.println("=============================== setup ==============================");
        member = Member.builder()
                .memberId("tester")
                .firstNm("t")
                .lastNm("ester")
                .password("!23")
                .email("test@naver.com")
                .build();

        board = Board.builder()
                .boardNm("board1")
                .useAt("Y")
                .comCode(null)
                .member(member)
                .build();

    }

    @Test
    @DisplayName("게시판 생성")
    @Transactional
    @Order(1)
    public void saveBoardTest(){

        // given
        // static board 사용

        // when
        Board saveBoard = boardRepository.save(board);

        // then
        assertThat(saveBoard).isNotNull();
        assertThat(saveBoard.getBoardNm()).isEqualTo(board.getBoardNm());
    }

    @Test
    @Order(2)
    @Transactional
    @DisplayName("게시판 목록 조회")
    public void selectBoardList(){

        // given
        boardRepository.save(board);

        // when
        List<Board> findList = boardRepository.findAll();

        // then
        assertThat(findList).isNotEmpty();
        assertThat(findList.size()).isEqualTo(1);
    }

    @Test
    @Order(3)
    @Transactional
    @DisplayName("게시판 수정")
    public void updateBoard(){

        // given
        Board saveEntity = boardRepository.save(board);

        // when
        saveEntity.update("수정된 이름");

        // then
        Board findEntity = boardRepository.findById(saveEntity.getBoardNo()).get();
        assertThat(findEntity).isNotNull();
        assertThat(findEntity.getBoardNm()).isEqualTo("수정된 이름");

    }
}
생성, 조회, 수정에 대해서 테스트를 진행한다.

각각의 테스트 설명

Repository - create

  • given : 생성 테스트를 위한 Entity는 setUp() 함수를 통해 생성한 board를 사용한다.
  • when : repository의 save함수를 호출한다. => 테스트 대상
  • then : 위의 함수를 실행한 결과로 board객체를 리턴 받고, 그 객체는 null이 아니고,
    저장된 객체의 boardNm과 인자로 넘긴 board의 boardNm이 같다고 예상한다.
위의 then 조건을 만족한다면 해당 테스트는 성공했다고 판단한다.

 

Repository - select

  • given : 조회 테스트를 위해 board 객체 하나를 저장한다. (먼저 실시한 테스트에서 검증완료)
  • when : repository의 findAll함수를 호출한다. => 테스트 대상
  • then : 위의 함수를 실행한 결과로 리턴 받은 리스트가 비어있지 않고, 하나를 저장한 뒤 조회 했으므로
    리스트의 크기는 1이라고 예상한다.

Repository - update

  • given : 수정 테스트를 위해 board 객체 하나를 저장한다. (먼저 실시한 테스트에서 검증완료)
  • when : saveEntity의 update("수정된 이름") 함수를 호출한다. -> 왜 엔티티의 함수를 호출 하는지는 뒤에 설명
  • then : boardRepository.findById를 통해 위에서 생성하고 수정한 객체를 조회한다.
    조회한 Entity객체가 null이 아니고 boardNm이 수정한 값과 동일한지 가정한다.
새로 조회한 객체의 boardNm이 수정한 값과 동일하다면 수정이 성공적으로 되었다고 판단할 수 있다

when 부분에서 왜 Repository 테스트인데 board Entity의 update 함수를 호출하는지에 대한 이유는 다음과 같다.

 

BoardService 코드

@Transactional
public void updateBoard(Long boardNo,BoardUpdateDto boardUpdateDto) {

    // 수정 하려는 게시판이 실제 존재하지 않다면 => 예외처리
    Board board = boardRepository.findById(boardNo).get();

    board.update(boardUpdateDto.getBoardNm());
}

BoardEntity코드

@DynamicInsert // null 값 전달 시 insert 컬럼에서 제외,=> Default 값 적용 가능
@Entity
@Getter
@Table(name = "board")
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString
public class Board extends BaseDateEntity {
    @Id
    @Column(name = "board_no")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long boardNo;

   ... 이하 생략...

    public void update(String boardNm) {
        this.boardNm = boardNm;
    }

}
boardService에서 board의 update()를 호출하고 update() 함수는 boardNm에 대한 setter함수이다.
그렇기 때문에 board의 boardNm이 변경되고 @Transactional로 인해 해당 함수가 종료됨과 동시에 트랜잭션이 커밋 되어 DB에 반영이 되게 된다 (더티 체킹).
그렇기 때문에 비록 service 계층이지만 DB에 반영된 것을 테스트 하는 것이 목적이기 때문에 Repository Test에서 
진행하였다. 

 4. setUp(@Before ~) 함수 사용 시 주의사항

위의 setUp 함수는 각각의 테스트에서 boardEntity의 값을 채워주는 것을 자동화하기 위해서 사용하였다.
해당 함수를 실제로 호출하는 행위는 @BeforeEach 어노테이션이 해주고 있으며, 모든 테스트가 실행되기 전 각각 호출된다.

하지만 클래스 기준 한 번만 실행하는 @BeforeAll 을 사용 시 테스트 일괄 실행할 때 문제가 생길 수 있다.

 

@BeforeEach 사용

BoardRepositoryTest 클래스를 잡고 실행하면 결과는 다음과 같다

이미지 없음이미지 없음

결과 : setUp함수가 실행될 때 찍히는 로그가 각각 찍히며, 모든 테스트가 성공한다.

 

@BeforeAll 사용

이미지 없음

결과 : Board 안에 Member 객체가 의존주입이 되어 있는데 Member Entity를 찾지 못해 에러가 난다

✅ why?

하나의 entity를 반복해서 사용하는 것이 문제가 생기는 거 같은데 정확한 문제 파악을 하지 못했다..
JPA와 static 등 여러 가지의 문제가 있는 거 같은데 아시는 분은 댓글 부탁드립니다.

 


 5.  필요한 어노테이션 및 함수 정리

@DataJpaTest : Repository 테스트를 할 때 의존성 주입을 할 수 있게 해준다.

@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)  :임베디드 데이터 베이스를 사용 안 한다는 선언.

@BeforeEach : 모든 테스트 함수가 실행되기 전 각각 해당 어노테이션이 붙어 있는 함수를 호출한다.

@BeforeAll : 테스트 클래스 안에 있는 테스트 코드가 실행될 때 딱 한번 어노테이션이 붙어 있는 함수를 호출한다.

반응형