- 기능 구현을 원하는 요구사항을 검증하는 테스트 추가
- 테스트를 만족하도록 기능 구현
- 이 때, 테스트는 한번에 많은 Scope 를 잡지 않는다. 이는 불필요한 개발 생산성 저하로 이어진다.
- 이 시점에서 우리가 몇 배의 시간을 더 투자한다고 해서 테스트 커버리지 100% 를 달성할 수는 없다.
- 구현된 기능에 대한 리팩토링 (인터페이스 구성, 코드 클리닝 등 코드 베이스 정리)
- 모든 코드를 테스트 가능하게 구현하는 것을 목표로 진행합니다.
- 모든 테스트 케이스가 성공했다는 것은 목표한 기능이 완성되었다는 것을 의미합니다.
- 테스트 커버리지 100% 가 아니라, 정확히 기능의 동작을 확인하는 테스트를 작성해 주세요.
- 주요 기능에서
private접근자, 객체간의 강결합 같이 테스트 불가능한 코드는 가능한 한 지양하는 것이 좋습니다.
- PATCH
/point/{id}/charge: 포인트를 충전한다. - PATCH
/point/{id}/use: 포인트를 사용한다. - GET
/point/{id}: 포인트를 조회한다. - GET
/point/{id}/histories: 포인트 내역을 조회한다. - 잔고가 부족할 경우, 포인트 사용은 실패해야 한다.
- 동시에 여러 건의 포인트 충전, 이용 요청이 들어올 경우 순차적으로 처리되어야 한다. (동시성 고려)
build.gradle은 수정하지 않는다 (추가적인 dependencies를 사용하지 않는다)- 초기에 주어진 DB 로직(
UserPointTable,PointHistoryTable은 수정하지 않는다)
- Transaction이 적용되어 있지 않기 때문에 각 기능의 원자성(atomicity)이 보장되지 않는다.
- ex. 포인트 충전(
PointService.chargePoint()) 중 포인트 충전 내역 저장(PointHistoryRepository.save()) 기능에서 에러가 발생했을 때, 충전된 포인트를 rollback 해야 함.
- ex. 포인트 충전(
- 현재 단일 유저에 대한 동시성 제어만 진행하고 있기 때문에 동시에 여러 유저로부터 포인트 충전/사용 요청이 왔을 경우
PointHistoryTable에서 문제가 발생할 수 있음.PointHistoryTable의cursor를 동시에 조회함으로써 PK 역할을 하는cursor가 중복되어 저장될 수 있음.- 다만, 이 부분은 DB 접근/구현 계층에서 고려해야 할 역할이라고 판단하였고, 이번 미션에서는 "database" package 코드를 수정하지 않는 것을 요구했으므로 무시하고 진행.
AtomicLong처럼 thread-safe data type을 쓴다던지, 실제 DBMS를 사용한다면 충분히 해결할 수 있고, 해당 계층에서 책임져야 할 역할이라고 생각하였음.