Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2단계 - 웹 자동차 경주] 허브(방대의) 미션 제출합니다. #128

Merged
merged 37 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8217daa
refactor: 서비스 단위테스트 Mockito 사용하도록 변경
greeng00se Apr 16, 2023
dc8a390
refactor: NumberGenerator Bean 설정파일을 이용하여 등록하도록 변경
greeng00se Apr 16, 2023
3f180ae
test: WebRacingGameController 슬라이스 테스트로 변경
greeng00se Apr 16, 2023
5a5bb8c
docs: 요구사항 정리
greeng00se Apr 17, 2023
687c018
refactor: DB 테이블 변경 및 엔티티 클래스 추가
greeng00se Apr 17, 2023
0254c3a
refactor: 게임 반복진행 로직을 게임 내부로 이동
greeng00se Apr 17, 2023
787df6f
refactor: 우승자 및 참가자를 리스트 형태로 받도록 수정
greeng00se Apr 17, 2023
c76a89e
refactor: 참가한 자동차를 전부 반환하는 메서드명 변경
greeng00se Apr 17, 2023
9ee855f
test: 테스트 메서드명 한국어로 변경
greeng00se Apr 17, 2023
8f58fc3
test: TestNumberGenerator 구현체 deque로 변경
greeng00se Apr 17, 2023
8c84be5
refactor: 콘솔 애플리케이션 출력 변경 및 중복 로직 제거
greeng00se Apr 17, 2023
e6dfb73
refactor: 데이터 저장시 SimpleJdbcInsert 사용하도록 변경
greeng00se Apr 17, 2023
afe536a
refactor: 도메인 문자열 상수 제거
greeng00se Apr 17, 2023
8774a6c
refactor: 더욱 의미있는 DTO명으로 변경
greeng00se Apr 17, 2023
a133140
feat: 엔티티를 매핑해주는 역할을 담당하는 클래스 추가
greeng00se Apr 17, 2023
9dc8c73
feat: GameDao가 키를 Optional 형태로 반환하도록 수정
greeng00se Apr 17, 2023
b022338
feat: CarDao 조회 기능 구현
greeng00se Apr 17, 2023
13b849a
feat: Car 엔티티를 받아 GamePlayResponseDto 리스트로 매핑 기능 추가
greeng00se Apr 17, 2023
29f8189
feat: RacingGameService 조회 기능 추가
greeng00se Apr 17, 2023
f2f7d91
feat: RacingGameController 조회 기능 추가
greeng00se Apr 17, 2023
c8b5b21
feat: RacingGameController 조회 기능 추가
greeng00se Apr 17, 2023
5bde85a
feat: request에 validation 적용
greeng00se Apr 17, 2023
ff564cf
remote: 필요없는 코드 제거
greeng00se Apr 18, 2023
55cdf91
refactor: 예외 출력 메시지 수정
greeng00se Apr 18, 2023
ee97a59
refactor: simpleJdbcInsert 변수명 변경
greeng00se Apr 18, 2023
12d6e83
conflict 해결
greeng00se Apr 18, 2023
a957c5c
refactor: Controller에서 ResponseEntity 사용하도록 수정
greeng00se Apr 18, 2023
fbd5d7f
feat: Override 빠진 부분 추가
greeng00se Apr 18, 2023
9934292
test: 테스트 잘못 작성된 부분 수정
greeng00se Apr 18, 2023
3781e59
feat: CarEntity의 필드 final 적용
greeng00se Apr 19, 2023
446e7e4
test: 검증부 개선
greeng00se Apr 19, 2023
9e8e755
refactor: DAO에서 빈 아이디를 반환받는 경우 IllegalStateException을 던지도록 설정
greeng00se Apr 19, 2023
8adb76a
feat: IllegalStateException 예외 핸들링 추가
greeng00se Apr 19, 2023
f913f4e
feat: 통합 테스트 추가
greeng00se Apr 19, 2023
57d98cd
refactor: CarDao 메서드 변수명 변경
greeng00se Apr 19, 2023
4ac2929
feat: 조회시 transactional readOnly 설정
greeng00se Apr 19, 2023
04a45cd
test: 테스트 클래스명 변경
greeng00se Apr 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/racingcar/dao/CarDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import racingcar.entity.CarEntity;

