Skip to content

단위 테스트 (Unit Test)

  1. 단위테스트 단계에서는 기능을 검증하는 용도로 충분히 테스트 되어야 한다.
  2. 스프링부트 컨테이너에서 제공하는 기능은 서비스/컨트롤러 테스트를 목업 테스트로 수행하며 실제 테스트는 통합테스트 단계에서 수행한다.
  3. 요청 DTO에서 입력값에 대한 유효성(validation) 테스트는 통합 테스트 단계에서 수행한다.
    1. 일부 커스텀 클래스는 데이터베이스나 로케일 등과 같은 컨테이너의 기능을 사용하기 때문이다.
  4. 공통팀에서 작성되어야 하는 테스트는 아래와 같다.
    1. 유틸 메소드 테스트
    2. 헬퍼 클래스 테스트
    3. 단위테스트 샘플
  5. API 개발자가 작성해야 하는 테스트는 아래와 같다.
    1. Value Object 클래스 테스트(Model, Request DTO, Response DTO, 그 외)
    2. 서비스 클래스 테스트
    3. 컨트롤러 클래스 테스트
  6. API 테스트 작성시 유의사항
    1. 코틀린으로 데이터 클래스 테스트시에 생성자 개수만큼 테스트를 진행하여야 한다.
    2. 서비스와 컨트롤러 테스트는 성공/실패에 대한 테스트 작성이 필요하다. 3 단위테스트 단계에서 코드 커버리지 퍼센티지를 최대한으로 높일 수 있도록 한다.
    3. 이전에 만들어진 테스트를 가져와 키워드 변경만으로 쉽게 테스트를 작성할 수 있다.
  1. Value Object 클래스 샘플
    Role.kt
    @Schema(description = "역할")
    data class Role(
    @field:Schema(description = "역할 ID")
    var roleId: Int?,
    @field:Schema(description = "역할 이름")
    var roleName: String,
    @field:Schema(description = "역할 타입 [A: API, M: 메뉴, *: 전체(API,메뉴)]")
    var roleType: String,
    @field:Schema(description = "사용 여부")
    var useYn: String,
    @field:Schema(description = "등록 일시")
    var createDt: String?,
    @field:Schema(description = "등록자 ID")
    var createUserId: Int?,
    @field:Schema(description = "등록 IP")
    var createIp: String?,
    @field:Schema(description = "수정 일시")
    var updateDt: String?,
    @field:Schema(description = "수정자 ID")
    var updateUserId: Int?,
    @field:Schema(description = "수정 IP")
    var updateIp: String?,
    ) {
    constructor( roleId: Int?,
    roleName: String,
    roleType: String,
    useYn: String,
    ) : this(
    roleId,
    roleName,
    roleType,
    useYn,
    null,
    null,
    null,
    null,
    null,
    null
    )
    constructor( roleName: String,
    roleType: String,
    useYn: String
    ) : this(
    null,
    roleName,
    roleType,
    useYn,
    )
    }
    1. 인스턴스 생성 테스트

      RoleTest.kt
      @DisplayName("Role 인스턴스 생성 테스트")
      @Test
      fun createInstanceTest() {
      //생성자를 통한 인스턴스 생성 테스트(모든 생성자에 대해서 테스트 작성 필요)
      val instance1 = Role(1000, "ADMIN", "*", "Y", null, 1000, "0:0:0:0:0:0:0:1", null, 1000, "0:0:0:0:0:0:0:1")
      assertNotNull(instance1)
      assertEquals(1000, instance1.roleId)
      assertEquals("ADMIN", instance1.roleName)
      assertEquals("*", instance1.roleType)
      assertEquals("Y", instance1.useYn)
      assertNull(instance1.createDt)
      assertEquals(1000, instance1.createUserId)
      assertEquals("0:0:0:0:0:0:0:1", instance1.createIp)
      assertNull(instance1.updateDt)
      assertEquals(1000, instance1.updateUserId)
      assertEquals("0:0:0:0:0:0:0:1", instance1.updateIp)
      //생성자를 통한 인스턴스 생성 테스트(모든 생성자에 대해서 테스트 작성 필요)
      val instance2 = Role(1001, "USER", "*", "Y")
      assertNotNull(instance2)
      assertEquals(1001, instance2.roleId)
      assertEquals("USER", instance2.roleName)
      assertEquals("*", instance2.roleType)
      assertEquals("Y", instance2.useYn)
      assertNull(instance2.createDt)
      assertNull(instance2.createUserId)
      assertNull(instance2.createIp)
      assertNull(instance2.updateDt)
      assertNull(instance2.updateUserId)
      assertNull(instance2.updateIp)
      //생성자를 통한 인스턴스 생성 테스트(모든 생성자에 대해서 테스트 작성 필요)
      val instance3 = Role("USER", "A", "Y")
      assertNotNull(instance3)
      assertNull(instance3.roleId)
      assertEquals("USER", instance3.roleName)
      assertEquals("A", instance3.roleType)
      assertEquals("Y", instance3.useYn)
      assertNull(instance3.createDt)
      assertNull(instance3.createUserId)
      assertNull(instance3.createIp)
      assertNull(instance3.updateDt)
      assertNull(instance3.updateUserId)
      assertNull(instance3.updateIp)
      }
    2. 데이터 비교 테스트

      RoleTest.kt
      @DisplayName("데이터 비교 테스트")
      @Test
      fun equalsTest() {
      //생성자를 통한 인스턴스 생성(모든 생성자에 대해서 인스턴스 작성 필요)
      val instance1 = Role(null, "ADMIN", "*", "Y", null, null, null, null, null, null)
      val instance2 = Role(null, "ADMIN", "*", "Y")
      assertEquals(instance2, instance1)
      val instance3 = Role("ADMIN", "*", "Y")
      assertEquals(instance3, instance1)
      val different = Role("USER", "M", "Y")
      assertNotEquals(different, instance1)
      }
    3. 데이터 수정 테스트

      RoleTest.kt
      @DisplayName("데이터 수정 테스트")
      @Test
      fun updateTest() {
      val instance1 = Role(null, "ADMIN", "*", "Y", null, 1000, "0:0:0:0:0:0:0:1", null, 1000, "0:0:0:0:0:0:0:1")
      instance1.roleId = 1000
      assertEquals(1000, instance1.roleId)
      instance1.roleName = "USER"
      assertEquals("USER", instance1.roleName)
      instance1.roleType = "M"
      assertEquals("M", instance1.roleType)
      instance1.useYn = "N"
      assertEquals("N", instance1.useYn)
      instance1.createDt = "1234.56.78"
      assertEquals("1234.56.78", instance1.createDt)
      instance1.createUserId = 1234
      assertEquals(1234, instance1.createUserId)
      instance1.createIp = "0:0:0:0:0:0:0:1"
      assertEquals("0:0:0:0:0:0:0:1", instance1.createIp)
      instance1.updateDt = "1234.56.78"
      assertEquals("1234.56.78", instance1.updateDt)
      instance1.updateUserId = 1234
      assertEquals(1234, instance1.updateUserId)
      instance1.updateIp = "0:0:0:0:0:0:0:1"
      assertEquals("0:0:0:0:0:0:0:1", instance1.updateIp)
      }
    4. toString 메소드 정상 작동 테스트

      RoleTest.kt
      @DisplayName("toString 메소드 정상 작동 테스트")
      @Test
      fun toStringTest() {
      //생성자를 통한 인스턴스 생성(모든 생성자에 대해서 인스턴스 작성 필요)
      val instance1 = Role(null, "ADMIN", "*", "Y", null, 1000, "0:0:0:0:0:0:0:1", null, 1000, "0:0:0:0:0:0:0:1")
      assertEquals(
      "Role(roleId=null, roleName=ADMIN, roleType=*, useYn=Y, createDt=null, createUserId=1000, createIp=0:0:0:0:0:0:0:1, updateDt=null, updateUserId=1000, updateIp=0:0:0:0:0:0:0:1)",
      instance1.toString()
      )
      //생성자를 통한 인스턴스 생성(모든 생성자에 대해서 인스턴스 작성 필요)
      val instance2 = Role(1001, "USER", "*", "Y")
      assertEquals(
      "Role(roleId=1001, roleName=USER, roleType=*, useYn=Y, createDt=null, createUserId=null, createIp=null, updateDt=null, updateUserId=null, updateIp=null)",
      instance2.toString()
      )
      //생성자를 통한 인스턴스 생성(모든 생성자에 대해서 인스턴스 작성 필요)
      val instance3 = Role("ADMIN", "A", "Y")
      assertEquals(
      "Role(roleId=null, roleName=ADMIN, roleType=A, useYn=Y, createDt=null, createUserId=null, createIp=null, updateDt=null, updateUserId=null, updateIp=null)",
      instance3.toString()
      )
      }
    5. copy 메소드 정상 작동 테스트

      RoleTest.kt
      @DisplayName("copy 메소드 정상 작동 테스트")
      @Test
      fun copyTest() {
      val instance1 = Role(null, "ADMIN", "*", "Y", null, 1000, "0:0:0:0:0:0:0:1", null, 1000, "0:0:0:0:0:0:0:1")
      assertEquals(instance1.copy(), instance1)
      val instance2 = instance1.copy(
      roleName = "USER",
      roleType = "A",
      )
      assertEquals("USER", instance2.roleName)
      assertEquals("A", instance2.roleType)
      }
    6. hashCode 메소드 정상 작동 테스트

      RoleTest.kt
      @DisplayName("hashCode 메소드 정상 작동 테스트")
      @Test
      fun hashCodeTest() {
      val instance1 = Role(null, "ADMIN", "*", "Y", null, 1000, "0:0:0:0:0:0:0:1", null, 1000, "0:0:0:0:0:0:0:1")
      assertEquals(instance1.copy().hashCode(), instance1.hashCode())
      val instance2 = instance1.copy(
      roleName = "USER",
      roleType = "A",
      )
      assertNotEquals(instance2.hashCode(), instance1.hashCode())
      }
    7. 추가 메소드가 있는경우 해당 메소드에 대해 테스트를 추가한다. (예 toContext, toModel)

      SelectRoleListContext.kt
      data class SelectRoleListContext(
      var roleName: String?,
      var roleType: String?,
      var page: Page?,
      )
      SelectRoleListRequest.kt
      @Schema(description = "역할 목록 조회 요청 DTO")
      data class SelectRoleListRequest(
      @field:Schema(description = "역할")
      var roleName: String?,
      @field:Schema(description = "역할 타입")
      var roleType: String?,
      @field:Schema(description = "페이지 정보")
      @field:NotNull
      var page: Page?,
      ) {
      fun toContext(): SelectRoleListContext =
      SelectRoleListContext(
      roleName,
      roleType,
      page,
      )
      }
      SelectRoleListRequestTest.kt
      @DisplayName("toContext 메소드 정상 작동 테스트")
      @Test
      fun toContextTest() {
      val instance1 = SelectRoleListRequest("ADMIN", "*", Page(1, 20))
      val context = instance1.toContext()
      assertEquals(context.roleName, instance1.roleName)
      assertEquals(context.roleType, instance1.roleType)
      assertEquals(context.page, instance1.page)
      }
  2. Service 클래스 샘플
    RoleService.kt
    fun selectRoleList(param: SelectRoleListContext): PageInfo<Role> {
    try {
    PageHelper.startPage<Role?>(param.page)
    return PageInfo.of(roleMapper.selectRoleList(param))
    } catch (e: Exception) {
    throw BizException(e)
    }
    }
    1. 단위테스트에서는 Mapper 메소드 호출 단계에서 목업을 사용한다.
    2. 성공 테스트
      RoleServiceTest.kt
      @DisplayName("selectRoleList 메소드 목업 데이터 테스트")
      @Test
      fun selectRoleListTest() {
      val roleMapper: RoleMapper = mockk<RoleMapper> {
      every { selectRoleList(any()) } returns listOf(Role("ADMIN", "*", "Y"))
      }
      val roleService = RoleService(roleMapper)
      val param = SelectRoleListContext("", "", Page(1, 20))
      assertEquals(PageInfo.of(listOf(Role("ADMIN", "*", "Y"))).list, roleService.selectRoleList(param).list)
      }
    3. 예외 발생 테스트
      RoleServiceTest.kt
      @DisplayName("selectRoleList 메소드 예외 발생 테스트")
      @Test
      fun selectRoleListExceptionTest() {
      val roleMapper: RoleMapper = mockk<RoleMapper> {
      every { selectRoleList(any()) } throws BizException(ApiResultCode.FAILURE)
      }
      val roleService = RoleService(roleMapper)
      val param = SelectRoleListContext("", "", Page(1, 20))
      assertThrows(BizException::class.java) {
      roleService.selectRoleList(param)
      }
      }
  3. Controller 클래스 샘플
    RoleController.kt
    @Operation(summary = "역할 목록 조회", description = "역할 목록 조회 API")
    @PostMapping("/list")
    fun selectRoleList(@Valid @RequestBody param: RequestDto<SelectRoleListRequest>): ResponseEntity<ResponseDto<SelectRoleListResponse>> {
    log.info("selectRoleList param: $param")
    val context = param.getPayload()?.toContext() as SelectRoleListContext
    val payload = SelectRoleListResponse(roleService.selectRoleList(context))
    return ResponseEntity.ok(ResponseDto(payload))
    }
    1. 단위테스트에서는 Service 메소드 호출 단계에서 목업을 사용한다.
    2. 성공 테스트
      RoleControllerTest.kt
      @DisplayName("selectRoleList 메소드 목업 데이터 테스트")
      @Test
      fun selectRoleListTest() {
      val roleService: RoleService = mockk<RoleService> {
      every { selectRoleList(any()) } returns PageInfo.of(listOf(Role("FRUIT", "APPLE", "Y")))
      }
      val roleController = RoleController(roleService)
      val param = RequestDto(SelectRoleListRequest("", "", Page(1, 20)))
      val payload = SelectRoleListResponse(PageInfo.of(listOf(Role("FRUIT", "APPLE", "Y"))))
      assertEquals(payload, roleController.selectRoleList(param).body?.getPayload())
      }
    3. 예외 발생 테스트
      RoleControllerTest.kt
      @DisplayName("selectRoleList 메소드 예외 발생 테스트")
      @Test
      fun selectRoleListExceptionTest() {
      val roleService: RoleService = mockk<RoleService> {
      every { selectRoleList(any()) } throws BizException(ApiResultCode.FAILURE)
      }
      val roleController = RoleController(roleService)
      val param = RequestDto(SelectRoleListRequest("", "", Page(1, 20)))
      assertThrows(BizException::class.java) {
      roleController.selectRoleList(param)
      }
      }