
添加折扣政策商品价格满 30,000 韩元时,自动优惠 1,000 韩元。 (例如,输入 35,000 韩元,保存为 34,000 韩元。)
if 语句实现这项功能。不过,既然已经学习了以领域为中心的设计,就让 **Product(领域)**对象自行决定是否打折。Product 对象。
虽然折扣逻辑尚未实现,但我们先写一个断言折扣应当生效的测试。package com.example.tdd_study.product;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class ProductTest {
@Test
void 가격이_3만원_이상이나면_1000원_할인된다() {
// given
String name = "비싼 커피";
int originalPrice = 35000;
int expectedPrice = 34000; // 1000원 할인 기대
System.out.println("[Test Start] 할인 정책 테스트 시작");
System.out.println("입력 가격: " + originalPrice);
// when
Product product = new Product(name, originalPrice);
// then
System.out.println("검증 결과: 실제 저장된 가격 = " + product.getPrice());
assertThat(product.getPrice()).isEqualTo(expectedPrice);
System.out.println("[Test Pass] 테스트 성공! 할인이 정상 적용됨");
}
@Test
void 가격이_3만원_미만이면_할인이_없다() {
// given
int price = 20000;
// when
Product product = new Product("일반 커피", price);
// then
assertThat(product.getPrice()).isEqualTo(price);
}
}35000 韩元。
控制台会输出 검증 결과: 실제 저장된 가격 = 35000,并因实际值与期望值(34000)不符而报错。
Product 类,让测试通过。
在构造函数中判断价格并应用折扣。package com.example.tdd_study.product;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.util.Assert;
@Entity
@Table(name = "products")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int price;
public Product(String name, int price) {
Assert.hasText(name, "상품명은 필수입니다.");
Assert.isTrue(price > 0, "상품 가격은 0보다 커야 합니다.");
Assert.isTrue(price % 1000 == 0, "상품 가격은 1000원 단위여야 합니다.");
// [추가] 할인 정책 적용
if (price >= 30000) {
System.out.println("[Logic] 3만원 이상 감지! 1000원 할인 적용함.");
price = price - 1000;
}
this.name = name;
this.price = price;
}
}ProductTest。
[Test Start] 할인 정책 테스트 시작
입력 가격: 35000
[Logic] 3만원 이상 감지! 1000원 할인 적용함.
검증 결과: 실제 저장된 가격 = 34000
[Test Pass] 테스트 성공! 할인이 정상 적용됨package com.example.tdd_study.product;
import com.example.tdd_study.notification.NotificationCenter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.LocalDateTime;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class ProductServiceTest {
@Mock
private ProductRepository productRepository;
@Mock // [중요] 이 부분이 없어서 NullPointerException이 발생했습니다.
private NotificationCenter notificationCenter;
@InjectMocks
private ProductService productService;
@Test
void 상품_등록시_고가_상품은_할인되어_저장된다() {
// given
String name = "고급 시계";
int inputPrice = 40000; // 3만원 이상
LocalDateTime fixedTime = LocalDateTime.of(2025, 1, 1, 12, 0);
System.out.println(">>> Service 테스트 시작: " + name + ", 가격: " + inputPrice);
// when
productService.register(name, inputPrice, fixedTime);
// then
// Repository.save()가 호출될 때, 넘겨진 Product 객체를 낚아챕니다(Capture).
ArgumentCaptor<Product> productCaptor = ArgumentCaptor.forClass(Product.class);
verify(productRepository).save(productCaptor.capture());
// [추가] 알림 발송도 검증 (여기서 NPE가 났었음)
verify(notificationCenter).send(any(String.class));
Product savedProduct = productCaptor.getValue();
System.out.println(">>> Repository에 저장된 가격: " + savedProduct.getPrice());
// 40000원이 아닌 39000원이 저장되어야 정상 (1000원 할인)
assertThat(savedProduct.getPrice()).isEqualTo(39000);
System.out.println(">>> Service 테스트 통과 완료");
}
// 기존 테스트 (영업시간 관련)도 함께 유지해야 합니다.
@Test
void 영업시간_외에는_상품_등록이_불가능하다() {
// given
String name = "아메리카노";
int price = 4000;
LocalDateTime fixedTime = LocalDateTime.of(2025, 1, 1, 1, 0); // 새벽 1시
System.out.println(">>> 영업시간 외 테스트 시작 (시간: " + fixedTime + ")");
// when & then
assertThatThrownBy(() -> productService.register(name, price, fixedTime))
.isInstanceOf(IllegalStateException.class)
.hasMessage("영업 시간이 아닙니다.");
System.out.println(">>> 예외 발생 확인 완료");
}
}
>>> Service 테스트 시작: 고급 시계, 가격: 40000
[Logic] 3만원 이상 감지! 1000원 할인 적용함.
>>> Repository에 저장된 가격: 39000
>>> Service 테스트 통과 완료System.out.println,直观地查看测试开始、逻辑执行和验证结果。Product 的价格)。下一篇将比较单元测试与集成测试的职责,梳理何时编写哪一种测试更有效。