찌리릿 [Zziririt] 테스트 코드

박미소·2024년 4월 25일
1

코틀린

목록 보기
44/44
post-thumbnail

Repository Layer 단위 테스트

직접 작성한 쿼리문을 테스트 하기 위해서 리포지토리 테스트를 작성했다.

@Repository
class BoardQueryDslRepositoryImpl : QueryDslSupport(), BoardQueryDslRepository {
    private val board = QBoardEntity.boardEntity
    private val post = QPostEntity.postEntity
    private val category = QCategoryEntity.categoryEntity

    override fun findBoards(): List<BoardRowDto> {
        return queryFactory
            .select(
                QBoardRowDto(
                    board.id,
                    board.boardName
                )
            )
            .from(board)
            .orderBy(board.id.asc())
            .fetch()
    }


    override fun findStreamers(): List<StreamerBoardRowDto> {
        return queryFactory
            .select(
                QStreamerBoardRowDto(
                    board.id,
                    board.boardUrl,
                    board.boardName,
                )
            )
            .from(board)
            .where(board.boardType.eq(BoardType.STREAMER_BOARD))
            .orderBy(board.id.asc())
            .fetch()
    }

    override fun findBoardStatusToInactive(): List<Long> {
        val checkInactiveDate = LocalDateTime.now().minusDays(8)

        return queryFactory.select(board.id).distinct()
            .from(post)
            .leftJoin(board)
            .on(board.id.eq(post.board.id))
            .where(post.modifiedAt.loe(checkInactiveDate), board.boardType.eq(BoardType.STREAMER_BOARD))
            .fetch()
    }

    override fun updateBoardStatusToInactive(inactiveBoardIdList: List<Long>) {
        queryFactory
            .update(board)
            .set(board.boardActStatus, BoardActStatus.INACTIVE)
            .where(board.id.`in`(inactiveBoardIdList))
            .execute()
    }

    override fun findActiveStatusBoards(): List<BoardRowDto> {
        return queryFactory
            .select(
                QBoardRowDto(
                    board.id,
                    board.boardName
                )
            ).from(board)
            .where(board.boardActStatus.eq(BoardActStatus.ACTIVE))
            .orderBy(board.id.asc())
            .fetch()
    }
}

먼저 리포지토리 테스트를 위한 데이터베이스를 설정했다.

spring:
  config:
    activate:
      on-profile: test
  datasource:
    url: jdbc:h2:mem:test;MODE=MySQL;
    driver-class-name: org.h2.Driver
    username: sa
    password:
  jpa:
    show-sql: true
    hibernate:
      ddl-auto:  create-drop
    properties:
      hibernate:
        format-sql: true
        highlight_sql: true
        use_sql_comments: true
        default_batch_fetch_size: 1000
    open-in-view: false  

보통 GIVEN 절에서 데이터를 설정할 때 모든 메서드에서 공동으로 사용하는 데이터가 있을 경우 BeforeEach를 활용해 중복을 제거할 수 있다. 하지만 현 상홯에서는 부적합하다는 것을 확인해 각 메서드마다 GIVEN 절에서 객체들을 따로 생성했다.

@DataJpaTest
@ActiveProfiles("test")
@Import(value = [QueryDslConfig::class, BaseTimeEntityConfig::class, BaseEntityConfig::class])
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class BoardQueryDslRepositoryImplTest @Autowired constructor(
    private val boardRepository: CustomBoardRepository,
    private val testEntityManager: TestEntityManager
) {
    @Test
    fun sampleTest() {
        boardRepository.save(
            BoardEntity(
                socialMember = SocialMemberEntity(
                    email = "test@gmail.com",
                    nickname = "test",
                    provider = OAuth2Provider.TEST,
                    providerId = "providerId",
                    memberRole = MemberRole.ADMIN
                ),
                boardName = "test",
                boardUrl = "boardUrl",
                boardType = BoardType.ZZIRIRIT_BOARD
            )
        )
        testEntityManager.flush()
    }

    @Test
    fun `게시판 전체가 조회되는지 확인`() {
        // GIVEN
        val socialMember = SocialMemberEntity(
            email = "test@gmail1.com",
            nickname = "test1",
            provider = OAuth2Provider.TEST,
            providerId = "providerId",
            memberRole = MemberRole.ADMIN
        )

        val DEFAULT_BOARD_LIST = listOf(
            BoardEntity(
                socialMember = socialMember,
                boardName = "test1",
                boardUrl = "boardUrl1",
                boardType = BoardType.ZZIRIRIT_BOARD,
                boardActStatus = BoardActStatus.INACTIVE
            ),
            BoardEntity(
                socialMember = socialMember,
                boardName = "test2",
                boardUrl = "boardUrl2",
                boardType = BoardType.ZZIRIRIT_BOARD,
                boardActStatus = BoardActStatus.ACTIVE
            ),
            BoardEntity(
                socialMember = socialMember,
                boardName = "test3",
                boardUrl = "boardUrl3",
                boardType = BoardType.ZZIRIRIT_BOARD,
                boardActStatus = BoardActStatus.ACTIVE
            ),
            BoardEntity(
                socialMember = socialMember,
                boardName = "test4",
                boardUrl = "boardUrl4",
                boardType = BoardType.ZZIRIRIT_BOARD,
                boardActStatus = BoardActStatus.ACTIVE
            ),
            BoardEntity(
                socialMember = socialMember,
                boardName = "test5",
                boardUrl = "boardUrl5",
                boardType = BoardType.ZZIRIRIT_BOARD,
                boardActStatus = BoardActStatus.ACTIVE
            ),
            BoardEntity(
                socialMember = socialMember,
                boardName = "test6",
                boardUrl = "boardUrl6",
                boardType = BoardType.STREAMER_BOARD,
                boardActStatus = BoardActStatus.INACTIVE
            ),
            BoardEntity(
                socialMember = socialMember,
                boardName = "test7",
                boardUrl = "boardUrl7",
                boardType = BoardType.STREAMER_BOARD,
                boardActStatus = BoardActStatus.ACTIVE
            ),
            BoardEntity(
                socialMember = socialMember,
                boardName = "test8",
                boardUrl = "boardUrl8",
                boardType = BoardType.STREAMER_BOARD,
                boardActStatus = BoardActStatus.ACTIVE
            ),
            BoardEntity(
                socialMember = socialMember,
                boardName = "test9",
                boardUrl = "boardUrl9",
                boardType = BoardType.STREAMER_BOARD,
                boardActStatus = BoardActStatus.ACTIVE
            ),
            BoardEntity(
                socialMember = socialMember,
                boardName = "test10",
                boardUrl = "boardUrl10",
                boardType = BoardType.STREAMER_BOARD,
                boardActStatus = BoardActStatus.ACTIVE
            )
        )
        boardRepository.saveAllAndFlush(DEFAULT_BOARD_LIST)

        // WHEN
        val result = boardRepository.findBoards()

        // THEN
        result.size shouldBe 10
        result[0].boardName shouldBe "test1"
        result[9].boardName shouldBe "test10"
    }

    @Test
    fun `스트리머 게시판 전체가 조회되는지 확인`() {
        // GIVEN
        val socialMember = SocialMemberEntity(
            email = "test@gmail1.com",
            nickname = "test1",
            provider = OAuth2Provider.TEST,
            providerId = "providerId",
            memberRole = MemberRole.ADMIN
        )

        val DEFAULT_BOARD_LIST = listOf(
            BoardEntity(
                socialMember = socialMember,
                boardName = "test1",
                boardUrl = "boardUrl1",
                boardType = BoardType.ZZIRIRIT_BOARD,
                boardActStatus = BoardActStatus.INACTIVE
            ),
            ...
        )
        boardRepository.saveAllAndFlush(DEFAULT_BOARD_LIST)

        // WHEN
        val result = boardRepository.findStreamers()

        // THEN
        result.size shouldBe 5
        result[4].streamerNickname shouldBe "test10"    // 스트리머의 닉네임이 게시판의 이름이도록 정책 세움
    }

    @Test
    fun `비활성화 상태로 변경시킬 게시판의 아이디들을 조회하는지 확인`() {
        // GIVEN & WHEN
        val socialMember = SocialMemberEntity(
            email = "test@gmail1.com",
            nickname = "test1",
            provider = OAuth2Provider.TEST,
            providerId = "providerId",
            memberRole = MemberRole.ADMIN
        )

        val DEFAULT_BOARD_LIST = listOf(
            BoardEntity(
                socialMember = socialMember,
                boardName = "test1",
                boardUrl = "boardUrl1",
                boardType = BoardType.ZZIRIRIT_BOARD,
                boardActStatus = BoardActStatus.INACTIVE
            ),
            ...
        )
        boardRepository.saveAllAndFlush(DEFAULT_BOARD_LIST)
        val result = boardRepository.findBoardStatusToInactive()

        // THEN
        result.size shouldNotBe null    // 비활성화 상태로 변경시킬 게시판의 아이디를 찾지 못하면 0이 size 가 0이라도 반환되기 때문에 null 이 아니면 메서드 동작 확인 성공.
    }

    @Test
    fun `게시판이 비활성화 상태로 업데이트 되는지 확인`() {
        // GIVEN
        val socialMember = SocialMemberEntity(
            email = "test@gmail1.com",
            nickname = "test1",
            provider = OAuth2Provider.TEST,
            providerId = "providerId",
            memberRole = MemberRole.ADMIN
        )

        val DEFAULT_BOARD_LIST = listOf(
            BoardEntity(
                socialMember = socialMember,
                boardName = "test1",
                boardUrl = "boardUrl1",
                boardType = BoardType.ZZIRIRIT_BOARD,
                boardActStatus = BoardActStatus.INACTIVE
            ),
            ...
        )
        val boards = boardRepository.saveAllAndFlush(DEFAULT_BOARD_LIST)

        // WHEN
        val boardIds = boards.map { it.id!! }

        // THEN
        boardRepository.updateBoardStatusToInactive(boardIds)   // 반환 타입이 unit 이라서 행위를 테스트

    }

    @Test
    fun `활성화 상태의 게시판이 조회되는지 확인`() {
        // GIVEN
        val socialMember = SocialMemberEntity(
            email = "test@gmail1.com",
            nickname = "test1",
            provider = OAuth2Provider.TEST,
            providerId = "providerId",
            memberRole = MemberRole.ADMIN
        )

        val DEFAULT_BOARD_LIST = listOf(
            BoardEntity(
                socialMember = socialMember,
                boardName = "test1",
                boardUrl = "boardUrl1",
                boardType = BoardType.ZZIRIRIT_BOARD,
                boardActStatus = BoardActStatus.INACTIVE
            ),
            ...
        )
        boardRepository.saveAllAndFlush(DEFAULT_BOARD_LIST)

        // WHEN
        val result = boardRepository.findActiveStatusBoards()

        // THEN
        result.size shouldBe 8
    }
}

