-
Notifications
You must be signed in to change notification settings - Fork 0
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
레디 방탈출 예약 관리 1 ~ 6 단계 #3
base: reddevilmidzy
Are you sure you want to change the base?
레디 방탈출 예약 관리 1 ~ 6 단계 #3
Conversation
* feat: 메인 화면 생성 * chore: devtools 추가 * fix: 메인 화면 조회 오류 수정 * feat: 예약 홈 화면 조회 구현 * test: 예약 홈 화면 조회 테스트 추가 * test: 예약 화면 조회 테스트 생성 * feat: Reservation 객체 생성 * feat: reservation 목록 조회 메서드 생성 * feat: 예약 목록 조회 기능 구현 * fix: html 수정 내역 삭제 * refactor: Reservation 컨트롤러 추가 및 서비스 삭제 * feat: 예약 추가 기능 구현 * feat: 예약 삭제 기능 구현 * refactor: @controller 대신 @RestController로 변경 * refactor: reservationId 변수명 변경 * refactor: 존재하지 않은 예약 삭제 시 404 반환 * test: test 변수 new HashMap에서 Mpt.of로 변경 * test: test 변수 new HashMap에서 Map.of로 변경 * refactor: 예약 저장시 반환타입 ResponseEntity 래핑 제거 * style: 애노테이션과 final 순서 변경 * refactor: 예약 저장 메서드 내 흐름 순서 변경 * refactor: 예약 반환, 응답 dto 레코드이름에서 dto 제거 * refactor: 예약 저장 메서드이름 변경 * test: 존재하지 않는 예약 삭제 테스트 추가 * refactor: AdminControllerTest에 있던 예약 테스트 ReservationControllerTest로 이동 * test: 미션별 단계 테스트 추가 * test: 예약 추가 및 삭제 테스트 분리, 및 RestAssured 대신 assertj 사용 * fix: html의 링크 오류 수정 * fix: 테스트 시 post 변경 * style: 메서드 순서 변경 * test: dto 변환 테스트 추가 * refactor: toEntity 대신 toReservation 으로 메서드명 변경 * fix: AdminControllerTest 테스트 port 설정 * style: 디미터 법칙에 따라 개행 추가 --------- Co-authored-by: juha <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
하이 레디! 커찬이야 (글을 편하게 쓰기 위해서 반말로 작성했어)
내가 궁금한 점들을 정리해서 코멘트 달아봤어!
9개만 단 것은, 다른 크루가 리뷰할걸 남겨놓기 위해...
레디 고생많았어! 7~9 단계도 화이팅!
# 방탈출 예약 관리 | ||
|
||
## API 명세 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 👍 👍
implementation 'org.springframework.boot:spring-boot-devtools' | ||
runtimeOnly 'com.h2database:h2' | ||
testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
testImplementation 'io.rest-assured:rest-assured:5.3.1' | ||
testRuntimeOnly 'com.h2database:h2' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
각각 라이브러리가 있는 것과 없는 것에는 어떤 차이가 있어?
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); | ||
|
||
public static ReservationResponse from(final Reservation reservation) { | ||
final String date = reservation.getDate().format(DateTimeFormatter.ISO_DATE); | ||
final String time = reservation.getTime().format(TIME_FORMATTER); | ||
return new ReservationResponse(reservation.getId(), reservation.getName(), date, time); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
약간 억지(?)일수도 있다고 생각하는데, ISO_DATE 보다는 직접 글자로 명시해주는게 더 직관적일수 있다 생각하는데, 레디는 어떻게 생각해?
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); | |
public static ReservationResponse from(final Reservation reservation) { | |
final String date = reservation.getDate().format(DateTimeFormatter.ISO_DATE); | |
final String time = reservation.getTime().format(TIME_FORMATTER); | |
return new ReservationResponse(reservation.getId(), reservation.getName(), date, time); | |
} | |
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | |
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); | |
public static ReservationResponse from(final Reservation reservation) { | |
final String date = reservation.getDate().format(DATE_FORMATTER); | |
final String time = reservation.getTime().format(TIME_FORMATTER); | |
return new ReservationResponse(reservation.getId(), reservation.getName(), date, time); | |
} |
@DisplayName("db 연결 확인") | ||
@Test | ||
void checkConnection() { | ||
try (final Connection connection = Objects.requireNonNull(jdbcTemplate.getDataSource()).getConnection()) { | ||
assertThat(connection).isNotNull(); | ||
assertThat(connection.getCatalog()).isEqualTo("DATABASE_TEST"); | ||
assertThat(connection.getMetaData().getTables(null, null, "RESERVATION", null).next()).isTrue(); | ||
} catch (final SQLException e) { | ||
throw new IllegalStateException(e); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- catch를 하면 예외를 던지기 보다는, 테스트를 실패하게 하는건 어떨까?
- 아래 메서드를 실행해보면 DB가 정상적으로 연결되는지 확인할 수 있는데, 해당 테스트가 크게 의미 있을까? 메서드들의 정상동작만 테크하면 되지 않을까?
@DisplayName("예약 삭제") | ||
@Test | ||
void removeReservation() { | ||
final List<Reservation> beforeSaving = reservationDao.findAll(); | ||
final Reservation reservation = Reservation.create("레디", "2024-02-03", "15:00"); | ||
reservationDao.save(reservation); | ||
final List<Reservation> afterSaving = reservationDao.findAll(); | ||
reservationDao.remove(1L); | ||
final List<Reservation> afterRemoving = reservationDao.findAll(); | ||
|
||
assertAll( | ||
() -> assertThat(beforeSaving).isEmpty(), | ||
() -> assertThat(afterSaving).hasSize(1), | ||
() -> assertThat(afterRemoving).isEmpty() | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 테스트는 save()
, remove()
가 모두 정상 동작해야만 통과하는데, 이걸 분리해보는 건 어떨까?
@DisplayName("단건 조회") | ||
@Test | ||
void findById() { | ||
//given | ||
final Reservation reservation = Reservation.create("레디", "2024-02-03", "12:00"); | ||
final Reservation expected1 = reservation.toReservation(1L); | ||
|
||
//when | ||
reservationDao.save(reservation); | ||
final Optional<Reservation> findReservation1 = reservationDao.findById(1L); | ||
|
||
//then | ||
assertThat(findReservation1).contains(expected1); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DisplayName("단건 조회") | |
@Test | |
void findById() { | |
//given | |
final Reservation reservation = Reservation.create("레디", "2024-02-03", "12:00"); | |
final Reservation expected1 = reservation.toReservation(1L); | |
//when | |
reservationDao.save(reservation); | |
final Optional<Reservation> findReservation1 = reservationDao.findById(1L); | |
//then | |
assertThat(findReservation1).contains(expected1); | |
} | |
@DisplayName("단건 조회") | |
@Test | |
void findById() { | |
//given | |
final Reservation reservation = Reservation.create("레디", "2024-02-03", "12:00"); | |
final Reservation expected = reservation.toReservation(1L); | |
//when | |
reservationDao.save(reservation); | |
final Optional<Reservation> findReservation = reservationDao.findById(1L); | |
//then | |
assertThat(findReservation).contains(expected); | |
} |
final List<Reservation> expected = new ArrayList<>(); | ||
for (long i = 1; i <= 3; i++) { | ||
expected.add(reservations.get((int) (i - 1)).toReservation(i)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final List<Reservation> expected = new ArrayList<>(); | |
for (long i = 1; i <= 3; i++) { | |
expected.add(reservations.get((int) (i - 1)).toReservation(i)); | |
} | |
final List<Reservation> expected = List.of( | |
reservation1.toReservation(1L), reservation2.toReservation(2L), reservation3.toReservation(3L) | |
); |
final URI uri = UriComponentsBuilder.fromPath("/reservations/{id}") | ||
.buildAndExpand(reservationResponse.id()) | ||
.toUri(); | ||
return ResponseEntity.created(uri) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
레디가 내 리뷰에도 남겨줬던것 같은데, 나는 오히려 해당 표현이 불필요하게 길어진다는 느낌을 받는 것 같아.
'+' 기호를 써서 URI를 만드는 것보다 위 표현이 어느 관점에서 더 좋다고 생각해?
@SpringBootApplication | ||
public class RoomescapeApplication { | ||
public static void main(String[] args) { | ||
|
||
public static void main(final String[] args) { | ||
SpringApplication.run(RoomescapeApplication.class, args); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final
광인의 면모를 다시 느끼고 갑니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
레디 미션하느라 고생 많았어~~
코멘트 남겼으니까 확인해줘!
아직 9단계까지 남았겠지? 끝까지 파이팅해 🔥🔥
return Optional.ofNullable(reservation); | ||
} catch (final EmptyResultDataAccessException exception) { | ||
return Optional.empty(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EmptyResultDataAccessException
만 예외 처리한 이유가 무엇인지 궁금해! 그리고 EmptyResultDataAccessException
가 발생할 수 있다는 것을 어떻게 알았는지도 궁금해
public static Reservation create(final String name, final String date, final String time) { | ||
return new Reservation(null, name, date, time); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
생성자가 아닌 정적 팩토리 메서드를 만든 이유가 있을까? 그리고 메서드 명을 create로 한 것도 이유가 궁금해!
void step6() { | ||
final Map<String, String> params = Map.of( | ||
"name", "브라운", | ||
"date", "2023-08-05", | ||
"time", "10:00"); | ||
|
||
RestAssured.given().log().all() | ||
.contentType(ContentType.JSON) | ||
.body(params) | ||
.when().post("/reservations") | ||
.then().log().all() | ||
.statusCode(201) | ||
.header("Location", "/reservations/1"); | ||
|
||
final Integer count = jdbcTemplate.queryForObject("SELECT COUNT(1) FROM reservation", Integer.class); | ||
assertThat(count).isEqualTo(1); | ||
|
||
RestAssured.given().log().all() | ||
.when().delete("/reservations/1") | ||
.then().log().all() | ||
.statusCode(204); | ||
|
||
final Integer countAfterDelete = jdbcTemplate.queryForObject("SELECT COUNT(1) FROM reservation", Integer.class); | ||
assertThat(countAfterDelete).isZero(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
믿고 보는 레디의 테스트 👍 개인적으로 인사이트를 얻었어 ㅎㅎ 고마워!
class ReservationRequestTest { | ||
|
||
@DisplayName("dto 변환") | ||
@Test | ||
void toReservation() { | ||
final long id = 1L; | ||
final String name = "레디"; | ||
final String date = "2024-04-18"; | ||
final String time = "13:00"; | ||
final ReservationRequest reservationRequest = new ReservationRequest(name, date, time); | ||
|
||
final Reservation reservation = new Reservation(id, name, date, time); | ||
|
||
assertThat(reservationRequest.toReservation(id)).isEqualTo(reservation); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dto 테스트를 작성하게 된 스토리가 궁금한 걸? 나는 테스트하지 않아도 괜찮다고 생각해서 하지 않았거든!
public Reservation toReservation(final long id) { | ||
return new Reservation(id, name, date, time); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
개인적으로 이 메서드명도 의미가 명확하지 않다고 느껴지는 것 같아!
예를 들어, reservation.toReservation(id)
이런 식으로 코드를 작성할 것 같은데, 이 문장 자체는 이미 존재하는 reservation을 toReservation한다?
예약을 예약으로 변경하는 것으로 느껴져서 어색하다고 생각해
@Override | ||
public boolean equals(final Object target) { | ||
if (this == target) { | ||
return true; | ||
} | ||
if (target == null || getClass() != target.getClass()) { | ||
return false; | ||
} | ||
final Reservation reservation = (Reservation) target; | ||
return Objects.equals(getId(), reservation.getId()) && Objects.equals(getName(), reservation.getName()) | ||
&& Objects.equals(getDate(), reservation.getDate()) && Objects.equals(getTime(), | ||
reservation.getTime()); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(getId(), getName(), getDate(), getTime()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
적절한 동등성 부여인지 고민이 필요할 것 같아! id만 같으면 동등한 객체로 보아도 괜찮지 않을까?
감사합니당