12. 새로운 할인 정책 개발
서비스 개발 과정에서, 다음과 같은 상황이 발생했다고 가정해보자.
- 기획자: 회의를 해봤는데, 아무래도 할인 정책을 바꿔야 할 것 같아요. 고정 금액 할인보다는 전체 구매가에서 일정 비율로 할인하도록 만들어 주세요. 예를 들어 할인 비율을 10%로 정했다면, 총 구매금액이 20000원이면 2000원을, 15000원이면 1500을 할인해주는 식으로요.
- 개발자: 또 시작이군..
이와 같은 요구사항의 변경은 개발 과정에서 흔히 있는 일이지만, 그러한 상황에서 특히나 빛을 발하는 것이 바로 객체 지향 프로그래밍이다.
이번 포스트에서 지금까지 개발한 프로그램에 새로운 할인 정책을 추가해본다.
새로운 할인 정책 추가
사실 코드상 구현할 부분은 단순하다. DiscountPolicy 구현체를 하나 더 생성하면 된다.
discount 패키지에 RateDiscountPolicy 클래스를 생성한다.
RateDiscountPolicy.java
package hdxian.hdxianspringcore.discount;
import hdxian.hdxianspringcore.member.Grade;
import hdxian.hdxianspringcore.member.Member;
public class RateDiscountPolicy implements DiscountPolicy {
// 할인률은 10퍼센트
private int discountPercent = 10;
// 회원 등급이 VIP일 경우 10% 할인
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return price * discountPercent / 100;
}
else {
return 0;
}
}
}
할인률인 discountPercent 멤버변수와 member의 등급을 비교해서 할인된 가격을 리턴하는 discount() 메서드를 작성한다.
이전 FixDiscountPolicy와의 차이점이라면, FixDiscountPolicy의 discount()는 할인 액수(1000)를 리턴했지만, RateDiscountPolicy의 discount()는 할인이 적용된 금액을 리턴한다.
새로운 할인 정책 테스트
추가한 할인 정책에 대한 테스트 코드를 작성해보자.
test 폴더의 core 패키지 하위에 discount 패키지를 생성하고, RateDiscountPolicyTest 클래스를 생성한다.
또는 intelliJ에서 제공하는 기능을 이용해 테스트 클래스를 생성한다.
ctrl + shift + t 단축키를 이용한다.
[Create New Test...]를 선택한다.
라이브러리는 JUint5, 클래스명은 본래 클래스 이름에 Test를 붙여준다.
나머진 건들지 않았다.
테스트 클래스의 패키지 경로는 기본적으로 test 폴더 하위에 똑같은 경로로 생성해준다.
테스트 코드를 작성한다.
RateDiscountPolicyTest.java
package hdxian.hdxianspringcore.discount;
import hdxian.hdxianspringcore.member.Grade;
import hdxian.hdxianspringcore.member.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class RateDiscountPolicyTest {
RateDiscountPolicy discountPolicy = new RateDiscountPolicy();
@Test
@DisplayName("VIP는 10% 할인이 적용되어야 합니다.")
void vip_o() {
// given
Member member = new Member(1L, "memberVIP", Grade.VIP);
// when
int discount = discountPolicy.discount(member, 10000);
// then
Assertions.assertThat(discount).isEqualTo(1000);
}
@Test
@DisplayName("VIP가 아니면 할인이 적용되지 않아야 합니다.")
void vip_x() {
// given
Member member = new Member(2L, "memberBASIC", Grade.BASIC);
// when
int discount = discountPolicy.discount(member, 10000);
// then
Assertions.assertThat(discount).isEqualTo(0);
}
}
@DisplayName 어노테이션을 통해 테스트 케이스의 이름을 지정해줄 수 있다.
테스트 메서드들을 보면, vip_o와 vip_x 두 개로 구성하였다.
vip 등급에 대해 할인이 적용되는지 테스트하는 것도 중요하지만,
일반 등급에 대해 할인이 적용되지 않는 것도 테스트해야 제대로 된 검증이 가능하다.
만약 등급에 상관없이 모두 할인해주는 오류가 존재하는데, vip 대상 할인 여부만 테스트한다면 그러한 오류는 잡을 수 없기 때문이다.
즉 테스트에 대한 성공, 실패 케이스를 모두 고려해 테스트 코드를 작성해야 함에 주의해야 한다.
+) MemberRepository는?
지금 작성한 RateDiscountPolicyTest는 이전의 OrderServiceTest와 달리 회원을 저장하고 조회하는 MemberRepository가 없는데, 할인 정책은 MemberRepository에 의존하지 않기 때문이다.
DiscountPolicy는 그저 전달받은 member의 회원 등급에 따라 할인 여부를 결정하는 역할만 한다.
따라서 DiscountPolicy에 대한 테스트 코드 역시 임의로 Member 객체를 생성하고 이에 대한 할인을 계산하는 코드만 포함되어야 한다.
다음 포스트에서는 추가한 할인 정책을 실제로 적용해보고, 우리가 작성한 프로그램이 정말로 객체 지향 원칙을 잘 준수했는지 생각해본다.
미리 말하자면 DIP, OCP 원칙에 위배되어 객체 지향 원칙이 지켜지지 않는데, 이를 해결하는 과정에서 자연스레 스프링 컨테이너가 왜 만들어졌는지 이해하는 것이 이번 섹션의 목표다.