Service 설계
Device API가 제공해야 할 Service를 분석하면 다음과 같다.
- Device list를 조회 할 수 있어야 한다.
- 특정 Device를 조회 할 수 있어야 한다.
- 특정 Device의 value를 수정 할 수 있어야 한다.
- 특정 Device의 command를 수정 할 수 있어야 한다.
이를 토대로 Service를 작성하면 다음과 같다.
package com.studuy.study.device
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
@Transactional(readOnly = true)
class DeviceService(
private val deviceRepository: DeviceRepository
) {
fun getAllDevice(): Iterable<Device>{
return deviceRepository.findAll();
}
@Transactional
fun createDevice(): Device{
val device = Device()
return deviceRepository.save(device)
}
fun getDevice(deviceId: Long): Device{
return deviceRepository.findById(deviceId).orElseThrow()
}
@Transactional
fun updateDeviceValue(deviceId: Long, value: String): Device{
val device = deviceRepository.findById(deviceId).orElseThrow()
device.value = value
return deviceRepository.save(device)
}
@Transactional
fun updateDeviceCommand(deviceId: Long, command: String): Device{
val device = deviceRepository.findById(deviceId).orElseThrow()
device.command = command
return deviceRepository.save(device)
}
}
- 설명
- private val deviceRepository: DeviceRepository
- 생성자에 type을 적어 놓으면 Spring이 auto wired해서 dependency injection을 해준다.
- @Service
- Spring에서 해당 class가 Service에 해당하는 걸 알려준다.
- @Transactional
- Spring에서 현재 스코프가 영속성 컨텍스트에 해당 한다는 것을 알려준다.
- readOnly가 true로 설정 돼 있으면 컨텍스트가 끝났을 때 dirty checking을 하지 않아 데이터를 수정하더라도 반영이 안된다.
- class에 @Transactional(readOnly = true)를 하면 class내부의 모든 함수들이 읽기 전용으로 데이터를 불러온다고 설정된다.
- 위 상태에서 특정 함수에 @Transactional을 붙여주면 readOnly의 defalut값이 false이기 때문에 dirty checking을 하며, 데이터 수정이 일어난다는 걸 의미한다.
- private val deviceRepository: DeviceRepository
TDD(Test Driven Development)
TDD란 한국어로 하면 기능 주도 개발이란 뜻으로 한개의 기능을 개발 할 때마다 테스트를 하고, 검증하면서 개발하는 방식이다. Spring에서는 프로젝트를 생성하면 TDD를 염두에 두고 생성된다.
Service Test Code 작성
지금까지는 프로젝트의 main/kotlin/com.study.study 에서 코드가 작성되어 있다면 test 코드는 test/kotlin/com.study.study 에서 작성된다. DeviceService test코드를 다음과 같이 작성한다.
package com.studuy.study.device
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.Mockito.any
import org.mockito.Mockito.`when`
import org.assertj.core.api.Assertions.assertThat
import org.mockito.InjectMocks
import org.springframework.data.repository.findByIdOrNull
import java.util.*
@ExtendWith(MockitoExtension::class)
internal class DeviceServiceTest {
@Mock
private lateinit var deviceRepository: DeviceRepository
@InjectMocks
private lateinit var deviceService: DeviceService
@Test
fun `test create device`() {
//given
val device = Device()
device.id = 1
//when
`when`(deviceRepository.save(any(Device::class.java))).thenReturn(device)
//do
val createdDevice = deviceService.createDevice()
//then
assertThat(createdDevice).isNotNull
assertThat(createdDevice.id).isNotNull
}
@Test
fun `test update value`(){
//given
val value = "223142"
val device = Device()
device.id = 1
//when
`when`(deviceRepository.findById(device.id!!)).thenReturn(Optional.of(device))
`when`(deviceRepository.save(device)).thenReturn(device)
//do
val updatedDevice = deviceService.updateDeviceValue(device.id!!, value)
//then
assertThat(updatedDevice.value).isEqualTo(value)
}
@Test
fun `test update command`(){
//given
val command = "test command"
val device = Device()
device.id = 1
//when
`when`(deviceRepository.findById(device.id!!)).thenReturn(Optional.of(device))
`when`(deviceRepository.save(device)).thenReturn(device)
//do
val updatedDevice = deviceService.updateDeviceCommand(device.id!!, command)
//then
assertThat(updatedDevice.command).isEqualTo(command)
}
}
- 설명
- @Mock
- class를 가짜로 dependency injection 해준다. injection class는 interface만 동일하며, 내부 적으로는 when으로 어떤 동작을 할지 알려주지 않으면 작동하지 않는다.
- @InjectMocks
- 실제로 테스트를 진행할 class이다.
- @Test
- 현재 함수가 test를 위한 함수라는 걸 명시(사실 많은 기능이 wrapper되는 것이다.)
- when
- mocking된 class가 특정 행위를 할때 반환해야 하는 값을 알려준다.
- assertThat
- 실행된 결과가 의도한 결과와 맞는지 확인해준다. 다르면 error를 throw하고, Test는 fail 된다.
- @Mock
위와 같이 작성한뒤 test를 다음과 같이 실행하면 잘 통과하는걸 확인할 수 있다.
다음에는 Controller와 Rest API에 대해서 포스팅 할 예정이다.
'웹' 카테고리의 다른 글
Spring + Kotlin API Server 만들기 (7) : 간단한 Page추가, CSR & SSR (0) | 2022.02.16 |
---|---|
Spring + Kotlin API Server 만들기 (6) : AWS EB + github action으로 CI/CD 구축 (0) | 2022.02.15 |
Spring + Kotlin API Server 만들기 (3) : Entity , Repository (0) | 2022.02.15 |
Spring + Kotlin API Server 만들기 (2) : project setting (0) | 2022.02.15 |
Spring + Kotlin API Server 만들기 (1) : 프로젝트 시작, MVC 패턴 (0) | 2022.02.15 |