Business Layer, 비즈니스 계층은 서비스의 핵심 로직이 집중 되어 있는 계층이다. 서비스 로직은 보통 데이터를 다루므로 Business Layer은 단독 계층 테스트가 아닌, Persistence Layer 와 함께 통합적으로 테스트를 진행한다.
서비스 테스트를 진행하며, 테스트에 용이하게 로직을 개선하면서 전체적인 로직도 개선되는 선순환을 만들 수 있도록 해보자. ( 관심 있다면 TDD 개념도 살펴보면 좋을 것 같다.)
5.1. 기본적인 Business Layer Test
비즈니스 계층 테스트에서 테스트관련 새로운 내용은 상대적으로 없으나, 샘플로 작성한 서비스 로직, 엔티티 관계에 대해 먼저 살펴보고 서비스 테스트 내용을 보는 걸 추천한다.
5.1.1. Entity 준비
비즈니스 로직을 작성하기 앞서 엔티티 관계와 구현하고자 하는 서비스를 소개를 한다.
상품을 생성하면 엔티티 관계에 의해 주문상품 데이터 그리고 주문 데이터가 생성된다.
상품과 주문의 서비스 내용:
상품 서비스: 신규 상품 추가(생성)한다. 상품이 추가 생성될 때 상품 번호는 최근 상품의 상품 번호에서 1 이 증가한 값이다. 상품이 하나도 없는 경이 신규 상품이 추가되는 경우면 상품 번호는 ‘001’ 이 할당된다.
package msa.tc.order.domain;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import msa.tc.orderproduct.domain.OrderProductEntity;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "orders") // db 예약어이여서 달리 명시함
@Entity
public class OrderEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING)
private OrderStatus orderStatus;
private int totalPrice;
private LocalDateTime registeredDateTime;
// 연관관계 주인 (필드명으로 설정), Order 가 cud 될 때, 같이 반영 되도록 생명주기도 all 로 설정
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderProductEntity> orderProducts = new ArrayList<>();
}
package simple.testcode.order.domain;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum OrderStatus {
INIT("주문생성"),
CANCELED("주문취소"),
PAYMENT_COMPLETED("결제완료"),
PAYMENT_FAILED("결제실패"),
RECEIVED("주문접수"),
COMPLETED("처리완료");
private final String text;
}
package simple.testcode.orderproduct.domain;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import simple.testcode.order.domain.OrderEntity;
import simple.testcode.product.domain.ProductEntity;
import javax.persistence.*;
/**
* 중간 테이블
* Order - Product
*/
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "orderProduct")
public class OrderProductEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// ManyToOne 경우에는 필요한 시점에 데이터를 조회하기 위해, 보통 lazy fetch
@ManyToOne(fetch = FetchType.LAZY)
private OrderEntity order;
@ManyToOne(fetch = FetchType.LAZY)
private ProductEntity product;
public OrderProductEntity(OrderEntity order, ProductEntity product) {
this.order = order;
this.product = product;
}
}
5.1.2. Business Layer Test
비즈니스 계층 테스트에서는 스프링 컨테이너가 제공하는 서비스 빈부터 데이터 접근이 가능한 Bean 등 복수개의 계층에서 다양한 Bean들이 필요하다. @SpringBootTest 애노테이션을 사용하자.
상품 요청 관련 응답 객체 준비, 상품 관련 서비스, 상품서비스 테스트 추가
*request, *response, *serviceVo: service layer 전용, 책임 분리, 의존성 분리, 확장 용이성을 위함 (e.g. ProductRequest, ProductResponse, ProductServiceVo)
package msa.tc.product.dto;
import lombok.Builder;
import lombok.Getter;
import msa.tc.product.domain.ProductEntity;
import msa.tc.product.domain.ProductSellingStatus;
/**
* 상품 응답 전용 vo
*/
@Getter
public class ProductResponse {
private Long id;
private String productNumber;
private ProductSellingStatus sellingStatus;
private String name;
private int price;
@Builder
private ProductResponse(Long id, String productNumber, ProductSellingStatus sellingStatus, String name, int price) {
this.id = id;
this.productNumber = productNumber;
this.sellingStatus = sellingStatus;
this.name = name;
this.price = price;
}
// entity -> vo
public static ProductResponse of(ProductEntity product) {
return ProductResponse.builder()
.id(product.getId())
.productNumber(product.getProductNumber())
.sellingStatus(product.getSellingStatus())
.name(product.getName())
.price(product.getPrice())
.build();
}
}
package msa.tc.product.dto;
import lombok.Builder;
import msa.tc.product.domain.ProductEntity;
import msa.tc.product.domain.ProductSellingStatus;
/**
* 상품 서비스 전용 vo
* service layer 전용, 책임 분리, 의존성 분리, 확장 용이성
*/
public class ProductServiceVo {
private ProductSellingStatus sellingStatus;
private String name;
private int price;
@Builder
private ProductServiceVo(ProductSellingStatus sellingStatus, String name, int price) {
this.sellingStatus = sellingStatus;
this.name = name;
this.price = price;
}
public ProductEntity toEntity(String nextProductNumber) {
return ProductEntity.builder()
.productNumber(nextProductNumber)
.sellingStatus(sellingStatus)
.name(name)
.price(price)
.build();
}
}
package msa.tc.product.service;
import lombok.RequiredArgsConstructor;
import msa.tc.product.dao.ProductRepository;
import msa.tc.product.domain.ProductEntity;
import msa.tc.product.dto.ProductResponse;
import msa.tc.product.dto.ProductServiceVo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class ProductService {
private final ProductRepository productRepository;
@Transactional
public ProductResponse createProduct(ProductServiceVo productCreateServiceRequestVo) {
String nextProductNumber = this.createNextProductNumber();
ProductEntity product = productCreateServiceRequestVo.toEntity(nextProductNumber);
ProductEntity savedProduct = productRepository.save(product);
return ProductResponse.of(savedProduct);
}
/**
* 다음 상품 번호 generator
*/
private String createNextProductNumber() {
String latestProductNumber = productRepository. findLatestProductNumber();
if (latestProductNumber == null) {
return "001";
}
int latestProductNumberInt = Integer.valueOf(latestProductNumber);
int nextProductNumberInt = latestProductNumberInt + 1;
// 9 -> 009, 10 -> 010
return String.format("%03d", nextProductNumberInt);
}
}
상품 서비스 테스트 코드이다. 신규 상품 추가(생성) 서비스 에 대해 2가지 테스트를 진행하였으며, 예외 케이스(경계값) 도 포함되어 있다. 테스트명은 문장으로 작성하였다.
package msa.tc.product.service;
import msa.tc.product.dao.ProductRepository;
import msa.tc.product.domain.ProductEntity;
import msa.tc.product.domain.ProductSellingStatus;
import msa.tc.product.dto.ProductResponse;
import msa.tc.product.dto.ProductServiceVo;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
@SpringBootTest
class ProductServiceTest {
@Autowired
private ProductService productService;
@Autowired private ProductRepository productRepository;
@DisplayName("신규 상품을 추가한다. 상품번호는 가장 최근 상품의 상품번호에서 1 증가한 값이다.")
@Test
void createProduct() {
// given
ProductEntity product1 = createProduct("001", ProductSellingStatus.SELLING, "아메리카노", 4000);
productRepository.save(product1);
ProductServiceVo productServiceVo = ProductServiceVo.builder()
.sellingStatus(ProductSellingStatus.SELLING)
.name("카푸치노")
.price(5700)
.build();
// when
ProductResponse productResponse = productService.createProduct(productServiceVo);
// then
assertThat(productResponse)
.extracting("productNumber", "sellingStatus", "name", "price")
.contains("002", ProductSellingStatus.SELLING, "카푸치노", 5700);
// 상품 저장 확인
List<ProductEntity> products = productRepository.findAll();
assertThat(products).hasSize(2)
.extracting("productNumber", "sellingStatus", "name", "price")
.containsExactlyInAnyOrder(
tuple("001", ProductSellingStatus.SELLING, "아메리카노", 4000),
tuple("002", ProductSellingStatus.SELLING, "카푸치노", 5700)
);
}
@DisplayName("상품이 하나도 없는 경우, 신규 상품을 추가하면, 상품 번호는 001 이다.")
@Test
void createProductWhenProductsIsEmpty() {
clearData();
// given
// 기존의 product 데이터 있다면 지우기 (clearData() 에서 구현함)
ProductServiceVo productServiceVo = ProductServiceVo.builder()
.sellingStatus(ProductSellingStatus.SELLING)
.name("카푸치노")
.price(5700)
.build();
// when
ProductResponse productResponse = productService.createProduct(productServiceVo);
// then
assertThat(productResponse)
.extracting("productNumber", "sellingStatus", "name", "price")
.contains("001", ProductSellingStatus.SELLING, "카푸치노", 5700);
// 상품 저장 확인
List<ProductEntity> products = productRepository.findAll();
assertThat(products).hasSize(1)
.extracting("productNumber", "sellingStatus", "name", "price")
.contains(
tuple("001", ProductSellingStatus.SELLING, "카푸치노", 5700)
);
}
private void clearData() {
productRepository.deleteAllInBatch();
}
private ProductEntity createProduct(String productNumber, ProductSellingStatus sellingStatus, String name, int price) {
return ProductEntity.builder()
.productNumber(productNumber)
.sellingStatus(sellingStatus)
.name(name)
.price(price)
.build();
}
}
상품이 하나도 없는 경우, 신규 상품을 추가하면, 상품 번호는 001 이다. 테스트를 진행하기 위해, 데이터를 클렌징 하였다.
private void clearData() {
productRepository.deleteAllInBatch();
}
5.2. Business Layer Test 예제
Business Layer 테스트 작성 - 2 주문 관련 서비스, 서비스 테스트 추가
package msa.tc.order.domain;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import msa.tc.orderproduct.domain.OrderProductEntity;
import msa.tc.product.domain.ProductEntity;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "orders") // db 예약어이여서 달리 명시함
@Entity
public class OrderEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING)
private OrderStatus orderStatus;
private int totalPrice;
private LocalDateTime registeredDateTime;
// 연관관계 주인 (필드명으로 설정), Order 가 cud 될 때, 같이 반영 되도록 생명주기도 all 로 설정
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderProductEntity> orderProducts = new ArrayList<>();
private int calculateTotalPrice(List<ProductEntity> products) {
return products.stream()
.mapToInt(ProductEntity::getPrice)
.sum();
}
@Builder
public OrderEntity(List<ProductEntity> products, OrderStatus orderStatus, LocalDateTime registeredDateTime) {
this.orderStatus = orderStatus;
this.totalPrice = calculateTotalPrice(products);
this.registeredDateTime = registeredDateTime;
this.orderProducts = products.stream()
.map(product -> new OrderProductEntity(this, product))
.collect(Collectors.toList());
}
public static OrderEntity create(List<ProductEntity> products, LocalDateTime registeredDateTime) {
return OrderEntity.builder()
.orderStatus(OrderStatus.INIT)
.products(products)
.registeredDateTime(registeredDateTime)
.build();
}
}
package msa.tc.order.dto;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Getter
// 서비스 전용 vo
// service layer 전용, 책임 분리, 의존성 분리, 확장 용이성
public class OrderServiceVo {
private List<String> productNumbers;
@Builder
private OrderServiceVo(List<String> productNumbers) {
this.productNumbers = productNumbers;
}
}
package msa.tc.order.dto;
import lombok.Builder;
import lombok.Getter;
import msa.tc.order.domain.OrderEntity;
import msa.tc.product.dto.ProductResponse;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Getter
public class OrderResponse {
private Long id;
private int totalPrice;
private LocalDateTime registeredDateTime;
// Order 클래스의 맴버 orderProducts 를 그대로 사용하는 것 보단 응답 로직 전용 객체 ProductResponse 로 바꿔서 선언
private List<ProductResponse> products; // List<OrderProduct> orderProducts
@Builder
private OrderResponse(Long id, int totalPrice, LocalDateTime registeredDateTime, List<ProductResponse> products) {
this.id = id;
this.totalPrice = totalPrice;
this.registeredDateTime = registeredDateTime;
this.products = products;
}
public static OrderResponse of(OrderEntity order) {
return OrderResponse.builder()
.id(order.getId())
.totalPrice(order.getTotalPrice())
.registeredDateTime(order.getRegisteredDateTime())
// OrderProduct <<>> ProductResponse mapping 부분
.products(order.getOrderProducts().stream()
.map(orderProduct -> ProductResponse.of(orderProduct.getProduct()))
.collect(Collectors.toList())
)
.build();
}
}
package msa.tc.order.service;
import lombok.RequiredArgsConstructor;
import msa.tc.order.dao.OrderRepository;
import msa.tc.order.domain.OrderEntity;
import msa.tc.order.dto.OrderResponse;
import msa.tc.order.dto.OrderServiceVo;
import msa.tc.product.dao.ProductRepository;
import msa.tc.product.domain.ProductEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class OrderService {
private final ProductRepository productRepository;
private final OrderRepository orderRepository;
@Transactional
public OrderResponse createOrder(OrderServiceVo orderCreateServiceRequestVo, LocalDateTime registeredDateTime) {
// Product
List<String> productNumbers = orderCreateServiceRequestVo.getProductNumbers();
List<ProductEntity> products = findProductsBy(productNumbers);
// Order
OrderEntity order = OrderEntity.create(products, registeredDateTime);
OrderEntity savedOrder = orderRepository.save(order);
return OrderResponse.of(savedOrder);
}
/**
* 상품번호로 상품 조회, 중복되는 상품번호 목록으로 주문 가능
*/
private List<ProductEntity> findProductsBy(List<String> productNumbers) {
List<ProductEntity> products = productRepository.findAllByProductNumberIn(productNumbers);
Map<String, ProductEntity> productMap = products.stream()
.collect(Collectors.toMap(ProductEntity::getProductNumber, p -> p));
return productNumbers.stream()
.map(productMap::get)
.collect(Collectors.toList());
}
}
package msa.tc.order.service;
import msa.tc.order.dto.OrderRequest;
import msa.tc.order.dto.OrderResponse;
import msa.tc.order.dto.OrderServiceVo;
import msa.tc.product.dao.ProductRepository;
import msa.tc.product.domain.ProductEntity;
import msa.tc.product.domain.ProductSellingStatus;
import org.assertj.core.groups.Tuple;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class OrderServiceTest {
@Autowired
private OrderService orderService;
@Autowired
private ProductRepository productRepository;
@DisplayName("주문번호 리스트를 받아 주문을 생성한다.")
@Test
void createOrder() {
// given
LocalDateTime registeredDateTime = LocalDateTime.now();
ProductEntity product1 = this.createProduct("001", 1000);
ProductEntity product2 = this.createProduct("002", 2000);
ProductEntity product3 = this.createProduct("003", 7000);
productRepository.saveAll(List.of(product1, product2, product3));
OrderServiceVo orderServiceVo = OrderServiceVo.builder()
.productNumbers(List.of("001", "002"))
.build();
// when
OrderResponse orderResponse = orderService.createOrder(orderServiceVo, registeredDateTime);
// then
assertThat(orderResponse.getId()).isNotNull();
assertThat(orderResponse)
.extracting("registeredDateTime", "totalPrice")
.contains(registeredDateTime, 3000);
assertThat(orderResponse.getProducts()).hasSize(2)
.extracting("productNumber", "price")
.containsExactlyInAnyOrder(
Tuple.tuple("001", 1000),
Tuple.tuple("002", 2000)
);
}
private ProductEntity createProduct(String productNumber, int price) {
return ProductEntity.builder()
.productNumber(productNumber)
.price(price)
.sellingStatus(ProductSellingStatus.SELLING)
.name("샘플 메뉴 이름")
.build();
}
}
2023-10-12 10:13:29.220 DEBUG 71864 --- [ main] org.hibernate.SQL :
insert
into
product
(id, name, price, product_number, selling_status)
values
(default, ?, ?, ?, ?)
2023-10-12 10:13:29.224 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [샘플 메뉴 이름]
2023-10-12 10:13:29.224 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [1000]
2023-10-12 10:13:29.224 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [001]
2023-10-12 10:13:29.224 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [VARCHAR] - [SELLING]
2023-10-12 10:13:29.232 DEBUG 71864 --- [ main] org.hibernate.SQL :
insert
into
product
(id, name, price, product_number, selling_status)
values
(default, ?, ?, ?, ?)
2023-10-12 10:13:29.232 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [샘플 메뉴 이름]
2023-10-12 10:13:29.232 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [2000]
2023-10-12 10:13:29.232 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [002]
2023-10-12 10:13:29.232 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [VARCHAR] - [SELLING]
2023-10-12 10:13:29.233 DEBUG 71864 --- [ main] org.hibernate.SQL :
insert
into
product
(id, name, price, product_number, selling_status)
values
(default, ?, ?, ?, ?)
2023-10-12 10:13:29.233 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [샘플 메뉴 이름]
2023-10-12 10:13:29.233 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [7000]
2023-10-12 10:13:29.233 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [003]
2023-10-12 10:13:29.233 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [VARCHAR] - [SELLING]
2023-10-12 10:13:29.312 DEBUG 71864 --- [ main] org.hibernate.SQL :
select
productent0_.id as id1_2_,
productent0_.name as name2_2_,
productent0_.price as price3_2_,
productent0_.product_number as product_4_2_,
productent0_.selling_status as selling_5_2_
from
product productent0_
where
productent0_.product_number in (
? , ?
)
2023-10-12 10:13:29.312 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [001]
2023-10-12 10:13:29.313 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [002]
2023-10-12 10:13:29.313 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([id1_2_] : [BIGINT]) - [1]
2023-10-12 10:13:29.315 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([name2_2_] : [VARCHAR]) - [샘플 메뉴 이름]
2023-10-12 10:13:29.315 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([price3_2_] : [INTEGER]) - [1000]
2023-10-12 10:13:29.315 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([product_4_2_] : [VARCHAR]) - [001]
2023-10-12 10:13:29.315 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([selling_5_2_] : [VARCHAR]) - [SELLING]
2023-10-12 10:13:29.315 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([id1_2_] : [BIGINT]) - [2]
2023-10-12 10:13:29.315 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([name2_2_] : [VARCHAR]) - [샘플 메뉴 이름]
2023-10-12 10:13:29.315 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([price3_2_] : [INTEGER]) - [2000]
2023-10-12 10:13:29.315 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([product_4_2_] : [VARCHAR]) - [002]
2023-10-12 10:13:29.315 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([selling_5_2_] : [VARCHAR]) - [SELLING]
2023-10-12 10:13:29.323 DEBUG 71864 --- [ main] org.hibernate.SQL :
insert
into
orders
(id, created_date_time, modified_date_time, order_status, registered_date_time, total_price)
values
(default, ?, ?, ?, ?, ?)
2023-10-12 10:13:29.324 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [TIMESTAMP] - [null]
2023-10-12 10:13:29.324 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [TIMESTAMP] - [null]
2023-10-12 10:13:29.324 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [INIT]
2023-10-12 10:13:29.324 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [TIMESTAMP] - [2023-10-12T10:13:29.199453]
2023-10-12 10:13:29.325 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [5] as [INTEGER] - [3000]
2023-10-12 10:13:29.326 DEBUG 71864 --- [ main] org.hibernate.SQL :
insert
into
order_product
(id, order_id, product_id)
values
(default, ?, ?)
2023-10-12 10:13:29.327 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
2023-10-12 10:13:29.327 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [1]
2023-10-12 10:13:29.328 DEBUG 71864 --- [ main] org.hibernate.SQL :
insert
into
order_product
(id, order_id, product_id)
values
(default, ?, ?)
2023-10-12 10:13:29.328 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
2023-10-12 10:13:29.328 TRACE 71864 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [2]
'테스트코드 > 작성방법' 카테고리의 다른 글
7. End to End 테스트 (0) | 2024.03.26 |
---|---|
6. Presentation Layer Test (1) | 2024.03.26 |
4. 외부 시스템과의 연계 테스트 (1) | 2024.03.26 |
3. Persistence Layer Test (0) | 2024.03.26 |
2. Layered Architecture (0) | 2024.03.26 |