Skip to content

[1주차 과제] TDD로 Point 서비스 개발하기 #1

Merged
kilhyeonjun merged 16 commits intomasterfrom
feature/tdd
Oct 3, 2024
Merged

[1주차 과제] TDD로 Point 서비스 개발하기 #1
kilhyeonjun merged 16 commits intomasterfrom
feature/tdd

Conversation

@kilhyeonjun
Copy link
Copy Markdown
Contributor

@kilhyeonjun kilhyeonjun commented Sep 22, 2024

변경 사항

  • 포인트 기능 구현

    • 포인트 충전 기능
    • 포인트 사용 기능
    • 포인트 조회 기능
    • 포인트 히스토리 조회 기능
  • 포인트 기능 테스트 코드 작성

    • service의 비지니스 로직 유닛 테스트
    • command 또는 Entity 생성시 validation check 에 따른 유닛 테스트
    • repository -> DB 통합 테스트
    • service -> repository 통합 테스트
  • STEP1 요구사항 일부 구현

    • 포인트 충전, 사용에 대한 정책 추가 (잔고 부족, 최대 잔고 등) 및 테스트 코드 구현

리뷰 포인트

  • 코드적으로 궁금한 사항은 코멘트 남겨놨습니다. 확인 부탁드립니다!
  • 리뷰어님들이 생각했을 때 더 좋은 패턴이 보일 경우 공유해주시면 좋을 것 같습니다.
  • 테스트 코드의 추가로 있으면 좋을 부분 또는 불 필요해 보이는 부분도 확인 부탁드립니다.

TODO

  • 다음 PR에선 동시성 리펙토링 및 테스트 작성이 이루어질 예정입니다.

ps. force push 의 경우 기존에 작업중인 브랜치를 다시 master 기준으로 되돌려서 작업하는데 사용했습니다. (갈아 엎었어요 ㅠ)

@kilhyeonjun kilhyeonjun self-assigned this Sep 22, 2024
Comment on lines +11 to +13
if (userId == null) {
throw new BusinessException(PointErrorCode.INVALID_USER_ID);
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

validatior 객체를 만든다면 이 부분에서 호출하는 형식이 되는 걸까요?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

생성자로 만든다면 힘들수도 있지만, 제가 옛날에 봤던 코드는 validator를 함수 매개변수로 전달해서 그 함수 안에서 해당 validator를 호출하는 형식이었습니다.
예를 들면 checkUserId(UserValidator validator) { validator.checkUserId(userId); }
이런식이였던 것 같습니다. 방법은 다양하겠지만 command를 bean으로로 만들수는 없으니 위 방법으로 해결한 것 같았습니다.

Comment on lines +27 to +28
final var userPoint = pointRepository.findById(command.userId())
.orElseThrow(() -> new BusinessException(PointErrorCode.USER_POINT_NOT_FOUND));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

userPoint를 못찾았을 때의 에러처리를 JPA 컨벤션 처럼 find가 아닌 get이라는 prefix로 repository에 메소드를 만들어서 관리하는 게 좋았을 지 의문입니다.

Comment on lines +31 to +32
// NOTE: Test 간의 의존성을 없애기 위해 DirtiesContext를 사용합니다.
@DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

테스트간의 순서에 대한 의존성을 주기 싫어서 @DirtiesContext 어노테이션을 사용했습니다.
데이터를 지우는 메소드가 있었으면 @BeforeEach를 썼을 것 같은데 DirtiesContext은 빈을 새로 띄우는 걸로 알고 있어서 비용에 문제가 있을 것 같은데 좋은 패턴일지가 의문입니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

저도 데이터 지우는 메소드가 없어서 조금 고민했었는데
@ BeforeEach 를 사용해서 유저를 세팅하고 테스트가 시작될 때마다 동일한 초기 상태를 가지게 해봤습니다.
@ DirtiesContext 이건 몰랐었는데 하나 배워갑니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

저도 같은 고민을 했는데, DirtiesContext는 컨텍스트를 다시 띄워서 전체 테스트 실행 속도가 느려진다고 알고 있어서, @beforeeach에서 ReflectionUtils를 사용해서 UserPointTable의 내부를 비우는 식으로 작성했습니다. 이건 저희가 받은 프로젝트의 제한사항 때문에 위처럼 가져가도 크게 상관없지 않을까 생각되네요

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;

@SpringBootTest
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

통합 테스트를 작성할 때 classes 를 지정해주는게 좋을까요?
예를 들어 서비스의 통합테스트의 경우

@SpringBootTest(classes = {PointService.class, PointRepository.class, PointHistoryRepository.class})

이런식으로 명시를 해주는게 큰 의미가 있을 지 의문입니다.

서비스의 의존성이 바뀔때마다 해당 구문이 수정이 되어야해서 유지보수 측면에선 안좋지않을까 싶은데 성능적으로 유의미할지도 궁금합니다.

@kilhyeonjun
Copy link
Copy Markdown
Contributor Author

kilhyeonjun commented Sep 24, 2024

TDD 방식의 커밋 메시지의 방식이 궁금합니다.
예를 들어 charge 기능을 작업을 할려면 먼저 테스트 코드 작성 -> 기능 작성 -> 리펙토링 순서인데
지금 같은 경우는 기능 + 테스트 코드를 하나의 커밋에 담고 있습니다.
기능과 테스트 코드의 커밋 분리를 하는 게 리뷰어 입장에서 좋은 걸까요?
분리가 필요하다면 TDD 방식이기에 테스트 코드를 먼저 커밋해야하는지 아니면 기능을 먼저 커밋해야되는지도 의문입니다.

@kilhyeonjun kilhyeonjun marked this pull request as ready for review September 24, 2024 13:23
INVALID_AMOUNT(HttpStatus.BAD_REQUEST, "유효하지 않은 금액입니다."),
INVALID_UPDATE_MILLIS(HttpStatus.BAD_REQUEST, "유효하지 않은 업데이트 시간입니다."),
;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

예외처리 하는 방법 배워갑니다!

@kilhyeonjun kilhyeonjun merged commit b180057 into master Oct 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants