테스트 코드 하나에는 하나의 주제가 있어야 한다.
- 반복문이나 분기와 같은 논리구조가 나타나지 않아야 한다.
- ~ 하나의 DisplayName으로 만들수가 있는가?
잘못된 코드
@DisplayName("상품 타입이 재고 관련 타입인지를 체크한다.")
@Test
void containStockType() {
// given
ProductType[] productTypes = ProductType.values();
for (ProductType productType : productTypes) {
if (productType == ProductType.HANDMADE) {
//when
boolean result = ProductType.containsStockType(productType);
//then
assertThat(result).isFalse();
}
if (productType == ProductType.BAKERY || productType == ProductType.BOTTLE) {
// when
boolean result = ProductType.containsStockType(productType);
// then
assertThat(result).isTrue();
}
}
}
수정된 코드
@DisplayName("상품 타입이 재고 관련 타입인지를 체크한다.")
@Test
void containsStockType() {
// given
ProductType givenType = ProductType.HANDMADE;
// when
boolean result = ProductType.containsStockType(givenType);
// then
assertThat(result).isFalse();
}
@DisplayName("상품 타입이 재고 관련 타입인지를 체크한다.")
@Test
void containsStockType2() {
// given
ProductType givenType = ProductType.BAKERY;
// when
boolean result = ProductType.containsStockType(givenType);
// then
assertThat(result).isTrue();
}
개발자가 제어하기 힘든 문법을 사용해 테스트코드를 작성하는것을 지양한다.
ex) LocalDateTime.now(), Random <- 개발자가 제어할수 없음
@DisplayName("주문 생성 시 주문 등록 시간을 기록한다.")
@Test
void registeredDateTime() {
// given
LocalDateTime registeredDateTime = LocalDateTime.now();
List<Product> products = List.of(
createProduct("001", 1000),
createProduct("002", 2000)
);
// when
Order order = Order.create(products, registeredDateTime);
// then
assertThat(order.getRegisteredDateTime()).isEqualTo(registeredDateTime);
}
public Order createOrder() {
LocalDateTime currentDateTime = LocalDateTime.now();
LocalTime currentTime = currentDateTime.toLocalTime();
if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) {
throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요.");
}
return new Order(currentDateTime, beverages);
}
public Order createOrder(LocalDateTime currentDateTime) {
LocalTime currentTime = currentDateTime.toLocalTime();
if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) {
throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요.");
}
return new Order(currentDateTime, beverages);
}
테스트 환경에 다른 API, 의존성을 가지는것을 지양
@DisplayName("재고가 부족한 상품으로 주문을 생성하려는 경우 예외가 발생한다.")
@Test
void createOrderWithNoStock() {
// given
LocalDateTime registeredDateTime = LocalDateTime.now();
Product product1 = createProduct(BOTTLE, "001", 1000);
Product product2 = createProduct(BAKERY, "002", 3000);
Product product3 = createProduct(HANDMADE, "003", 5000);
productRepository.saveAll(List.of(product1, product2, product3));
Stock stock1 = Stock.create("001", 2);
Stock stock2 = Stock.create("002", 2);
stock1.deductQuantity(1); // todo
stockRepository.saveAll(List.of(stock1, stock2));
OrderCreateServiceRequest request = OrderCreateServiceRequest.builder()
.productNumbers(List.of("001", "001", "002", "003"))
.build();
// when // then
assertThatThrownBy(() -> orderService.createOrder(request, registeredDateTime))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("재고가 부족한 상품이 있습니다.");
}
두개의 Stock이 생겼고, 그중 하나를 뺐다
라는 과정을 이해해야한다.순수한 생성자, 혹은 빌더를 기반으로 객체를 생성하고 Given절을 구성한다.
서로다른 테스트코드간 영향을 주어선 안된다.
- 테스트간 순서가 없어야 한다. 각각 언제 수행되든 같은 결과를 도출해야 한다.
- 테스트 코드 밖의공유자원 사용을 지양해야한다.
주로 Given절을 다룰때 사용된다.
@DisplayName("재고와 관련된 상품이 포함되어 있는 주문번호 리스트를 받아 주문을 생성한다.")
@Test
void createOrderWithStock() {
// given
LocalDateTime registeredDateTime = LocalDateTime.now();
Product product1 = createProduct(BOTTLE, "001", 1000);
Product product2 = createProduct(BAKERY, "002", 3000);
Product product3 = createProduct(HANDMADE, "003", 5000);
productRepository.saveAll(List.of(product1, product2, product3));
...
}
@DisplayName("재고가 부족한 상품으로 주문을 생성하려는 경우 예외가 발생한다.")
@Test
void createOrderWithNoStock() {
// given
LocalDateTime registeredDateTime = LocalDateTime.now();
Product product1 = createProduct(BOTTLE, "001", 1000);
Product product2 = createProduct(BAKERY, "002", 3000);
Product product3 = createProduct(HANDMADE, "003", 5000);
...
}
공유 자원을 사용하지 않고도 중복된 given절을 줄일수 있다.
@BeforeAll
static void beforeAll() {
}
@BeforeEach
void setUp() {
}
문서
다. 파편화된 코드는 이해하기 힘들어진다.tearDown을 진행해 repository를 롤백하는 방법은 3개가 있다.
1. @Transactional
2. deleteAll
3. deleteAllInBatch
Jpa에서의 deleteAll 구현 코드
@Override
@Transactional
public void deleteAll() {
for (T element : findAll()) {
delete(element);
}
}