-
Notifications
You must be signed in to change notification settings - Fork 297
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단계 - 수강신청(도메인 모델) #667
2단계 - 수강신청(도메인 모델) #667
Conversation
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.
2단계 정말 잘 구현해주셨어요! 👍 💯
특별히 피드백 드릴부분없이 잘해주셨습니다!
소소한 피드백과 테스트관련해서 남겨드렸는데, 확인부탁드릴게요!
그리고 이제부턴 마크다운문법을 사용해 요구사항 체크리스트를 만들고 커밋을 세분화해서 구현해보시는것 추천드립니다! 😄
public class CoverImageResolution { | ||
private static final int MIN_WIDTH = 300; | ||
private static final int MIN_HEIGHT = 200; | ||
private static final int ASPECT_RATIO_WIDTH = 3; | ||
private static final int ASPECT_RATIO_HEIGHT = 2; |
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.
이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다.
👍
private static void validate(int width, int height) { | ||
validateWidthAndHeight(width, height); | ||
validateAspectRatioValid(width, height); | ||
} | ||
|
||
private static void validateWidthAndHeight(int width, int height) { | ||
boolean isValid = width >= MIN_WIDTH && height >= MIN_HEIGHT; | ||
|
||
if (!isValid) { | ||
throw new IllegalArgumentException("이미지의 width는 300픽셀, height는 200픽셀 이상이어야 합니다."); | ||
} | ||
} | ||
|
||
private static void validateAspectRatioValid(int width, int height) { | ||
boolean isValid = width * ASPECT_RATIO_HEIGHT == height * ASPECT_RATIO_WIDTH; | ||
|
||
if (!isValid) { | ||
throw new IllegalArgumentException("width와 height의 비율은 3:2여야 합니다."); | ||
} | ||
} |
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.
👍
public enum CoverImageType { | ||
GIF, | ||
JPG, | ||
PNG, | ||
SVG | ||
} |
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.
enum 👍
private LocalDateTime startedAt; | ||
private LocalDateTime endedAt; |
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.
수강기간으로 캡슐화할 수 있을것 같아요 🤔
public class CoverImage { | ||
|
||
private final CoverImageFileSize size; | ||
private final CoverImageType type; | ||
private final CoverImageResolution resolution; |
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.
👍
@Override | ||
public void validateRegistration(Session session, Money paymentAmount) { | ||
|
||
} |
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.
무료인 경우 검증이 없어서 빈칸으로 두신거군요!
최소한의 주석을 남겨두는것도 좋을것 같아요 😄 (자칫 커밋이 누락된걸로 오해할 수 있을것 같아요 :D)
public interface RegistrationPolicy { | ||
void validateRegistration(Session session, Money paymentAmount); | ||
} |
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.
유/무료 정책을 이렇게 구현해주셨군요 👍 👏
} | ||
|
||
public Payment register(NsUser nsUser, Money paymentAmount) { | ||
if (!sessionStatus.isRegistrable()) { |
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 CourseTest { | ||
|
||
} |
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.
테스트가 비었군요 😅
@Test | ||
void 유료_강의는_강의_최대_수강_인원을_초과할_수_없다() { | ||
int maxStudentCount = 1; | ||
int sessionFee = 20000; | ||
|
||
CoverImage coverImage = CoverImageTest.createCoverImage1(); | ||
LocalDateTime startedAt = LocalDateTime.of(2023, 10, 1, 0, 0, 0); | ||
LocalDateTime endedAt = LocalDateTime.of(2023, 10, 1, 23, 0, 0); | ||
|
||
Session session | ||
= SessionFactory.ofPaid(1, coverImage, SessionStatus.RECRUITING, sessionFee, maxStudentCount, startedAt, endedAt); | ||
|
||
IllegalArgumentException e = catchIllegalArgumentException(() -> { | ||
session.register(NsUserTest.JAVAJIGI, new Money(sessionFee)); | ||
session.register(NsUserTest.SANJIGI, new Money(sessionFee)); | ||
}); | ||
|
||
assertThat(e).hasMessage("강의 최대 수강 인원을 초과할 수 없습니다."); | ||
} |
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.
이 테스트에서 필요한 given 절은 무엇일까요?
유료여부, 최대수강인원 2가지만 given 절에 존재해야합니다!
지금은 이미지, 수강기간과 같은 불필요한 값들이 존재하군요 🤔
이렇게 불필요한 값들이 존재하면 테스트의 의도가 명확하지 않을 수 있고 팀원들이 봤을때를 생각해서 불필요한 값이 있다면 코드 읽는게 오래걸려 피곤해지기도하고 다른 팀원들의 시간도 소중하니 중요한 부분이라고 생각해요.
테스트 코드도 결국 우리가 함께 읽고 유지보수하는 소중한 자산이니, 핵심만 담아 더 명확하게 작성하면 어떨까요? 😊
@Test | |
void 유료_강의는_강의_최대_수강_인원을_초과할_수_없다() { | |
int maxStudentCount = 1; | |
int sessionFee = 20000; | |
CoverImage coverImage = CoverImageTest.createCoverImage1(); | |
LocalDateTime startedAt = LocalDateTime.of(2023, 10, 1, 0, 0, 0); | |
LocalDateTime endedAt = LocalDateTime.of(2023, 10, 1, 23, 0, 0); | |
Session session | |
= SessionFactory.ofPaid(1, coverImage, SessionStatus.RECRUITING, sessionFee, maxStudentCount, startedAt, endedAt); | |
IllegalArgumentException e = catchIllegalArgumentException(() -> { | |
session.register(NsUserTest.JAVAJIGI, new Money(sessionFee)); | |
session.register(NsUserTest.SANJIGI, new Money(sessionFee)); | |
}); | |
assertThat(e).hasMessage("강의 최대 수강 인원을 초과할 수 없습니다."); | |
} | |
@Test | |
void 유료_강의는_강의_최대_수강_인원을_초과할_수_없다() { | |
// Given - 필요한 값만 명시적으로 설정 | |
Session session = SessionBuilder.aPaidSession() | |
.withMaxStudentCount(1) | |
.withFee(20000) | |
.build(); | |
// When & Then | |
IllegalArgumentException e = catchIllegalArgumentException(() -> { | |
session.register(NsUserTest.JAVAJIGI, new Money(20000)); | |
session.register(NsUserTest.SANJIGI, new Money(20000)); | |
}); | |
assertThat(e).hasMessage("강의 최대 수강 인원을 초과할 수 없습니다."); | |
} |
이렇게 테스트픽스쳐를 만들어서 테스트에 필요한 부분에만 집중하고 검증할 수 있게 리팩토링해보는건 어떨까요? 🤔
도움이 될만한 글 남겨드릴게요
꼭 읽어보시고 다른 테스트코드도 각각 필요한 기능에 초점을 맞출 수 있게 작성 부탁드립니다 🙇
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.
@mskangg
좋은 리뷰 주셔서 감사합니다.
적절한 디폴트값을 주어서 처리하는 방법을 제안주신것 같네요.
이부분에 대해 고민이 많았는데 역시나 빌더 패턴이 필요하겠군요. 귀차니즘..
수정해보겠습니다.
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.
피드백 반영 정말 잘해주셨네요! 👍
이렇게 의견 나누니 리뷰가 즐겁군요 😄
그럼 다음 단계 진행해주세요! 🔥
if (!session.isStudentCountLessThan((int) maxStudentCount.getValue())) { | ||
throw new IllegalArgumentException("강의 최대 수강 인원을 초과할 수 없습니다."); | ||
} |
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.
그래서.. canAcceptMoreStudents과 같은 추상적인 이름을 가지는것 보다는 isStudentCountLessThan와 같은 구체적인 이름으로 작성하여 캡슐화의 목적도 이루고 RegistrationPolicy가 해당 책임을 가져가게끔 처리할 수 있을 것 같습니다.
이견 있으시면 말씀주세요.
동의합니다 👍
public class SessionBuilder { | ||
private Long id = 1L; | ||
private Course course; | ||
private CoverImage coverImage = CoverImageTest.createCoverImage1(); | ||
private SessionStatus sessionStatus; | ||
private RegistrationPolicy registrationPolicy; | ||
private SessionPeriod sessionPeriod = SessionPeriodTest.createSessionPeriod1(); |
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.
👍 💯
@Test | ||
void 유료_강의는_강의_최대_수강_인원을_초과할_수_없다() { | ||
int sessionFee = 20000; | ||
int maxStudentCount = 1; | ||
|
||
Session session = new SessionBuilder() | ||
.paid(sessionFee, maxStudentCount) | ||
.sessionStatus(SessionStatus.RECRUITING) | ||
.build(); | ||
|
||
IllegalArgumentException e = catchIllegalArgumentException(() -> { | ||
session.register(NsUserTest.JAVAJIGI, new Money(sessionFee)); | ||
session.register(NsUserTest.SANJIGI, new Money(sessionFee)); | ||
}); | ||
|
||
assertThat(e).hasMessage("강의 최대 수강 인원을 초과할 수 없습니다."); | ||
} |
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.
적절한 디폴트값을 주어서 처리하는 방법을 제안주신것 같네요.
이부분에 대해 고민이 많았는데 역시나 빌더 패턴이 필요하겠군요. 귀차니즘..
수정해보겠습니다.
이렇게 해두면 귀찮겠지만, 팀원들의 예쁨(?)을 모두 받으실겁니다 😄
다른글 하나더 링크 남길게요~!
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.
@mskangg
좋은 글 공유해주셔서 감사합니다.
수강 신청 기능 요구사항
과정(Course)은 기수 단위로 운영하며, 여러 개의 강의(Session)를 가질 수 있다.
강의는 시작일과 종료일을 가진다.
강의는 강의 커버 이미지 정보를 가진다.
이미지 크기는 1MB 이하여야 한다.
이미지 타입은 gif, jpg(jpeg 포함),, png, svg만 허용한다.
이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다.
강의는 무료 강의와 유료 강의로 나뉜다.
무료 강의는 최대 수강 인원 제한이 없다.
유료 강의는 강의 최대 수강 인원을 초과할 수 없다.
유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다.
강의 상태는 준비중, 모집중, 종료 3가지 상태를 가진다.
강의 수강신청은 강의 상태가 모집중일 때만 가능하다.
유료 강의의 경우 결제는 이미 완료한 것으로 가정하고 이후 과정을 구현한다.
결제를 완료한 결제 정보는 payments 모듈을 통해 관리되며, 결제 정보는 Payment 객체에 담겨 반한된다.
프로그래밍 요구사항
DB 테이블 설계 없이 도메인 모델부터 구현한다.
도메인 모델은 TDD로 구현한다.
단, Service 클래스는 단위 테스트가 없어도 된다.
다음 동영상을 참고해 DB 테이블보다 도메인 모델을 먼저 설계하고 구현한다.