public interface CarDao {
void saveAll(final List<CarEntity> players);
void saveAll(final List<CarEntity> cars);

List<CarEntity> findAll();
}
2 changes: 1 addition & 1 deletion src/main/java/racingcar/dao/ConsoleCarDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

public class ConsoleCarDao implements CarDao {
@Override
public void saveAll(final List<CarEntity> players) {
public void saveAll(final List<CarEntity> cars) {
}

@Override
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/racingcar/entity/CarEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import racingcar.domain.Car;

public class CarEntity {
private Integer id;
private String name;
private int position;
private boolean winner;
private Integer gameId;
private final Integer id;
private final String name;
private final int position;
private final boolean winner;
private final Integer gameId;

public CarEntity(final String name, final int position, final boolean winner, final Integer gameId) {
this(null, name, position, winner, gameId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
public class RacingGameExceptionHandler {
private static final String DELIMITER = ", ";

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ExceptionResponse> handleIllegalArgumentException(IllegalArgumentException e) {
@ExceptionHandler({IllegalArgumentException.class, IllegalStateException.class})
public ResponseEntity<ExceptionResponse> handleIllegalArgumentException(final RuntimeException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ExceptionResponse(e.getMessage()));
}
Expand All @@ -28,7 +28,7 @@ public ResponseEntity<ExceptionResponse> handleValidException(final MethodArgume
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ExceptionResponse> handleException(Exception e) {
public ResponseEntity<ExceptionResponse> handleException(final Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ExceptionResponse("서버가 응답할 수 없습니다."));
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/racingcar/service/RacingGameService.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ public GamePlayResponseDto play(final GamePlayRequestDto gameRequest) {

final GameEntity gameEntity = racingGameMapper.toGameEntity(gameRequest.getCount());
final int gameId = gameDao.saveAndGetId(gameEntity)
.orElseThrow(() -> new IllegalArgumentException("게임 저장에 실패하였습니다."));
.orElseThrow(() -> new IllegalStateException("게임 저장에 실패하였습니다."));

final List<CarEntity> carEntities = racingGameMapper.toCarEntities(game, gameId);
carDao.saveAll(carEntities);

return GamePlayResponseDto.of(game.findWinners(), game.getCars());
}

@Transactional
@Transactional(readOnly = true)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와,,,, 허브 굉장하시네요 👍
지금과 같이 하나의 클러스터를 사용하는 경우에는 영향이 없겠지만 나중에 규모있는 애플리케이션을 작성할 때는 항상 신경써야 하는 부분이에요. 정말 선행학습이 잘 되어있으시네요 👏

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

칭찬을 받으니 너무 좋습니다👍🏻 라빈의 기운 받고 꾸준히 정진 하겠습니다!!

public List<GamePlayResponseDto> findAll() {
final List<CarEntity> result = carDao.findAll();
return racingGameMapper.toGamePlayResponseDtos(result);
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/racingcar/dao/CarJdbcDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ void setUp() {

// when
final List<CarEntity> result = carDao.findAll();

// then
final CarEntity carEntity = result.get(0);
assertAll(
() -> assertThat(result).hasSize(2),
() -> assertThat(carEntity.getId()).isPositive(),
() -> assertThat(carEntity.getName()).isEqualTo("car1"),
() -> assertThat(carEntity.getPosition()).isEqualTo(1),
() -> assertThat(carEntity.isWinner()).isEqualTo(false),
() -> assertThat(carEntity.isWinner()).isFalse(),
() -> assertThat(carEntity.getGameId()).isEqualTo(gameId)
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/racingcar/dao/GameJdbcDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ void setUp() {
final Optional<Integer> gameId = gameDao.saveAndGetId(game);

// then
assertThat(gameId.isPresent()).isTrue();
assertThat(gameId).isPresent();
}
}
98 changes: 98 additions & 0 deletions src/test/java/racingcar/integration/RacingGameIntegrationTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package racingcar.integration;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.mockito.BDDMockito.given;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
import racingcar.dao.CarDao;
import racingcar.dao.GameDao;
import racingcar.domain.NumberGenerator;
import racingcar.dto.GamePlayRequestDto;
import racingcar.entity.CarEntity;
import racingcar.entity.GameEntity;

@SpringBootTest
@AutoConfigureMockMvc
@Transactional
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
public class RacingGameIntegrationTest {
Comment on lines +33 to +38
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요거는 저희 팀에서 관리하는 컨벤션인데요.
하나의 클래스에 다양한 어노테이션을 붙일 때 중요도가 높은 어노테이션일 수록 클래스 이름에 가깝게 명시해주고 있어요. 해당 클래스가 어떤 도움을 받고 있는지 빠르게 파악하기 위해서요.
중요도를 선정하는 기준은 스프링 프레임워크를 사용하고 있고, 해당 클래스가 빈인지, 설정 파일인지 등을 명시해주는 어노테이션을 가장 먼저 사용하고 그 이후에 부가적인 어노테이션들을 붙이고 있어요. 이 테스트 클래스를 기준으로 본다면 @SpringBootTest -> @AutoConfigureMockMvc -> @transactional -> @DisplayNameGeneration -> @SuppressWarnings 순이 되겠네요!
이 피드백은 반드시 따르거나 지켜야하는건 아니고 허브가 어떤 방식이 클래스를 봤을 때 무엇을 하는지 파악하기 쉬운지 고민해보셔도 좋을거 같아요 😉

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

중요도에 의해 순서를 결정하는 부분 좋은 것 같습니다. 확실히 해당 클래스가 어떤 애너테이션에 제일 영향을 많이 받는지 한 눈에 이해할 수 있을 것 같아요!
다음 미션부터 적용하도록 해보겠습니다!
실무에서 직접 사용하고 있는 컨벤션에 대한 부분을 리뷰로 남겨주셔서 정말 좋은 것 같습니다!👍🏻👍🏻👍🏻


@Autowired
private MockMvc mockMvc;

@Autowired
private ObjectMapper objectMapper;

@Autowired
private GameDao gameDao;

@Autowired
private CarDao carDao;

@MockBean
private NumberGenerator numberGenerator;

@Test
void 게임을_진행한다() throws Exception {
// given
final GamePlayRequestDto gamePlayRequestDto = new GamePlayRequestDto(List.of("비버", "허브"), 3);
final String request = objectMapper.writeValueAsString(gamePlayRequestDto);
given(numberGenerator.generate()).willReturn(7, 3, 3, 4, 5, 2);

// expect
mockMvc.perform(post("/plays")
.content(request)
.contentType(APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.winners", Matchers.contains("비버")))
.andExpect(jsonPath("$.racingCars", hasSize(2)))
.andExpect(jsonPath("$.racingCars[0].name", is("비버")))
.andExpect(jsonPath("$.racingCars[0].position").value(2))
.andExpect(jsonPath("$.racingCars[1].name", is("허브")))
.andExpect(jsonPath("$.racingCars[1].position").value(1))
.andDo(print());
assertThat(carDao.findAll()).hasSize(2);
}

@Test
void 게임_결과를_반환한다() throws Exception {
// given
final Integer gameId = gameDao.saveAndGetId(new GameEntity(9999)).get();
carDao.saveAll(List.of(
new CarEntity("비버", 5, true, gameId),
new CarEntity("허브", 4, false, gameId)
));

// expect
mockMvc.perform(get("/plays")
.contentType(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].winners", Matchers.contains("비버")))
.andExpect(jsonPath("$[0].racingCars", hasSize(2)))
.andExpect(jsonPath("$[0].racingCars[0].name", is("비버")))
.andExpect(jsonPath("$[0].racingCars[0].position").value(5))
.andExpect(jsonPath("$[0].racingCars[1].name", is("허브")))
.andExpect(jsonPath("$[0].racingCars[1].position").value(4))
.andDo(print());
}
}
13 changes: 13 additions & 0 deletions src/test/java/racingcar/service/RacingGameServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package racingcar.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
Expand Down Expand Up @@ -60,6 +61,18 @@ void setUp() {
);
}

@Test
void play_메서드_저장_후_빈_아이디를_반환받는_경우_IllegalStateException을_던진다() {
// given
final GamePlayRequestDto request = new GamePlayRequestDto(List.of("브리", "비버", "허브"), 1);
given(gameDao.saveAndGetId(any())).willReturn(Optional.empty());

// expect
assertThatThrownBy(() -> racingGameService.play(request))
.isInstanceOf(IllegalStateException.class)
.hasMessage("게임 저장에 실패하였습니다.");
}

@Test
void findAll_메서드는_게임_결과를_반환한다() {
// given
Expand Down