구현 Package 및 Class
비즈니스 로직 적용
Event
도메인에 비즈니스 로직을 적용할 것이다.
비즈니스 로직의 예를 들면, Event
에서 basePrice
와 maxPrice
가 존재한다면, 그 이벤트는 free
가 true
일 수 없다. 또, location
이 존재하면, 그 이번트는 offline
이 true
이여야 한다.
비즈니스 로직이란?
- 도메인 로직이라고도 한다.
- 컴퓨터 프로그램에서 실세계의 규칙에 따라 데이터를 생성, 표시, 저장, 변경하는 부분을 일컫는다. 특히 데이터베이스, 표시장치 등 프로그램의 다른 부분과 대조되는 개념으로 쓰인다.
- 현실 세상의 문제를 해결하는 코드
- 사용자의 요구사항을 컴퓨터에게 잘 전달하기 위해 짜여진 코드 로직
Event
Event
도메인에 update()
메서드를 구현하여 비즈니스 로직을 적용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| package me.hantomas.restapi.events;
import lombok.*;
import javax.persistence.*;
import java.time.LocalDateTime;
@Builder @AllArgsConstructor @NoArgsConstructor
@Getter @Setter @EqualsAndHashCode(of = "id")
@Entity
public class Event {
@Id @GeneratedValue
private Integer id;
private String name;
private String description;
private LocalDateTime beginEnrollmentDateTime;
private LocalDateTime closeEnrollmentDateTime;
private LocalDateTime beginEventDateTime;
private LocalDateTime endEventDateTime;
private String location;
private int basePrice;
private int maxPrice;
private int limitOfEnrollment;
private boolean offline;
private boolean free;
@Enumerated(EnumType.STRING)
private EventStatus eventStatus = EventStatus.DRAFT;
public void update(){
// Update free
if(this.basePrice == 0 && this.maxPrice == 0){
this.free = true;
} else{
this.free = false;
}
// Update offline
if(this.location == null || this.location.isBlank()){
this.offline = false;
} else{
this.offline = true;
}
}
}
|
isBlank()
: Java 11 이후로 제공하는 메소드로,isEmpty()
와 달리 빈 공백을 알아서 trim해준다. 따라서,isEmpty()
는 문자열의 길이가 0인 경우에만 true
를 리턴하지만, isBlank()
의 경우 문자열이 비어 있거나, 빈 공백으로만 이루어져 있어도, true
를 리턴합니다.
EventTest
EventTest
에서 이를 테스트한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| // import 생략
public class EventTest {
// 이 외 테스트 생략
@Test // free에 대한 테스트
public void testFree(){
// Given
Event event = Event.builder()
.basePrice(0)
.maxPrice(0)
.build();
// When
event.update();
// Then
assertThat(event.isFree()).isTrue();
// Given
event = Event.builder()
.basePrice(100)
.maxPrice(0)
.build();
// When
event.update();
// Then
assertThat(event.isFree()).isFalse();
// Given
event = Event.builder()
.basePrice(0)
.maxPrice(100)
.build();
// When
event.update();
// Then
assertThat(event.isFree()).isFalse();
}
@Test // offline에 대한 테스트
public void testOffline(){
// Given
Event event = Event.builder()
.location("강남역 네이버 D2 스타텁 팩토리")
.build();
// When
event.update();
// Then
assertThat(event.isOffline()).isTrue();
// Given
event = Event.builder()
.build();
// When
event.update();
// Then
assertThat(event.isOffline()).isFalse();
}
}
|
결과(성공)
EventController
EventController
에 Event
의 update()
를 적용 시킨다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
| package me.hantomas.restapi.events;
import org.modelmapper.ModelMapper;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.validation.Valid;
import java.net.URI;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
@Controller
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_UTF8_VALUE)
public class EventController {
private final EventRepository eventRepository;
private final ModelMapper modelMapper;
private final EventValidator eventValidator;
public EventController(EventRepository eventRepository, ModelMapper modelMapper, EventValidator eventValidator) {
this.eventRepository = eventRepository;
this.modelMapper = modelMapper;
this.eventValidator = eventValidator;
}
@PostMapping
public ResponseEntity createEvent(@RequestBody @Valid EventDto eventDto, Errors errors){
if (errors.hasErrors()){
return ResponseEntity.badRequest().body(errors);
}
eventValidator.validate(eventDto, errors);
if (errors.hasErrors()){
return ResponseEntity.badRequest().body(errors);
}
Event event = modelMapper.map(eventDto, Event.class);
event.update(); // 추가
Event newEvent = this.eventRepository.save(event);
URI createdUri = linkTo(EventController.class).slash(newEvent.getId()).toUri();
return ResponseEntity.created(createdUri).body(newEvent);
}
}
|
EventControllerTests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| // import 생략
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class EventControllerTests {
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
@Test
@TestDescription("정상적으로 이벤트를 생성하는 테스트")
public void createEvent() throws Exception{
EventDto event = EventDto.builder()
.name("Spring")
.description("REST API Development With Spring")
.beginEnrollmentDateTime(LocalDateTime.of(2024,02,19,14,21))
.closeEnrollmentDateTime(LocalDateTime.of(2024,02,20,14,21))
.beginEventDateTime(LocalDateTime.of(2024,02,21,14,21))
.endEventDateTime(LocalDateTime.of(2024,02,22,14,21))
.basePrice(100)
.maxPrice(200)
.limitOfEnrollment(100)
.location("강남역 D2 스타트업 팩토리")
.build();
mockMvc.perform(post("/api/events/")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaTypes.HAL_JSON)
.content(objectMapper.writeValueAsString(event))
)
.andDo(print())
.andExpect(status().isCreated())
.andExpect(jsonPath("id").exists())
.andExpect(header().exists(HttpHeaders.LOCATION))
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_UTF8_VALUE))
.andExpect(jsonPath("free").value(false)) // 수정
.andExpect(jsonPath("offline").value(true)) // 수정
.andExpect(jsonPath("eventStatus").value(EventStatus.DRAFT.name()))
;
}
// 이 외 테스트 코드 생략
}
|
결과 (성공)
매개변수를 이용한 테스트
EventTest
의 테스트 코드를 살펴보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| // import 생략
public class EventTest {
// 이 외 테스트 생략
@Test // free에 대한 테스트
public void testFree(){
// Given
Event event = Event.builder()
.basePrice(0)
.maxPrice(0)
.build();
// When
event.update();
// Then
assertThat(event.isFree()).isTrue();
// Given
event = Event.builder()
.basePrice(100)
.maxPrice(0)
.build();
// When
event.update();
// Then
assertThat(event.isFree()).isFalse();
// Given
event = Event.builder()
.basePrice(0)
.maxPrice(100)
.build();
// When
event.update();
// Then
assertThat(event.isFree()).isFalse();
}
@Test // offline에 대한 테스트
public void testOffline(){
// Given
Event event = Event.builder()
.location("강남역 네이버 D2 스타텁 팩토리")
.build();
// When
event.update();
// Then
assertThat(event.isOffline()).isTrue();
// Given
event = Event.builder()
.build();
// When
event.update();
// Then
assertThat(event.isOffline()).isFalse();
}
}
|
테스트들을 보면 파라미터만 다를 뿐, 중복된 코드가 많아보인다. 이를 JUnitParams
를 이용하여, 매개변수를 이용한 테스트로 리팩토링 해보겠다.
의존성 추가(pom.xml)
1
2
3
4
5
6
| <dependency>
<groupId>pl.pragmatists</groupId>
<artifactId>JUnitParams</artifactId>
<version>1.1.1</version>
<scope>test</scope>
</dependency>
|
EventTest 방법 (1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| // import 생략
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.runner.RunWith;
@RunWith(JUnitParamsRunner.class)
public class EventTest {
// 이 외 테스트 생략
@Test
@Parameters({
"0, 0, true",
"100, 0, false",
"0, 100, false"
})
public void testFree(int basePrice, int maxPrice, boolean isFree){
// Given
Event event = Event.builder()
.basePrice(basePrice)
.maxPrice(maxPrice)
.build();
// When
event.update();
// Then
assertThat(event.isFree()).isEqualTo(isFree);
}
}
|
결과 (성공)
EventTest 방법 (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| // import 생략
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.runner.RunWith;
@RunWith(JUnitParamsRunner.class)
public class EventTest {
// 이 외 테스트 생략
@Test
@Parameters(method = "paramsForTestFree")
public void testFree(int basePrice, int maxPrice, boolean isFree){
// Given
Event event = Event.builder()
.basePrice(basePrice)
.maxPrice(maxPrice)
.build();
// When
event.update();
// Then
assertThat(event.isFree()).isEqualTo(isFree);
}
private Object[] paramsForTestFree(){
return new Object[]{
new Object[] {0, 0, true},
new Object[] {100, 0, false},
new Object[] {0, 100, false},
new Object[] {100, 200, false}
};
}
}
|
결과 (성공)
EventTest (최종)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| // import 생략
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.runner.RunWith;
@RunWith(JUnitParamsRunner.class)
public class EventTest {
// 이 외 테스트 생략
@Test
@Parameters // 수정
public void testFree(int basePrice, int maxPrice, boolean isFree){
// Given
Event event = Event.builder()
.basePrice(basePrice)
.maxPrice(maxPrice)
.build();
// When
event.update();
// Then
assertThat(event.isFree()).isEqualTo(isFree);
}
private Object[] paramsForTestFree(){
return new Object[]{
new Object[] {0, 0, true},
new Object[] {100, 0, false},
new Object[] {0, 100, false},
new Object[] {100, 200, false}
};
}
@Test
@Parameters // 수정
public void testOffline(String location, boolean isOffline){
// Given
Event event = Event.builder()
.location(location)
.build();
// When
event.update();
// Then
assertThat(event.isOffline()).isEqualTo(isOffline);
}
private Object[] parametersForTestOffline(){
return new Object[]{
new Object[]{"강남", true},
new Object[]{null, false},
new Object[]{" ", false}
};
}
}
|
@Parameters
: @Parameters
의(method = "")
부분을 생략해 주어도 된다. @Parameters
는 parametersFor
라는 prefix가 붙은, 테스트와 같은 이름의 Object를 자동으로 파라미터로 사용한다.
결과 (성공)