Repository Layer Test Code 트러블 슈팅

이전에 작성했던 코드에서 companion object 를 이용해 테스트 메서드 내에서 동일한 데이터를 모두 사용할 수 있게 작성했는데 오류를 만나게 됐다.

@Test
fun `게시판 전체가 조회되는지 확인`() {
    // GIVEN
    boardRepository.saveAllAndFlush(DEFAULT_BOARD_LIST)

    // WHEN
    val result = boardRepository.findBoards()

    // THEN
    result.size shouldBe 10
    result[0].boardName shouldBe "test1"
    result[9].boardName shouldBe "test10"
}
...
 companion object {
        companion object {
        val socialMember = SocialMemberEntity(
            email = "test@gmail1.com",
            nickname = "test1",
            provider = OAuth2Provider.TEST,
            providerId = "providerId",
            memberRole = MemberRole.ADMIN
        )

        val DEFAULT_BOARD_LIST = listOf(
            BoardEntity(
                socialMember = socialMember,
                boardName = "test1",
                boardUrl = "boardUrl1",
                boardType = BoardType.ZZIRIRIT_BOARD,
                boardActStatus = BoardActStatus.INACTIVE
            ),
            ...

org.springframework.dao.DataIntegrityViolationException: could not execute statement [Referential integrity constraint violation: "FK3N752SW9EC3V9S2V4MMJ3710N: PUBLIC.BOARD FOREIGN KEY(SOCIAL_MEMBER_ID) REFERENCES PUBLIC.SOCIAL_MEMBER(ID) (CAST(1 AS BIGINT))"; SQL statement:

에러 메시지를 확인해보니 companion object 에서 만든 Board 객체 내 SocialMember 객체를 @Test 메서드 실행 시 Board 안에서 찾을 수 없기 때문이었다.

Board 테이블이 참조하는 SocialMember 의 컬럼 값과 참조되는 SocialMember 테이블의 컬럼 값이 동일하지 않아 발생한 문제였다.

참조 무결성 제약조건이 위배된 상황을 확인하고 난 후 테스트 메서드 내에서 모두 같은 데이터를 쓰지만 데이터를 격리해 픽스쳐를 공유하지 않기로 결정했다.

생성되는 데이터를 메서드 내에서 바로 확인할 수 있다는 이점도 생겼다.

1개의 댓글

comment-user-thumbnail
2024년 5월 1일

👍

답글 달기