테스트 코드를 작성하는 이유
테스트 기준은 여러 기준으로 분류할 수 있고ㅡ 테스트 대상 범위를 기준으로 구분하면 크게 단위 테스트, 통합 테스트로 구분된다.
Given-When-Then 패턴
불필요하게 코드가 길어지므로 간단한 테스트로 여겨지는 단위 테스트에서는 잘 사용하지 않는다.
명세 문서의 역할을 수행한다는 측면에서 많은 도움이 된다.
F.I.R.S.T
테스트 코드를 작성하는데 도움이 될 수 있는 5가지 규칙을 의미한다.
단위 테스트에 적용할 수 있는 규칙이다.
Junit을 활용한 테스트 코드 작성
JUnit은 자바 언어에서 사용되는 대표적인 테스트 프레임워크로서, 단위 테스트를 위한 도구를 제공한다.
단위 테스트 뿐만 아니라 통합 테스트를 수행할 수 있는 기능도 제공한다.
어노테이션 기반의 테스트 방식을 지원한다. -> JUnit을 사용하면 몇 개의 어노테이션만으로 간단하게 테스트 코드를 작성할 수 있다.
Junit을 활용하면 단정문(assert)문을 통해 테스트 케이스의 기댓값이 정상적으로 도출됐는지 검토할 수 있다는 장점이 있다.
크게 Jupiter, Platform, Vintabe 세 모듈로 구성된다.
스프링 부트 프로젝트
ProductServiceImpl 클래스 수정
@Service
public class ProductServiceImpl implements ProductService {
private final Logger LOGGER = LoggerFactory.getLogger(ProductServiceImpl.class);
private final ProductRepository productRepository;
@Autowired
public ProductServiceImpl(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@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;
}
@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] savedProduct : {}", savedProduct);
ProductResponseDto productResponseDto = new ProductResponseDto();
productResponseDto.setNumber(savedProduct.getNumber());
productResponseDto.setName(savedProduct.getName());
productResponseDto.setPrice(savedProduct.getPrice());
productResponseDto.setStock(savedProduct.getStock());
return productResponseDto;
}
@Override
public ProductResponseDto changeProductName(Long number, String name) {
Product foundProduct = productRepository.findById(number).get();
foundProduct.setName(name);
Product changedProduct = productRepository.save(foundProduct);
ProductResponseDto productResponseDto = new ProductResponseDto();
productResponseDto.setNumber(changedProduct.getNumber());
productResponseDto.setName(changedProduct.getName());
productResponseDto.setPrice(changedProduct.getPrice());
productResponseDto.setStock(changedProduct.getStock());
return productResponseDto;
}
@Override
public void deleteProduct(Long number) {
productRepository.deleteById(number);
}
}
Product Entity 수정
@Entity
@Builder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString(exclude = "name")
@Table(name = "product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long number;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer price;
@Column(nullable = false)
private Integer stock;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
testImplementation 'org.springframework.boot:spring-boot-starter-test'
스프링 부트에서 제공하는 spring-boot-starter-test 라이브러리는 JUnit, Mokito, assertJ 등의 다양한 테스트 도구를 제공한다. 또한 자동 설정ㅇ을 지원한다.
JUnit의 생명 주기
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 Case 2!!!")
void test2() {
System.out.println("## test2 시작 ##");
System.out.println();
}
// Disabled Annotation : 테스트를 실행하지 않게 설정하는 어노테이션
@Test
@Disabled
void test3() {
System.out.println("## test3 시작 ##");
System.out.println();
}
}
## beforeEach Annotation 호출 ##
## test1 시작 ##
## afterEach Annotation 호출 ##
## beforeEach Annotation 호출 ##
## test2 시작 ##
## afterEach Annotation 호출 ##
## afterAll Annotation 호출 ##
위 테스트 코드를 실행하면 다음과 같은 로그가 출력된다.
@BeforeAll과 @AfterAll 어노테이션이 지정된 메서드는 전체 테스트 동작에서 처음과 마지막에만 각각 수행된다.
@BeforeEach와 @AfterEach 어노테이션이 지정된 메서드는 각 메서드가 실행될 때마다 실행되어 테스트 메서드 기준으로 실행된다.
test3()는 @Disabled 애노테이션을 지정했는데, 이 애노테이션은 테스트를 비활성화한다. 따라서 test3()은 실행되지 않는다.
ProductController의 getProduct()와 createProduct() 메서드에 대한 테스트 코드 작성
@RestController
@RequestMapping("/product")
public class ProductController {
private final Logger LOGGER = LoggerFactory.getLogger(ProductController.class);
private final ProductService productService;
ProductCobntroller는 ProductService 객체를 의존성 주입받는다. 테스트하는 입장에서 ProductController만 테스트하고 싶다면 ProuctService는 외부 요인에 해당한다. 따라서 독립적인 테스트 코드를 작성하기 위해서는 Mock 객체를 활용해야 한다.
getProduct() 테스트코드 작성
@WebMvcTest(ProductController.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
// ProductController에서 잡고 있는 Bean 객체에 대해 Mock 형태의 객체를 생성해줌
@MockBean
ProductServiceImpl productService;
// 예제 7.6
// http://localhost:8080/api/v1/product-api/product/{productId}
@Test
@DisplayName("MockMvc를 통한 Product 데이터 가져오기 테스트")
void getProductTest() throws Exception {
// given : Mock 객체가 특정 상황에서 해야하는 행위를 정의하는 메소드
given(productService.getProduct(123L)).willReturn(
new ProductResponseDto(123L, "pen", 5000, 2000));
String productId = "123";
// andExpect : 기대하는 값이 나왔는지 체크해볼 수 있는 메소드
mockMvc.perform(
get("/product?number=" + productId))
.andExpect(status().isOk())
.andExpect(jsonPath(
"$.number").exists()) // json path의 depth가 깊어지면 .을 추가하여 탐색할 수 있음 (ex : $.productId.productIdName)
.andExpect(jsonPath("$.name").exists())
.andExpect(jsonPath("$.price").exists())
.andExpect(jsonPath("$.stock").exists())
.andDo(print());
// verify : 해당 객체의 메소드가 실행되었는지 체크해줌
verify(productService).getProduct(123L);
}
}
일반적으로 @WebMvcTest 어노테이션을 사용한 테스트는 슬라이스(Slice) 테스트라고 부른다.
슬라이스 테스트는 단위 테스트와 통합 테스트의 중간 개념으로, 레이어드 아키텍처를 기준으로 각 레이어별로 나누어 테스트를 진행한다는 의미이다. 단위 테스트를 수행하기 위해서는 모든 외부 요인을 차단하고 테스트를 진행해야 하지만 컨트롤러는 개념상 웹과 맞닿은 레이어로서 외부 요인을 차단하고 테스트하면 의미가 없기 때문에 슬라이스 테스트를 진행하는 경우가 많다.
Mockito 라이브러리는 given() 메서드를 통해 Mock 객체에 특정 상황에서 어떤 메서드를 실행할 때 어떤 결과를 반환해야 하는지 미리 지정할 수 있다.
perform() 메서드를 통해 MockMvc 객체의 HTTP 요청을 수행하며 결과를 검증한다. 해당 메서드는 HTTP 요청의 다양한 메서드(GET, POST, PUT, DELETE 등)를 처리할 수 있다. 테스트 코드에서는 URL, 요청 데이터와 같은 필수 정보를 설정한다. perform() 메서드는 결과값을 ResultActions 객체로 반환하는데, 이 객체는 추가적인 검증 메서드를 실행할 수 있게 돕는다. 예를 들어, andExpect() 메서드를 사용해 반환값을 검증하거나, 예상 결과값을 설정할 수 있다. andExpect 메서드와 함께 사용되는 ResultMatcher 객체를 통해 반환된 응답의 상태 코드나 데이터 값을 확인할 수 있다.
요청과 응답의 값을 더 구체화할 때는 andDo() 메서드를 사용한다. andDo 메서드는 테스트 결과를 출력하거나 로그로 남기는 역할을 한다. 이어서 MockMvcResultMatchers 유틸리티를 사용해 HTTP 응답 상태 코드나 반환된 결과값을 검증할 수 있다.
verify() 메서드를 통해 특정 메서드가 호출되었는지 확인한다. 이는 테스트의 끝단에서 중요한 검증 작업으로, given() 메서드에 정의된 동작이 제대로 수행되었는지 점검하는 역할을 한다.
createProduct() 테스트코드 작성
implementation 'com.google.code.gson:gson:2.10.1'
build.gradle 기준 Gson 의존성 추가
@Test
@DisplayName("Product 데이터 생성 테스트")
void createProductTest() throws Exception {
//Mock 객체에서 특정 메소드가 실행되는 경우 실제 Return을 줄 수 없기 때문에 아래와 같이 가정 사항을 만들어줌
given(productService.saveProduct(new ProductDto("pen", 5000, 2000)))
.willReturn(new ProductResponseDto(12315L, "pen", 5000, 2000));
ProductDto productDto = ProductDto.builder()
.name("pen")
.price(5000)
.stock(2000)
.build();
Gson gson = new Gson();
String content = gson.toJson(productDto);
mockMvc.perform(
post("/product")
.content(content)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.number").exists())
.andExpect(jsonPath("$.name").exists())
.andExpect(jsonPath("$.price").exists())
.andExpect(jsonPath("$.stock").exists())
.andDo(print());
verify(productService).saveProduct(new ProductDto("pen", 5000, 2000));
}
given() 메서드를 통해 ProductService의 saveProduct() 메서드의 동작 규칙을 설정하고, 테스트에 필요한 객체를 생성한다. 실제 테스트를 수행하는 부분은 리소스 생성 기능을 테스트하기 때문에 post 메서드를 통해 URL을 구성한다. 그리고 @RequestBody의 값을 넘겨주기 위해 content() 메서드에 DTO의 값을 담아 테스트를 진행한다. 마지막으로 POST 요청을 통해 도출된 결과값에 대해 각 항목이 존재하는지 jsonPath()와 .exists()를 통해 검증한다. 검증한 결과, 대응하는 값이 없다면 오류가 발생한다.
ProductService의 getProduct()와 createProduct() 메서드에 대한 테스트 코드 작성
@Test
void getProductTest() {
// given
Product givenProduct = new Product();
givenProduct.setNumber(123L);
givenProduct.setName("펜");
givenProduct.setPrice(1000);
givenProduct.setStock(1234);
Mockito.when(productRepository.findById(123L))
.thenReturn(Optional.of(givenProduct));
// when
ProductResponseDto productResponseDto = productService.getProduct(123L);
// then
Assertions.assertEquals(productResponseDto.getNumber(), givenProduct.getNumber());
Assertions.assertEquals(productResponseDto.getName(), givenProduct.getName());
Assertions.assertEquals(productResponseDto.getPrice(), givenProduct.getPrice());
Assertions.assertEquals(productResponseDto.getStock(), givenProduct.getStock());
verify(productRepository).findById(123L);
}
@SpringBootTest, @WebTest를 사용하지 않고 단위 테스트를 수행하는 대신 직접 객체를 생성해 테스트를 진행했다. Mockito.mock() 메서드를 통해 Mock 객체로 ProductRepository를 주입받았다.
이후 테스트 대상인 ProductService 클래스에서 사용하는 메서드를 실행하는 테스트이다.
@Test
void saveProductTest() {
// given
Mockito.when(productRepository.save(any(Product.class)))
.then(returnsFirstArg());
// when
ProductResponseDto productResponseDto = productService.saveProduct(
new ProductDto("펜", 1000, 1234));
// then
Assertions.assertEquals(productResponseDto.getName(), "펜");
Assertions.assertEquals(productResponseDto.getPrice(), 1000);
Assertions.assertEquals(productResponseDto.getStock(), 1234);
verify(productRepository).save(any());
}
ProductService가 정상적으로 동작하며, ProductRepository가 올바르게 호출되었는지를 검증한다.
Mock 객체를 사용했기 때문에 외부 데이터베이스에 접근하지 않아도 테스트를 수행할 수 있다.
Mock 객체를 직접 생성하지 않고 @MockBean 어노테이션을 사용해 스프링 컨테이너에 Mock 객체를 주입받는 방법
@ExtendWith(SpringExtension.class)
@Import({ProductServiceImpl.class})
class ProductServiceTest2 {
@MockBean
ProductRepository productRepository;
@Autowired
ProductService productService;
// 테스트 코드 작성
}
1. @MockBean을 사용하는 것은 스프링에 Mock 객체를 등록해서 주입받은 형식이며,
2. @MockBean.mock()을 사용하는 방식은 스프링 빈에 등록하지 않고 직접 객체를 초기화해서 사용하는 방식이다.
두 방식 모두 테스트 속도에는 큰 차이가 없지만 스프링을 사용하지 않는 Mock 객체를 직접 생성하는 방식이 더 빠르게 동작한다.
리포지토리 객체의 테스트
JpaRepository를 상속받아 기본적인 쿼리 메서드들을 사용할 수 있다.
그러나 findById(), save() 같은 레포지토리의 기본 메서드는 테스트 검증을 마치고 제공된 것이므로 이를 테스트할 필요는 없다.
단위 테스트의 경우 외부 요인에 해당하는 데이터베이스를 제외할 수 있다.
만약 데이터베이스를 연동할 경우 테스트 과정에서 데이터베이스에 테스트 데이터가 적재되므로 테스트 데이터를 제거하는 코드까지 포함하여 작성하는 것이 좋다.
데이터베이스에 값을 저장하는 테스트 코드
@DataJpaTest
public class ProductRepositoryTestByH2 {
@Autowired
private ProductRepository productRepository;
@Test
void saveTest() {
// given
Product product = new Product();
product.setName("펜");
product.setPrice(1000);
product.setStock(1000);
// when
Product savedProduct = productRepository.save(product);
// then
assertEquals(product.getName(), savedProduct.getName());
assertEquals(product.getPrice(), savedProduct.getPrice());
assertEquals(product.getStock(), savedProduct.getStock());
}
}
@DataJpaTest 어노테이션 사용
@DataJpaTest 어노테이션을 선언했기 때문에 리포지토리를 정상적으로 주입받을 수 있게 된다.
데이터 조회에 대한 테스트 코드
@Test
void selectTest() {
// given
Product product = new Product();
product.setName("펜");
product.setPrice(1000);
product.setStock(1000);
Product savedProduct = productRepository.saveAndFlush(product);
// when
Product foundProduct = productRepository.findById(savedProduct.getNumber()).get();
// then
assertEquals(product.getName(), foundProduct.getName());
assertEquals(product.getPrice(), foundProduct.getPrice());
assertEquals(product.getStock(), foundProduct.getStock());
}
조회를 위해서는 데이터베이스에 테스트 데이터를 추가해야 한다.
-> given 절에서 객체를 데이터베이스에 저장하는 작업을 수행했다.
조회 메서드를 호출하여 테스트를 진행하고 코드에서 데이터를 비교하여 검증을 수행한다.
위 코드는 h2 데이터베이스에서 실행하기 위한 코드이며, 마리아DB에서 테스트하기 위해서는 별도의 설정이 필요하다.
테스트 데이터베이스 변경을 위한 어노테이션 추가
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class ProductRepositoryTest {
@Autowired
private ProductRepository productRepository;
@Test
void save() {
// given
Product product = new Product();
product.setName("펜");
product.setPrice(1000);
product.setStock(1000);
// when
Product savedProduct = productRepository.save(product);
// then
assertEquals(product.getName(), savedProduct.getName());
assertEquals(product.getPrice(), savedProduct.getPrice());
assertEquals(product.getStock(), savedProduct.getStock());
}
}
replace = Replace.ANY일 경우 임베디드 메모리 데이터베이스를 사용한다.
replace = Replace.NONE로 변경하면 애플리케이션에서 실제로 사용하는 데이터베이스로 테스트를 할 수 있다.
@DataJpaTest를 사용하지 않고 @SpringBootTest 어노테이션으로 테스트 진행
package com.springboot.demo.data.repository;
import com.springboot.demo.data.entity.Product;
import org.assertj.core.api.Assertions;
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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@SpringBootTest
public class ProductRepositoryTest2 {
@Autowired
ProductRepository productRepository;
@Test
public void basicCRUDTest() {
/* create */
// given
Product givenProduct = Product.builder()
.name("노트")
.price(1000)
.stock(500)
.build();
// when
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 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 foundProduct = productRepository.findById(selectedProduct.getNumber())
.orElseThrow(RuntimeException::new);
foundProduct.setName("장난감");
Product updatedProduct = productRepository.save(foundProduct);
// then
assertEquals(updatedProduct.getName(), "장난감");
/* delete */
// when
productRepository.delete(updatedProduct);
// then
assertFalse(productRepository.findById(selectedProduct.getNumber()).isPresent());
}
}
@SpringBootTest 어노테이션을 활용하면 스프링의 모든 설정을 가져오고 빈 객체도 전체를 스캔하기 때문에 의존성 주입에 대해 고민할 필요가 없이 테스트가 가능하다.
다만, 테스트 속도가 느리기 때문에 다른 방법으로 테스트할 수 있다면 대안을 고려해보는 것이 좋다.
코드 커버리지는 소프트웨어의 테스트 수준이 충분한지를 표현하는 지표 중 하나이다.
테스트를 진행했을 때 대상 코드가 실행됐는지 표현하는 방법으로도 사용된다.
커버리지를 확인하기 위해 사용하는 도구 중 JaCoCo가 가장 보편적으로 사용된다.
Java Code Coverage의 약자로 JUnit 테스트를 통해 애플리케이션의 코드가 얼마나 테스트되었느지 Line과 Branch를 기준으로 한 커버리지로 리포트한다.
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<configuration>
<excludes>
<exclude>**/ProductServiceImpl.class</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>jacoco-report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
<element>METHOD</element>
<limits>
<limit>
<counter>LINE</counter>
<value>TOTALCOUNT</value>
<maximum>50</maximum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
index.html이 아래처럼 떠서 진행하지 못했습니다. 책 참고해주세요!
TDD(Test Driven Development) 반복 테스트를 이용한 소프트웨어 개발 방법론으로서 테스트 코드를 먼저 작성한 후 테스트를 통과하는 코드를 작성하는 과정을 반복하는 소프트웨어 개발 방식이다.
총 3개의 단계로 개발 주기를 표현한다.
일반적인 개발 방법은 설계를 진행한 후 그에 맞게 애플리케이션 코드를 작성하고 마지막으로 테스트 코드를 작성하는 흐름으로 진행된다.
반면 테스트 주도 개발에서는 설계 이후 바로 테스트 코드를 작성하고 애플리케이션 코드를 작성한다는 점에서 차이가 있다.
테스트 주도 개발의 효과
Q1(드래그 하여 정답 확인)
1. ( 단위 테스트 )는 애플리케이션의 개별 모듈을 독립적으로 테스트하는 방식이며, ( 통합 테스트 )는 애플리케이션을 구성하는 다양한 모듈을 결합해 전체적인 로직이 의도한 대로 동작하는지 테스트하는 방식이다.
2. 테스트 코드를 작성하는 방법에는 ( Given-When-Then ) 패턴과 F.I.R.S.T 전략이 있다.
3. ( JUnit )은 자바 언어에서 사용되는 대표적인 테스트 프레임워크로서 단위 테스트를 위한 도구를 제공한다. 가장 큰 특징은 어노테이션 기반의 테스트 방식을 지원한다는 점이다.
4. JUnit의 생명주기와 관련된 어노테이션으로, 각 테스트 메서드가 실행되기 전에 동작하는 메서드를 정의하는 어노테이션은 ( @BeforeEach )이다.
5. 테스트 과정에서 외부 요인이 있을 때 독립적인 테스트 코드를 작성하기 위해 ( Mock )객체를 활용해야 한다.
6. 커버리지를 확인하기 위한 다양한 커버리지 도구 중 가장 보편적으로 사용되는 도구는 ( jaCoCo )이다.
7. ( TDD, Test-Driven-Development, 테스트 주도 개발 )은 반복 테스트를 이용한 소프트웨어 개발 방법론으로서 테스트 코드를 먼저 작성한 후 테스트를 통과하는 코드를 작성하는 과정을 반복하는 소프트웨어 개발 방식이다.
Q2
public class TestLifeCycle {
@BeforeAll
static void beforeAll() {
System.out.println("## BeforeAll Annotation 호출 ##");
System.out.println();
}
// .. 생략
@Test
// 테스트를 실행하지 않게 설정하는 어노테이션 작성
void test3() {
System.out.println("## test3 시작 ##");
System.out.println();
}
}
// 스프링의 모든 설정을 가져오고 빈 객체도 전체를 스캔하는 어노테이션 (@DataJpaTest X)
public class ProductRepositoryTest2 {
@Autowired
ProductRepository productRepository;
@Test
public void basicCRUDTest() {
// .. 생략
}
}
출처: 스프링부트 핵심 가이드
SPRING #2
Editor : 노을
[스프링 2] 6장. 데이터베이스 연동 (0) | 2024.11.29 |
---|---|
[스프링 2팀]5~6장. API를 작성하는 다양한 방법 & 데이터베이스 연동 (0) | 2024.11.22 |
[스프링 2팀] 1~4장. 스프링 부트 개발 환경과 애플리케이션 개발하기 (0) | 2024.11.15 |
[스프링 2팀] 스프링 입문 - 섹션 07~08 (0) | 2024.11.08 |
[스프링 2팀] 스프링 입문 - 섹션 05~06 (0) | 2024.10.11 |