*책 내용과 다르게, 다음과 같은 환경에서 프로젝트 생성
- Windows11(윈도우 11) 환경
- 자바 JDK 17 버전 설치 https://yungenie.tistory.com/11
[Java] 차근차근 Java 설치하기 (JDK17, Window 11)
자바 개발 도구 설치 방법에 대해서 알아보겠습니다. Java17은 LTS(Long Term Support : 장기 지원) 릴리즈로 1년 후까지 기술 지원 및 버그를 개선한 서비스를 제공받을 수 있습니다. 업데이트 버전을 꾸
yungenie.tistory.com
- 스프링 부트 4.31.0 사용 - STS(Spring Tool Suite) 설치(Spring Tools for Eclipse - https://spring.io/tools)
=> https://priming.tistory.com/147 참고
[Windows] Spring Tool Suite 4(STS 4) 다운로드 및 설치
STS란?Spring Tool Suite(STS)는 스프링 프로젝트를 생성하고, 개발할 수 있게 해주는 도구입니다. STS 설치 과정에 대해 설명드리겠습니다. 설치 파일 다운로드STS 공식 사이트에서 설치 파일을 다운로
priming.tistory.com
- MySQL Community Server 8.0.42 설치 https://dev.mysql.com/downloads/mysql/
MySQL :: Download MySQL Community Server
Select Version: 9.3.0 Innovation 8.4.5 LTS 8.0.42 Select Operating System: Select Operating System… Microsoft Windows Ubuntu Linux Debian Linux SUSE Linux Enterprise Server Red Hat Enterprise Linux / Oracle Linux Fedora Linux - Generic Oracle Solaris mac
dev.mysql.com
- Gradle
**STS에서 Gradle 프로젝트 생성한 과정
*** 함께 보면 좋은 글
스프링 부트 핵심 가이드(장정우 지음) - 스프링 부트 개요
1. 스프링 프레임워크자바(Java) 기반 애플리케이션 프레임워크로, 엔터프라이즈급(기업 환경 대상 개발) 애플리케이션을 위한 다양한 기능 제공-> 오픈소스 경량급 애플리케이션 프레임워크로
keep-programming-study.tistory.com
스프링 부트 핵심 가이드(장정우 지음) - 개발에 앞서 알면 좋은 기초 지식
1. 서버 간 통신마이크로서비스 아키텍처에서 한 서버가 다른 서버에 통신을 요청하는 것을 의미-> 한 대는 서버/다른 한 대는 클라이언트가 됨 가장 많이 사용되는 방식은 HTTP/HTTPS 방식(TCP/IP, SOA
keep-programming-study.tistory.com
스프링 부트 핵심 가이드(장정우 지음) - REST API 명세를 문서화하는 방법(Swagger), 로깅 라이브러리
*책 내용과 다르게, 다음과 같은 환경에서 프로젝트 생성 Windows11(윈도우 11) 환경자바 JDK 17 버전 설치 https://yungenie.tistory.com/11 [Java] 차근차근 Java 설치하기 (JDK17, Window 11)자바 개발 도구 설치 방법
keep-programming-study.tistory.com
스프링 부트 핵심 가이드(장정우 지음) - 테스트 코드 작성하기
*책 내용과 다르게, 다음과 같은 환경에서 프로젝트 생성 Windows11(윈도우 11) 환경자바 JDK 17 버전 설치 https://yungenie.tistory.com/11 [Java] 차근차근 Java 설치하기 (JDK17, Window 11)자바 개발 도구 설치 방법
keep-programming-study.tistory.com
4. JUnit을 활용한 테스트 코드 작성
1) JUnit의 세부 모듈
(1) JUnit Platform
- JVM에서 테스트를 시작하기 위한 뼈대 역할
- 테스트를 발견하고 테스트 계획을 생성하는 테스트 엔진(TestEngine)의 인터페이스를 갖고 있음
-> 테스트 발견/수행/결과 보고 역할, 각종 IDE와의 연동을 보조하는 역할 - TestEngine API, Console Launcher, JUnit 4 Based Runner 등이 포함되어 있음
(2) JUnit Jupiter
- 테스트 엔진 API의 구현체를 포함하고 있음
- JUnit5에서 제공하는 Jupiter 기반 테스트를 실행하기 위한 테스트 엔진을 갖고 있음
- 별도 모듈의 역할을 수행하는 테스트의 실제 구현체 중 하나가 Jupiter Engine
-> Jupiter API를 활용해서 작성한 테스트 코드를 발견하고 실행하는 역할을 수행
(3) JUnit Vintage
- JUnit3, 4에 대한 테스트 엔진 API를구현
- 기존에 작성된 JUnit3, 4 버전의 테스트 코드를 실행할 때 사용
- Vintage Engine을 포함
2) 스프링 부트 프로젝트에 JUnit 테스트 준비
- DAO 레이어는 제외하고, 서비스 레이어에서 바로 리포지토리를 사용하는 구조로 수정
(1) build.gradle에 의존성 추가
depenedencies {
... 생략 ...
// 테스트 코드를 작성할 때 필요한 라이브러리들을 포함한 Spring Boot 테스트 스타터 의존성
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// 테스트 실행 시 필요한 JUnit 플랫폼 런처 (테스트를 실제로 실행하는 데 사용됨)
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// Mockito의 핵심 라이브러리: mock 객체를 생성하고 동작을 정의할 수 있게 해줌
testImplementation 'org.mockito:mockito-core:5.2.0'
// JUnit 5(Jupiter)와 Mockito를 함께 사용할 수 있도록 해주는 확장 라이브러리
// @ExtendWith(MockitoExtension.class) 등을 사용할 때 필요함
testImplementation 'org.mockito:mockito-junit-jupiter:5.2.0'
// Gson 라이브러리 사용 - Google에서 제공하는 JSON 직렬화/역직렬화 라이브러리로, Java 객체를 JSON으로 변환하거나 JSON을 Java 객체로 변환할 때 사용
implementation 'com.google.code.gson:gson:2.10.1'
// H2는 인메모리 관계형 데이터베이스로, 테스트 환경에서만 사용
testImplementation 'com.h2database:h2'
// AssertJ 사용
testImplementation 'org.assertj:assertj-core:3.24.2'
}
- spring-boot-starter-test 라이브러리에서 제공하는 라이브러리들
- 단정문 (assert): "이 조건이 반드시 참이어야 해!" 라고 코드에 명시적으로 선언하는 문장
-> 조건이 거짓이면, 프로그램이 오류를 발생시키고 멈춤
- 단정문 (assert): "이 조건이 반드시 참이어야 해!" 라고 코드에 명시적으로 선언하는 문장
JUnit 5 | 자바 애플리케이션의 단위 테스트 지원 |
Spring Test & Spring Boot Test | 스프링 부트 애플리케이션에 대한 유틸리티와 통합 테스트 지원 |
AssertJ | 다양한 단정문(assert)을 지원 |
Hamcrest | Matcher를 지원 |
Mockito | 자바 Mock 객체 지원 |
JSONassert | JSON용 단정문(assert) 라이브러리 |
JsonPath | JSON용 XPath 지원 |
(2) build.gradle 우클릭 후 Gradle -> Refresh Gradle Project 클릭
(3) ProductServiceImpl.java 수정
// ProductServiceImpl.java
package com.example.demo.service.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.jpa.data.dto.ProductDTO;
import com.example.demo.jpa.data.dto.ProductResponseDTO;
import com.example.demo.jpa.data.entity.Product;
import com.example.demo.jpa.data.repository.ProductRepository;
import com.example.demo.service.ProductService;
/**
* ProductService 인터페이스의 구현 클래스
* 비즈니스 로직을 처리하며, Repository를 통해 DB와 통신하고 DTO를 통해 Controller와 데이터를 주고받음
*/
@Service
public class ProductServiceImpl implements ProductService {
private final Logger LOGGER = LoggerFactory.getLogger(ProductServiceImpl.class);
private final ProductRepository productRepository;
/**
* 생성자 주입 방식으로 ProductRepository를 주입
* @param productRepository 상품 데이터 접근 객체
*/
@Autowired
public ProductServiceImpl(ProductRepository productRepository) {
this.productRepository = productRepository;
}
/**
* 상품 번호를 기반으로 상품 정보를 조회하고, 응답 DTO로 변환하여 반환
* @param number 조회할 상품의 고유 번호
* @return ProductResponseDTO 조회된 상품 정보
*/
@Override
public ProductResponseDTO getProduct(Long number) {
LOGGER.info("[getProduct] input number : {}", number);
Product product = productRepository.findById(number).get();
LOGGER.info("[getProduct] product number : {}, name : {}", product.getNumber(), product.getName());
ProductResponseDTO productResponseDTO = new ProductResponseDTO();
productResponseDTO.setNumber(product.getNumber());
productResponseDTO.setName(product.getName());
productResponseDTO.setPrice(product.getPrice());
productResponseDTO.setStock(product.getStock());
return productResponseDTO;
}
/**
* 클라이언트로부터 전달받은 DTO를 기반으로 상품을 생성하고 저장
* 저장된 상품 정보를 응답 DTO로 변환하여 반환
* @param productDTO 저장할 상품 정보
* @return ProductResponseDTO 저장된 상품 정보
*/
@Override
public ProductResponseDTO saveProduct(ProductDTO productDTO) {
LOGGER.info("[saveProduct] productDTO : {}", productDTO.toString());
Product product = new Product();
product.setName(productDTO.getName());
product.setPrice(productDTO.getPrice());
product.setStock(productDTO.getStock());
Product savedProduct = productRepository.save(product);
LOGGER.info("[saveProduct] saveProduct : {}", savedProduct);
ProductResponseDTO productResponseDTO = new ProductResponseDTO();
productResponseDTO.setNumber(savedProduct.getNumber());
productResponseDTO.setName(savedProduct.getName());
productResponseDTO.setPrice(savedProduct.getPrice());
productResponseDTO.setStock(savedProduct.getStock());
return productResponseDTO;
}
/**
* 특정 상품의 이름을 변경하고, 변경된 정보를 응답 DTO로 반환
* @param number 변경할 상품의 고유 번호
* @param name 새로운 상품 이름
* @return ProductResponseDTO 변경된 상품 정보
* @throws Exception 상품이 존재하지 않거나 변경 실패 시 예외 발생
*/
@Override
public ProductResponseDTO changeProductName(Long number, String name) throws Exception {
Product foundroduct = productRepository.findById(number).get();
foundroduct.setName(name);
Product changedProduct = productRepository.save(foundroduct);
ProductResponseDTO productResponseDTO = new ProductResponseDTO();
productResponseDTO.setNumber(changedProduct.getNumber());
productResponseDTO.setName(changedProduct.getName());
productResponseDTO.setPrice(changedProduct.getPrice());
productResponseDTO.setStock(changedProduct.getStock());
return productResponseDTO;
}
/**
* 특정 상품을 삭제
* @param number 삭제할 상품의 고유 번호
* @throws Exception 상품이 존재하지 않거나 삭제 실패 시 예외 발생
*/
@Override
public void deleteProduct(Long number) throws Exception {
productRepository.deleteById(number);
}
}
(4) Product.java(엔티티 클래스)에 어노테이션 추가
// Product.java
package com.example.demo.jpa.data.entity;
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
//클래스의 모든 필드에 대해 Getter 메서드를 자동 생성
@Getter
//클래스의 모든 필드에 대해 Setter 메서드를 자동 생성
@Setter
//파라미터가 없는 기본 생성자를 자동 생성
@NoArgsConstructor
//모든 필드를 파라미터로 받는 생성자를 자동 생성
@AllArgsConstructor
//이 클래스는 JPA 엔티티로 데이터베이스 테이블과 매핑
@Entity
// 객체 생성 시 빌더 패턴을 사용할 수 있게 해주는 어노테이션
@Builder
// equals()와 hashCode() 메서드를 자동 생성해주는 어노테이션
@EqualsAndHashCode
// toString() 메서드를 자동 생성하되, name 필드는 출력에서 제외
@ToString(exclude="name")
//엔티티가 매핑될 테이블 이름을 명시적으로 지정 (기본값은 클래스 이름)
@Table(name="product")
public class Product {
// 기본 키(PK)로 지정된 필드. 데이터베이스의 고유 식별자 역할
@Id
// ID 값을 자동 생성함. GenerationType.IDENTITY는 DB가 직접 AUTO_INCREMENT 방식으로 생성하도록 함(insert 후에 pk 자동생성)
// -> IDENTITY외에도 AUTO(기본값, 사용하는 데이터베이스에 맞게 기본값 자동 생성),
// SEQUENCE(Oracle, PostgreSQL 등 시퀀스를 지원하는 DB에서 별도 시퀀스 객체를 생성해 PK값 증가),
// TABLE(키 값을 관리하는 별도 테이블을 생성해 PK값 증가-성능 낮음)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long number;
// 엔티티 클래스의 필드는 자동으로 테이블 칼럼으로 매핑되므로, 별다른 설정을 하지 않을 경우 @Column을 생략해도 됨
// 상품 이름 (Null 허용하지 않음)
@Column(nullable = false)
private String name;
// 가격 (Null 허용하지 않음)
@Column(nullable = false)
private int price;
// 재고 수량 (Null 허용하지 않음)
@Column(nullable = false)
private int stock;
// 상품 생성 일시 (자동 설정할 수 있으나 현재는 수동 할당 전제)
private LocalDateTime createdAt;
// 상품 수정 일시
private LocalDateTime updatedAt;
}
3) 스프링 부트 프로젝트에 JUnit 테스트 실행 전, JUnit의 생명주기 알아보기
(1) 생명주기와 관련되며, 테스트 순서에 관여하는 어노테이션들
@Test | 테스트 코드를 포함한 메서드 정의 |
@BeforeAll | 테스트를 시작하기 전에 호출되는 메서드 정의 |
@BeforeEach | 각 테스트 메서드가 실행되기 전에 동작하는 메서드 정의 |
@AfterAll | 테스트를 종료하면서 호출되는 메서드 정의 |
@AfterEach | 각 테스트 메서드가 종료되면서 호출되는 메서드 정의 |
(2) 위 어노테이션들의 동작 알아보기
- src/test/java 경로의 패키지에서 테스트 코드 작성할 클래스 생성
// src/test/java/com.example.demo/TestLifeCycle.java
package com.example.demo;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class TestLifeCycle {
@BeforeAll
static void beforeAll() {
System.out.println("## BeforeAll Annotation 호출");
System.out.println();
}
@AfterAll
static void afterAll() {
System.out.println("## AfterAll Annotation 호출");
System.out.println();
}
@BeforeEach
void beforeEach() {
System.out.println("## BeforeEach Annotation 호출");
System.out.println();
}
@AfterEach
void afterEach() {
System.out.println("## AfterEach Annotation 호출");
System.out.println();
}
@Test
void test1() {
System.out.println("## test1 시작");
System.out.println();
}
@Test
@DisplayName("Test Case2")
void test2() {
System.out.println("## test2 시작");
System.out.println();
}
@Test
@Disabled
void test3() {
System.out.println("## test3 시작");
System.out.println();
}
}
- 테스트 코드 실행
- 테스트 코드 실행 결과: @Disabled 어노테이션을 지정한 test3() 메서드를 제외하고 모든 메서드가 실행됨
4) 스프링 부트 프로젝트에 JUnit 테스트 실행
*** 책 내용대로 @RequsetBody을 사용한 것이 아닌, @RequestParam을 사용했으므로 테스트가 되지 않는 것이 정상..
- src/test/java 경로의 패키지에서 테스트 코드 작성할 클래스 생성
// src/test/java/com.example.demo/ProductControllerTest.java
package com.example.demo;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import static org.mockito.Mockito.verify; // 호출 여부 검증
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; // HTTP 요청 생성
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; // 응답 검증
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; // 결과 출력
import static org.mockito.BDDMockito.given; // BDD 스타일의 mock 설정
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; // Controller 테스트용 어노테이션
import org.springframework.test.web.servlet.MockMvc; // 가짜 HTTP 요청을 보내는 객체
import com.example.demo.controller.ProductController;
import com.example.demo.jpa.data.dto.ProductDTO;
import com.example.demo.jpa.data.dto.ProductResponseDTO;
import com.example.demo.service.impl.ProductServiceImpl;
@WebMvcTest(ProductController.class) // ProductController만 테스트 대상으로 설정
public class ProductControllerTest {
@Autowired
private MockMvc mockMvc; // 실제 서버 없이 Controller를 테스트할 수 있게 해주는 객체
@Mock
ProductServiceImpl productService; // ProductServiceImpl을 mock 객체로 생성
@Test
@DisplayName("MockMvc를 통한 Product 데이터 가져오기 테스트") // 테스트 이름 지정
void getProductTest() throws Exception {
// productService.getProduct(123L)가 호출되면, 아래 DTO를 반환하도록 설정
given(productService.getProduct(123L)).willReturn(
new ProductResponseDTO(123L, "pen", 5000, 2000));
String productId = "123";
// GET 요청을 /product?number=123 으로 보내고, 응답을 검증
mockMvc.perform(
get("/product?number=" + productId)) // 요청 생성
.andExpect(status().isOk()) // HTTP 상태 코드 200 확인
.andExpect(jsonPath("$.number").exists()) // JSON 응답에 number 필드 존재 확인
.andExpect(jsonPath("$.name").exists()) // name 필드 존재 확인
.andExpect(jsonPath("$.price").exists()) // price 필드 존재 확인
.andExpect(jsonPath("$.number").exists()) // number 필드 중복 확인 (한 번만 해도 충분)
.andDo(print()); // 응답 결과 콘솔에 출력
// productService.getProduct(123L)가 실제로 호출되었는지 검증
verify(productService).getProduct(123L);
}
@Test
@DisplayName("상품 생성 API 테스트 - createProduct")
void createProductTest() throws Exception {
// productService.saveProduct(...)가 호출되면 아래 DTO를 반환하도록 설정
ProductResponseDTO mockResponse = new ProductResponseDTO(101L, "notebook", 15000, 500);
ProductDTO inputDTO = new ProductDTO();
inputDTO.setName("notebook");
inputDTO.setPrice(15000);
inputDTO.setStock(500);
given(productService.saveProduct(inputDTO)).willReturn(mockResponse);
// POST 요청을 /product로 보내고, 응답을 검증
mockMvc.perform(
post("/product")
.param("name", "notebook")
.param("price", "15000")
.param("stock", "500"))
.andExpect(status().isOk()) // HTTP 200 응답 확인
.andExpect(jsonPath("$.number").value(101)) // 응답 JSON의 number 필드 값 확인
.andExpect(jsonPath("$.name").value("notebook")) // name 필드 값 확인
.andExpect(jsonPath("$.price").value(15000)) // price 필드 값 확인
.andExpect(jsonPath("$.stock").value(500)) // stock 필드 값 확인
.andDo(print()); // 결과 출력
// productService.saveProduct(...)가 호출되었는지 검증
verify(productService).saveProduct(inputDTO);
}
}
// src/test/java/com.example.demo/ProductServiceTest.java
package com.example.demo;
// Mockito와 JUnit에서 사용하는 static 메서드들을 import
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.AdditionalAnswers.returnsFirstArg;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
// 테스트 대상 클래스와 관련된 DTO, Entity, Repository 클래스 import
import com.example.demo.jpa.data.dto.ProductDTO;
import com.example.demo.jpa.data.dto.ProductResponseDTO;
import com.example.demo.jpa.data.entity.Product;
import com.example.demo.jpa.data.repository.ProductRepository;
import com.example.demo.service.impl.ProductServiceImpl;
// ProductServiceImpl 클래스에 대한 단위 테스트 클래스 정의
public class ProductServiceTest {
// ProductRepository를 Mockito를 사용해 Mock 객체로 생성
private ProductRepository productRepository = Mockito.mock(ProductRepository.class);
// 테스트 대상인 ProductServiceImpl 인스턴스 선언
private ProductServiceImpl productService;
// 각 테스트 실행 전에 호출되는 초기화 메서드
@BeforeEach
public void setUpTest() {
// ProductServiceImpl 인스턴스를 생성하면서 Mock Repository를 주입
productService = new ProductServiceImpl(productRepository);
}
// saveProduct 메서드에 대한 단위 테스트
@Test
void saveProductTest() {
// Repository의 save 메서드가 호출되면 전달된 인자를 그대로 반환하도록 설정
Mockito.when(productRepository.save(any(Product.class)))
.then(returnsFirstArg());
// 테스트 대상 메서드 호출: ProductDTO를 전달하여 저장 요청
ProductResponseDTO productResponseDTO = productService.saveProduct(
new ProductDTO("펜", 1000, 1234));
// 반환된 결과가 기대한 값과 일치하는지 검증
Assertions.assertEquals(productResponseDTO.getName(), "펜");
Assertions.assertEquals(productResponseDTO.getPrice(), 1000);
Assertions.assertEquals(productResponseDTO.getStock(), 1234);
// Repository의 save 메서드가 실제로 호출되었는지 검증
verify(productRepository).save(any());
}
}
// src/test/java/com.example.demo/ProductRepositoryTest.java
package com.example.demo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import org.assertj.core.api.Assertions;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import com.example.demo.jpa.data.entity.Product;
import com.example.demo.jpa.data.repository.ProductRepository;
// JPA 관련 테스트를 위한 설정. 실제 DB 대신 테스트용 인메모리 DB를 사용함
@DataJpaTest
public class ProductRepositoryTest {
// 테스트 대상인 ProductRepository를 주입받음
@Autowired
private ProductRepository productRepository;
@Test
void saveTest() {
// given: 저장할 Product 객체 생성
Product product = new Product();
product.setName("펜");
product.setPrice(1000);
product.setStock(1000);
// when: Product 저장
Product saveProduct = productRepository.save(product);
// then: 저장된 값이 원래 값과 같은지 검증
assertEquals(product.getName(), saveProduct.getName());
assertEquals(product.getPrice(), saveProduct.getPrice());
assertEquals(product.getStock(), saveProduct.getStock());
}
@Test
void selectTest() {
// given: 저장할 Product 객체 생성 및 저장
Product product = new Product();
product.setName("펜");
product.setPrice(1000);
product.setStock(1000);
Product savedProduct = productRepository.saveAndFlush(product);
// when: 저장된 Product를 ID로 조회
Product foundProduct = productRepository.findById(savedProduct.getNumber()).get();
// then: 조회된 값이 원래 값과 같은지 검증
assertEquals(product.getName(), foundProduct.getName());
assertEquals(product.getPrice(), foundProduct.getPrice());
assertEquals(product.getStock(), foundProduct.getStock());
}
@Test
public void basicCRUDTest() {
// Create 테스트
// given: Product 객체 생성
Product givenProduct = Product.builder()
.name("노트")
.price(1000)
.stock(500)
.build();
// when: Product 저장
Product savedProduct = productRepository.save(givenProduct);
// then: 저장된 값이 원래 값과 같은지 검증
Assertions.assertThat(savedProduct.getNumber())
.isEqualTo(givenProduct.getNumber());
Assertions.assertThat(savedProduct.getName())
.isEqualTo(givenProduct.getName());
Assertions.assertThat(savedProduct.getPrice())
.isEqualTo(givenProduct.getPrice());
Assertions.assertThat(savedProduct.getStock())
.isEqualTo(givenProduct.getStock());
// Read 테스트
// when: 저장된 Product를 ID로 조회
Product selectedProduct = productRepository.findById(savedProduct.getNumber())
.orElseThrow(RuntimeException::new);
// then: 조회된 값이 원래 값과 같은지 검증
Assertions.assertThat(selectedProduct.getNumber())
.isEqualTo(givenProduct.getNumber());
Assertions.assertThat(selectedProduct.getName())
.isEqualTo(givenProduct.getName());
Assertions.assertThat(selectedProduct.getPrice())
.isEqualTo(givenProduct.getPrice());
Assertions.assertThat(selectedProduct.getStock())
.isEqualTo(givenProduct.getStock());
// Update 테스트
// when: Product 이름 변경
Product foundProduct = productRepository.findById(selectedProduct.getNumber())
.orElseThrow(RuntimeException::new);
foundProduct.setName("장난감");
// 변경된 Product 다시 조회
Product updatedProduct = productRepository.findById(selectedProduct.getNumber())
.orElseThrow(RuntimeException::new);
// then: 이름이 변경되었는지 확인
assertEquals(updatedProduct.getName(), "장난감");
// Delete 테스트
// when: Product 삭제
productRepository.delete(updatedProduct);
// then: 삭제된 Product가 존재하지 않는지 확인
assertFalse(productRepository.findById(selectedProduct.getNumber()).isPresent());
}
}
'스프링(Spring), 스프링부트(SpringBoot) > 스프링부트(SpringBoot) 기초' 카테고리의 다른 글
스프링 부트 핵심 가이드(장정우 지음) - Spring Data JPA 활용 1(JPQL, 쿼리 메서드 살펴보기, 정렬과 페이징 처리) (0) | 2025.08.15 |
---|---|
스프링 부트 핵심 가이드(장정우 지음) - JaCoCo를 활용한 테스트 커버리지 확인, 테스트 주도 개발(TDD) (0) | 2025.08.12 |
스프링 부트 핵심 가이드(장정우 지음) - 테스트 코드 작성하기 (0) | 2025.07.30 |
스프링 부트 핵심 가이드(장정우 지음) - 데이터베이스 연동 4: 서비스와 컨트롤러 설계2, 롬복(Lombok) (0) | 2025.07.24 |
스프링 부트 핵심 가이드(장정우 지음) - 데이터베이스 연동 3: DAO 설계, 서비스와 컨트롤러 설계 (0) | 2025.07.23 |