단위 테스트 (Unit Test)
- 단위테스트 단계에서는 기능을 검증하는 용도로 충분히 테스트 되어야 한다.
- 스프링부트 컨테이너에서 제공하는 기능은 서비스/컨트롤러 테스트를 목업 테스트로 수행하며 실제 테스트는 통합테스트 단계에서 수행한다.
- 요청 DTO에서 입력값에 대한 유효성(validation) 테스트는 통합 테스트 단계에서 수행한다.
- 일부 커스텀 클래스는 데이터베이스나 로케일 등과 같은 컨테이너의 기능을 사용하기 때문이다.
- 공통팀에서 작성되어야 하는 테스트는 아래와 같다.
- 유틸 메소드 테스트
- 헬퍼 클래스 테스트
- 단위테스트 샘플
- API 개발자가 작성해야 하는 테스트는 아래와 같다.
- Value Object 클래스 테스트(Model, Request DTO, Response DTO, 그 외)
- 서비스 클래스 테스트
- 컨트롤러 클래스 테스트
- API 테스트 작성시 유의사항
- 코틀린으로 데이터 클래스 테스트시에 생성자 개수만큼 테스트를 진행하여야 한다.
- 서비스와 컨트롤러 테스트는 성공/실패에 대한 테스트 작성이 필요하다. 3 단위테스트 단계에서 코드 커버리지 퍼센티지를 최대한으로 높일 수 있도록 한다.
- 이전에 만들어진 테스트를 가져와 키워드 변경만으로 쉽게 테스트를 작성할 수 있다.
단위 테스트
Section titled “단위 테스트”- 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,)}-
인스턴스 생성 테스트
RoleTest.kt @DisplayName("Role 인스턴스 생성 테스트")@Testfun 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)} -
데이터 비교 테스트
RoleTest.kt @DisplayName("데이터 비교 테스트")@Testfun 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)} -
데이터 수정 테스트
RoleTest.kt @DisplayName("데이터 수정 테스트")@Testfun 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 = 1000assertEquals(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 = 1234assertEquals(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 = 1234assertEquals(1234, instance1.updateUserId)instance1.updateIp = "0:0:0:0:0:0:0:1"assertEquals("0:0:0:0:0:0:0:1", instance1.updateIp)} -
toString 메소드 정상 작동 테스트
RoleTest.kt @DisplayName("toString 메소드 정상 작동 테스트")@Testfun 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())} -
copy 메소드 정상 작동 테스트
RoleTest.kt @DisplayName("copy 메소드 정상 작동 테스트")@Testfun 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)} -
hashCode 메소드 정상 작동 테스트
RoleTest.kt @DisplayName("hashCode 메소드 정상 작동 테스트")@Testfun 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())} -
추가 메소드가 있는경우 해당 메소드에 대해 테스트를 추가한다. (예 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:NotNullvar page: Page?,) {fun toContext(): SelectRoleListContext =SelectRoleListContext(roleName,roleType,page,)}SelectRoleListRequestTest.kt @DisplayName("toContext 메소드 정상 작동 테스트")@Testfun 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)}
-
- 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)}}- 단위테스트에서는 Mapper 메소드 호출 단계에서 목업을 사용한다.
- 성공 테스트
RoleServiceTest.kt @DisplayName("selectRoleList 메소드 목업 데이터 테스트")@Testfun 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)} - 예외 발생 테스트
RoleServiceTest.kt @DisplayName("selectRoleList 메소드 예외 발생 테스트")@Testfun 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)}}
- 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 SelectRoleListContextval payload = SelectRoleListResponse(roleService.selectRoleList(context))return ResponseEntity.ok(ResponseDto(payload))}- 단위테스트에서는 Service 메소드 호출 단계에서 목업을 사용한다.
- 성공 테스트
RoleControllerTest.kt @DisplayName("selectRoleList 메소드 목업 데이터 테스트")@Testfun 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())} - 예외 발생 테스트
RoleControllerTest.kt @DisplayName("selectRoleList 메소드 예외 발생 테스트")@Testfun 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)}}