7.1 테스트 코드를 작성하는 이유
7.2 단위 테스트와 통합 테스트
7.2.1 단위 테스트의 특징
7.2.2 통합 테스트의 특징
- 단위 테스트와 통합 테스트의 차이
단위 테스트 | 통합 테스트 |
모듈을 독립적으로 테스트 | 여러 모듈을 함께 테스트해서 정상적인 로직 수행이 가능한지 확인 |
특정 모듈에 대한 테스트만 진행하기 때문에 데이터베이스나 네트워크 같은 외부 요인들을 제외하고 진행 |
외부 요인들을 포함하고 테스트를 진행하므로 애플리케이션이 온전히 동작하는지 테스트 (단, 테스트를 수행할 때마다 모든 컴포넌트가 동작해야 하기 때문에 테스트 비용이 커지는 단점 존재) |
7.3 테스트 코드를 작성하는 방법
7.3.1 Given-When-Then 패턴
◻️ Given
- 테스트를 수행하기 전에 테스트에 필요한 환경을 설정하는 단계
- 테스트에 필요한 변수를 정의하거나 Mock 객체를 통해 특정 상황에 대한 행동을 정의
◻️ When
- 테스트의 목적을 보여주는 단계
- 실제 테스트 코드가 포함되며, 테스트를 통한 결괏값을 가져오게 됨
◻️ Then
- 테스트의 결과를 검증하는 단계
- 일반적으로 When 단계에 나온 결괏값을 검증하는 작업을 수행
- 결괏값이 아니더라도 이 테스트를 통해 나온 결과에서 검증해야 하는 부분이 있다면 Then 단계에 포함
7.3.2 좋은 테스트를 작성하는 5가지 속성 (F.I.R.S.T)
◻️ 빠르게(fast)
- 테스트가 느리면 코드를 개선하는 작업이 느려져 코드의 품질이 떨어질 수 있어 테스트는 빠르게 수행될 필요 O
- 테스트 속도에 절대적인 기준은 없지만 목적을 단순하게 설정해서 작성하거나
외부 환경을 사용하지 않는 단위 테스트를 작성하는 것 등을 빠른 테스트라고 할 수 있음
◻️ 고립된, 독립적(Isolated)
- 하나의 테스트 코드는 목적으로 여기는 하나의 대상에 대해서만 수행되어야 함
- 만약 하나의 테스트가 다른 테스트 코드와 상호작용하거나 관리할 수 없는 외부 소스를 사용하게 되면
외부 요인으로 인해 테스트가 수행되지 않을 수 있음
◻️ 반복 가능한(Repeatable)
- 테스트는 어떤 환경에서도 반복 가능하도록 작성해야 함
- 테스트는 개발 환경의 변화나 네트워크의 연결 여부와 상관없이 수행되어야 함
◻️ 자가 검증(Self-Validating)
- 테스트는 그 자체만으로도 테스트의 검증이 완료되어야 함
- 테스트가 성공했는지 실패했는지 확인할 수 있는 코드를 함께 작성해야 함
(결과값과 기대값을 비교하는 작업을 코드가 아니라 개발자가 직접 확인하고 있다면 좋지 못한 테스트 코드)
◻️ 적시에(Timely)
- 테스트 코드는 테스트하려는 애플리케이션 코드를 구현하기 전에 완성되어야 함
(너무 늦게 작성된 테스트 코드는 정상적인 역할을 수행하기 어려움 & 문제 해결에 소모되는 개발 비용 ↑)
- 테스트 주도 개발의 원칙을 따르는 테스트 작성 규칙으로, 테스트 주도 개발 기반이 아니라면 생략 가능
7.4 JUnit을 활용한 테스트 코드 작성
7.4.1 JUnit의 세부 모듈
◻️ JUnit Platform
- JVM에서 테스트를 시작하기 위한 뼈대 역할
- 테스트를 발견하고 테스트 계획을 생성하는 테스트 엔진의 인터페이스를 가지고 있음
- 테스트 엔진은 테스트를 발견하고 테스트를 수행하며, 그 결과를 보고하는 역할을 수행
- 각종 IDE와의 연동을 보조하는 역할 수행
◻️ JUnit Jupiter
- 테스트 엔진 API의 구현체를 포함
- JUnit 5에서 제공하고 Jupiter 기반의 테스트를 실행하기 위한 테스트 엔진을 가지고 있음
- Jupiter Engine은 Jupiter API를 활용해서 작성한 테스트 코드를 발견하고 실행하는 역할을 수행
◻️ JUnit Vintage
- JUnit 3, 4에 대한 테스트 엔진 API를 구현
- 기존에 작성된 JUnit 3, 4 버전의 테스트 코드를 실행할 때 사용되며 Vintage Engine을 포함하고 있음
7.4.2 스프링 부트 프로젝트 생성
◻️ Developer Tools : Lombok, Spring Configuration Processor
◻️ Web : Spring Web
◻️ SQL : Spring Data JPA, Maria DB Driver
7.4.3 스프링 부트의 테스트 설정
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
◻️ JUnit 5 : 자바 애플리케이션의 단위 테스트를 지원
◻️ Spring Test & Spring Boot Test : 스프링 부트 애플리케이션에 대한 유틸리티와 통합 테스트를 지원
◻️ AssertJ : 다양한 단정문(assert)을 지원하는 라이브러리
◻️ Hamcrest : Matcher를 지원하는 라이브러리
◻️ Mockito : 자바 Mock 객체를 지원하는 프레임워크
◻️ JSONassert : JSON용 단정문 라이브러리
◻️ JsonPath : JSON용 XPath를 지원
7.4.4 JUnit의 생명주기
◻️ @Test : 테스트 코드를 포함한 메서드를 정의
◻️ @BeforeAll : 테스트를 시작하기 전에 호출되는 메서드를 정의
◻️ @BeforeEach : 각 테스트 메서드가 실행되기 전에 동작하는 메서드를 정의
◻️ @AfterAll : 테스트를 종료하면서 호출되는 메서드를 정의
◻️ @AfterEach : 각 테스트 메서드가 종료되면서 호출되는 메서드를 정의
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();
}
}
수행결과
## BeforeAll Annotation 호출 ##
## BeforeEach Annotation 호출 ##
## test1 시작 ##
## AfterEach Annotation 호출 ##
## BeforeEach Annotation 호출 ##
## test2 시작 ##
## AfterEach Annotation 호출 ##
void com.springboot.test.TestLifeCycle.test3() is @Disabled
## AfterAll Annotation 호출 ##
7.4.6 컨트롤러 객체의 테스트
@RestController
@RequestMapping("/product")
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping()
public ResponseEntity<ProductResponseDto> getProduct(Long number) {
ProductResponseDto productResponseDto = productService.getProduct(number);
return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
}
package com.springboot.test.controller;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.google.gson.Gson;
import com.springboot.test.data.dto.ProductDto;
import com.springboot.test.data.dto.ProductResponseDto;
import com.springboot.test.service.impl.ProductServiceImpl;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(ProductController.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc; // MockMvc : 컨트롤러의 API를 테스트하기 위해 사용된 객체
// ProductController에서 잡고 있는 Bean 객체에 대해 Mock 형태의 객체를 생성해줌
@MockBean
ProductServiceImpl productService;
// getProduct() 메서드 테스트 코드
@Test
@DisplayName("MockMvc를 통한 Product 데이터 가져오기 테스트")
void getProductTest() throws Exception {
// given : Mock 객체가 특정 상황에서 해야하는 행위를 정의하는 메소드
// given() & willReturn()
given(productService.getProduct(123L)).willReturn(
new ProductResponseDto(123L, "pen", 5000, 2000));
String productId = "123";
// perform() 메서드 이용 -> 서버로 URL 요청을 보내는 것처럼 통신 테스트 코드를 작성해서 컨트롤러 테스트 가능
// andExcept() 메서드 를 사용해 perform() 메서드의 결과값으로 리턴된 ResultAction 객체 결과값 검증 수행 가능
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()); // 요청과 응답의 전체 내용을 확인하기 위해 andDo() 사용
// verify() : 지정된 메서드가 실행됐는지 검증하는 역할(일반적으로 given()에 정의된 동작과 대응)
verify(productService).getProduct(123L);
}
}
◻️@WebMvcTest(테스트 대상 클래스.class)
- 웹에서 사용되는 요청과 응답에 대한 테스트 수행 가능
- 대상 클래스만 로드해 테스트를 수행
- 만약 대상 클래스를 추가하지 않으면 @Controller, @RestController, @ControllerAdvice 등의
컨트롤러 관련 빈 객체가 모두 로드
- @SpringBootTest보다 가볍게 테스트하기 위해 사용
◻️ @MockBean
- 실제 빈 객체가 아닌 Mock(가짜) 객체를 생성해서 주입하는 역할 -> 실제 행위를 수행하지 X
- 따라서 해당 객체는 개발자가 Mockito의 given() 메서드를 통해 동작을 정의해야 함
◻️ @Test
- 테스트 코드가 포함되어 있다고 선언하는 어노테이션
- JUnit Jupiter에서는 이 어노테이션을 감지해서 테스트 계획에 포함시킴
◻️ @DisplayName
- 테스트 메서드의 이름이 복잡해서 가독성이 떨어질 경우 이 어노테이션을 통해 테스트에 대한 표현 정의 가능
@PostMapping()
public ResponseEntity<ProductResponseDto> createProduct(@RequestBody ProductDto productDto) {
ProductResponseDto productResponseDto = productService.saveProduct(productDto);
return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
}
// createProduct() 메서드 테스트 코드 (getProduct() 테스트 코드와 유사)
@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));
}
<Gson 의존성 추가>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
7.4.7 서비스 객체의 테스트
package com.springboot.test.service.impl;
import static org.mockito.AdditionalAnswers.returnsFirstArg;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import com.springboot.test.data.dto.ProductDto;
import com.springboot.test.data.dto.ProductResponseDto;
import com.springboot.test.data.entity.Product;
import com.springboot.test.data.repository.ProductRepository;
import java.util.Optional;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
public class ProductServiceTest {
private ProductRepository productRepository = Mockito.mock(ProductRepository.class);
// Mockito의 mock() 메서드를 통해 Mock 객체로 ProductRepository 주입
private ProductServiceImpl productService;
// 위의 Mock 객체를 기반으로 각 테스트 전 ProductService로 객체를 초기화해서 사용
@BeforeEach
public void setUpTest() {
productService = new ProductServiceImpl(productRepository);
}
@Test
void getProductTest() {
// given -> 테스트에 사용될 Product 엔티티 객체를 생성하고 ProductRepository의 동작에 대한 결과값 리턴 설정
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
// 리턴받은 ProductResponseDto 객체에 대해서 Assertion을 통해 값을 검증함으로 테스트 목적을 달성하는지 확인
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()로 부가 검증 시도
verify(productRepository).findById(123L);
}
@Test
void saveProductTest() {
// given
// any()
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());
}
}
package com.springboot.test.service.impl;
import ...
// 스프링에서 객체를 주입받기 위해 스프링 테스트 컨텍스트를 사용하도록 설정
@ExtendWith(SpringExtension.class)
// @Autowired 어노테이션으로 주입받는 ProductService를 주입받기 위해 직접 클래스를 @Import 어노테이션을 통해 사용
@Import({ProductServiceImpl.class})
class ProductServiceTest2 {
@MockBean
ProductRepository productRepository;
@Autowired
ProductService productService;
... 생략 ...
}
@MockBean 어노테이션을 사용한 테스트 환경 설정
7.4.8 리포지토리 객체의 테스트
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<H2 DB 의존성 추가>
// @DataJpaTest 어노테이션 선언
@DataJpaTest
public class ProductRepositoryTestByH2 {
// @DataJpaTest 어노테이션 선언 -> 리포지토리 정상 주입 가능
@Autowired
private ProductRepository productRepository;
// Given-When-Then 패턴으로 작성
// 데이터베이스 저장 테스트 코드
@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());
}
// 데이터베이스 조회 테스트 코드
@Test
void selectTest() {
// given
Product product = new Product();
product.setName("펜");
product.setPrice(1000);
product.setStock(1000);
// 데이터베이스 조회 테스트를 위해 Given 절에서 객체를 데이터베이스에 저장
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());
}
}
※ @DataJpaTest의 기능
◻️ JPA와 관련된 설정만 로드해서 테스트를 진행
◻️ 기본적으로 @Transactional 어노테이션을 포함해 테스트 코드가 종료되면 자동으로 데이터베이스의 롤백 진행
◻️ 기본값으로 임베디드 데이터 베이스를 사용 & 다른 데이터베이스를 사용하려면 별도의 설정을 거쳐 사용 가능
@DataJpaTest
// replace 요소는 @AutoConfigureTestDatabase 어노테이션 값을 조정하는 작업 수행
@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());
}
}
<테스트 데이터베이스 변경을 위한 어노테이션 추가>
@SpringBootTest
public class ProductRepositoryTest2 {
@Autowired
ProductRepository productRepository;
@Test
public void basicCRUDTest() {
// 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());
// 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(), "장난감");
// when
productRepository.delete(updatedProduct);
// then
assertFalse(productRepository.findById(selectedProduct.getNumber()).isPresent());
}
}
7.5 JaCoCo를 활용한 테스트 커버리지 확인
7.5.1 JaCoCo 플러그인 설정
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
</dependency>
<configuration>
<excludes>
<exclude>**/ProductServiceImpl.class</exclude>
</excludes>
</configuration>
<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>
<executions 설정>
◻️ help : jacoco-maven-plugin에 대한 도움말을 보여줌
◻️ prepare-agent : 테스트 중인 애플리케이션에 VM 인수를 전달하는 JaCoCo 런타임 에이전트의 속성을 준비
에이전트는 maven-surefire-plugin을 통해 테스트한 결과를 가져오는 역할 수행
◻️ prepare-agent-integration : prepare-agent와 유사하지만 통합 테스트에 적합한 기본값을 제공
◻️ merge : 실행 데이터 파일 세트(.exec)를 단일 파일로 병합
◻️ report : 단일 프로젝트 테스트를 마치면 생성되는 코드 검사 보고서를 HTML, XML, CSV 중에서 선택 가능
◻️ report-aggregate : Reactor 내의 여러 프로젝트에서 구조화된 보고서(HTML , XML, CSV)를 생성
보고서는 해당 프로젝트가 의존하는 모듈에서 생성
◻️ check : 코드 커버리지의 메트릭 충족 여부를 검사
◻️ dump : TCP 서버 모드에서 실행 중인 JaCoCo 에이전트에서 TCP/IP를 통한 덤프를 생성
◻️instrument : 오프라인 측정을 수행하는 명령
테스트를 실행한 후 restore-instrument-classes Goal로 원본 클래스 파일을 저장해야 함
◻️ restore-instrumented-class : 오프라인 측정 전 원본 파일을 저장하는 기능 수행
◻️ BUNDLE(기본값) : 패키지 번들(프로젝트 내 모든 파일)
& Element를 기준으로 <limits> 태그 내 <counter>와 <value>를 활용해 커버리지 측정 단위와 방식을 설정
◻️ PACKAGE : 패키지
◻️ CLASS : 클래스
◻️ GROUP : 논리적 번들 그룹
◻️ SOURCEFILE : 소스 파일
◻️ METHOD : 메서드
◻️ LINE : 빈 줄을 제외한 실제 코드의 라인 수
◻️ BRANCH : 조건문 등의 분기 수
◻️ CLASS : 클래스 수
◻️ METHOD : 메서드 수
◻️ INSTRUCTION(기본값) : 자바의 바이트 코드 명령 수
◻️ COMPLEXITY : 복잡도 (복잡도는 맥케이브 순환 복잡도 정의를 따름)
◻️ TOTALCOUNT : 전체 개수
◻️ MISSEDCOUNT : 커버되지 않은 개수
◻️ COVEREDCOUNT : 커버된 개수
◻️ MISSEDRATIO : 커버되지 않은 비율
◻️ COVEREDRATIO(기본값) : 커버된 비율
7.5.2 JaCoCo 테스트 커버리지 확인
<각 칼럼의 의미>
◻️ Element : 우측 테스트 커버리지를 측정한 단위를 표현 & 링크를 따라 들어가면 세부 사항을 볼 수 있음
◻️ Missed Instructions - Cov.(Coverage) : 테스트를 수행한 후 바이트코드의 커버리지를 퍼센티지와 바 형식 제공
◻️ Missed Branches - Cov.(Coverage) : 분기에 대한 테스트 커버리지를 퍼센티지와 바 형식으로 제공
◻️ Missed - Cxty(Complexity) : 복잡도에 대한 커버리지 대상 개수와 커버되지 않은 수를 제공
◻️ Missed - Lines : 테스트 대상 라인 수와 커버되지 않은 라인 수를 제공
◻️ Missed - Methods : 테스트 대상 메서드 수와 커버되지 않은 메서드 수를 제공
◻️ Missed - Classes : 테스트 대상 클래스 수와 커버되지 않은 메서드 수를 제공
7.6 테스트 주도 개발(TDD)
7.6.1 테스트 주도 개발의 개발 주기
7.6.2 테스트 주도 개발의 효과
Quiz
코드 1)
컨트롤러 객체 테스트 시 getProduct()의 테스트 코드를 given()과 willReturn()을 사용하여 빈 코드를 작성하면?
@WebMvcTest(ProductController.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc; // MockMvc : 컨트롤러의 API를 테스트하기 위해 사용된 객체
@MockBean
ProductServiceImpl productService;
// getProduct() 메서드 테스트 코드
@Test
@DisplayName("MockMvc를 통한 Product 데이터 가져오기 테스트")
void getProductTest() throws Exception {
given(productService.getProduct(123L)).willReturn(
new ProductResponseDto(123L, "pen", 5000, 2000));
String productId = "123";
코드 2)
@MockBean 어노테이션을 사용한 테스트 환경 설정을 위해 작성해야하는 빈칸의 어노테이션 두 가지는?
... 생략 ...
@ExtendWith(SpringExtension.class)
@Import({ProductServiceImpl.class})
class ProductServiceTest2 {
@MockBean ProductRepository productRepository;
@Autowired ProductService productService;
... 생략 ...
}
[출처] 장정우, 『스프링 부트 핵심가이드 스프링 부트를 활용한 애플리케이션 개발 실무』, 위키북스(2022), p159-207.
ⓒ 다다
[스프링3] 9장 연관관계 매핑 (1) | 2023.12.01 |
---|---|
[스프링3] 8장. Spring Data JPA 활용 (0) | 2023.11.24 |
[스프링3] 6장. 데이터베이스 연동 - 2 (0) | 2023.11.10 |
[스프링3] 6장. 데이터베이스 연동 - 1 (0) | 2023.11.03 |
[스프링3] 5장. API를 작성하는 다양한 방법 (0) | 2023.11.03 |