백엔드/Spring Boot

6. 블로그 기획하고 API 만들기 (3) - 블로그 글 작성을 위한 API 구현❶

두개의 문 2023. 7. 26. 22:27

3. 블로그 글 작성을 위한 API 구현하기 

 

- 이전 시간까지 엔티티 구성이 끝났으므로, API를 하나씩 구현해보자.

- API 구현 과정 ( 요청 방향과 반대로 구현 )

  ① 서비스 클래스에서 메서드 구현

  ② 컨트롤러에서 사용할 메서드 구현 

  ③ API 테스트 

 

클라이언트 -- 요청 ( POST /api/articles ) --> 컨트롤러 -- save( ) --> 서비스 -- save( ) --> 리포지터리 

 


1) 서비스 메서드 코드 작성하기 

 

◉ 서비스 계층에 블로그에 글을 추가하는 코드 작성 

  - 서비스 계층에서 요청을 받을 객체인 AddArticleRequest 객체 생성 

  - BlogService 클래스 생성한 다음 블로그 글 추가 메서드인 save( ) 구현 

 

 

 

 ◎ dto 패키지 생성 후, 컨트롤러에서 요청 본문을 받을 객체인 AddArticleRequest.java 파일 생성 

< DTO와 DAO 정리 >
   ▪︎ DTO ( Data Transfer Object ) 
     - 계층끼리 데이터를 교환하기 위해 사용하는 객체 
     - 로직을 가지지 않는 순수한 데이터 객체( Getter & Setter만 가진 클래스 )

   ▪︎ DAO ( Data Access Object ) 
     - 데이터베이스와 연결되고 데이터를 조회하고 수정하는데 사용하는 객체 
     - 데이터베이스에 접근하기 위한 로직과 비즈니스 로직을 분리하기 위해 사용 

 

▪︎ AddArticleRequest.java

package org.choongang.dto;

import org.choongang.domain.Article;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class AddArticleRequest {
	private String title;
	private String content;
	
	// * 생성자를 사용해 객체 생성 
	// : Article.java의 생성자 위에 @Builder 추가 -> 생성자 호출 가능 
	public Article toEntity() {
		return Article.builder()	// 롬복이 빌더 패턴을 찾아 생성자 이용 가능 
				.title(title)
				.content(content)
				.build();
	}
}

• toEntity( ) 메서드 

  - 빌더 패턴을 사용해 DTO를 엔티티로 만들어주는 메서드 

     → 추후 블로그 글을 추가할 때 저장할 엔티티로 변환하는 용도로 사용함

  - Article.java에서 생성자 위에 롬복의 애너테이션인 '@Builder' 추가 

     → toEntity( ) 메서드에서 빌터 패턴 이용해 생성자 이용 가능 

 

 

 

 

◎ service 패키지 생성 후, BlogService.java를 생성해  BlogService 클래스 구현 

 

▪︎  BlogService.java

package org.choongang.service;

import java.util.List;
import org.choongang.domain.Article;
import org.choongang.dto.AddArticleRequest;
import org.choongang.repository.BlogRepository;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor	// final이 붙거나 @NotNull이 붙은 필드의 생성자 추가 
@Service 	// 비즈니스 로직 처리, 빈으로 등록 ( ∵ @Component가 존재 )
public class BlogService {
	// * 데이터베이스와 작업 모델 ( 특히 JPA의 모델은 리포지토리 ) 멤버변수 선언 
	// 의존성 주입 방법 
	private final BlogRepository blogRepository;	// 보통 클래스명의 소문자로 객체변수 선언 
	
	// * 블로그 글 추가 메서드 -> 리포지토리에 의뢰 : 보통 메서드 이름을 동일하게 설정함 
	// => jpa의 save() : sql의 insert into 명령문을 자동으로 생성 
	
	// * AddArticleRequest : 컨트롤러의 요청 본문을 받을 서비스 계층의 객체
	// 	- toEntity() 메서드 : Article 객체의 빌더패턴을 사용 → 생성자 방식으로 새로운 글이 추가됨 
	public Article save(AddArticleRequest request) {
		return blogRepository.save(request.toEntity());
	}

❶ @RequiredArgsContructor 

   - 빈을 생성자로 생성하는 롬복에서 지원하는 애너테이션 

   - final 키워드나 @NotNull이 붙은 필드로 생성자를 만들어줌 

      → 클래스에 선언된 final 변수들, 필드들을 매개변수로 하는 생성자를 자동으로 만들어줌 

   - 주로 DI( 의존성 주입 ) 중 Constructor Injection( 생성자 주입 )을 임의의 코드 없이 자동으로 설정 

 


   * @RequiredArgsContructor 적용 전

public class LombokTest {
	
    private final String id;
    private final String name;
    
    @Autowired
    public LombokTest( String id, String name ){
    	this.id = id ;
        this.name = name ;
    }
}

 

 

   * @RequiredArgsContructor 적용 후 

@RequiredArgsConstructor
public class LombokTest {
	
    private final String id;
    private final String name;
  
}

 ⇒ 새로운 필드를 추가할 때 다시 생성자를 만드는 번거로움을 없앨 수 있음 

     ( @Autowired 사용하지 않고 의존 주입 )


 

 

❷ BlogService의 save( ) 메서드 

	public Article save(AddArticleRequest request) {
		return blogRepository.save(request.toEntity());
	}

  • request.toEntity( )

     - AddArticleRequest객체의 toEntity() 메서드 호출 결과 

       : Article 클래스의 생성자를 빌더패턴으로 이용해 Article 객체 생성 

   

  • BlogRepository.save( )

     - JpaRepository에서 지원하는 저장 메서드인 save( ) 메서드로 AddArticleRequest 클래스에 저장된 값들을 article 데이터베이스에 저장함 

 

 

 


2) 컨트롤러 메서드 코드 작성하기 

 

◉ 이제 URL에 매핑하기 위한 컨트롤러 메서드 추가 

 

▪︎ 컨트롤러 메서드의 URL 매핑 애너테이션 ( HTTP 메서드에 대응함 )

  : @GetMapping, @PostMapping, @PutMapping, @DeleteMapping 등 사용 가능 

 

▪︎ /api/articles에 POST 요청이 들어오는 경우

  ❶ @PostMapping을 이용해 요청을 매핑 : 해당 컨트롤러 찾음 

  ❷ 컨트롤러에서 블로그 글을 생성하는 BlogService의 save( ) 메서드를 호출  

  ❸ 그 결과, 생성된 블로그 글을 반환하는 작업을 할 addArticle( ) 메서드를 작성

 

 - 전체적인 흐름 

   BlogService 객체 : 비즈니스 로직 처리 → BlogRepository 객체 : 데이터베이스 처리 후, Article 타입 객체로 반환됨 → 데이터베이스에 저장 

 

 

 

◎ controller 패키지 생성 후, BlogApiController.java 파일 생성 

 

▪︎ BlogApiController.java

package org.choongang.controller;

import java.util.List;

import org.choongang.domain.Article;
import org.choongang.dto.AddArticleRequest;
import org.choongang.dto.ArticleResponse;
import org.choongang.service.BlogService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;


@RequiredArgsConstructor // final이 붙거나 @NotNull이 붙은 필드의 생성자 추가 
						 // 빈을 생성자로 생성하는 롬복에서 지원하는 애너테이션 
@RestController		// HTTP Response Body에 객체 데이터를 JSON 형식으로 반환하는 컨트롤러 
public class BlogApiController {
	// * 의존성 주입 중 생성자 주입에 해당 
    //   → @RequiredArgsConstructor으로 인해 임의의 코드없이 가능  
	private final BlogService blogService;
	
    // * HTTP 메서드가 POST일 때 전달받은 URL와 동일하면 메서드로 매핑 
    @PostMapping("/api/articles")
    // * 요청 본문 값 매핑 
    // * 요청 API 처리 
	//  - AddArticle() 메서드 
    	//    : 웹 브라우저가 제공한 양식 데이터(JSON 데이터)를 데이터베이스에 저장하는 메서드 
	//      양식 데이터 -> POST 방식 / 데이터 전처리 객체인 BlogService 호출 
	public ResponseEntity<Article> addArticle(@RequestBody AddArticleRequest request){
	
		Article savedArticle = blogService.save(request);
		
	 	// * 요청한 자원이 성공적으로 생성되었으며, 저장된 블로그 글 정보를 응답 객체에 담아 전송 
        	//   : 응답코드로 201, 즉 Created를 응답하고 테이블에 저장된 객체를 반환함 
		return ResponseEntity.status(HttpStatus.CREATED) 
					.body(savedArticle);
		
	}
	
}

 

 


◉ Spring에서 컨트롤러를 지정해주기 위한 어노테이션 

• 전통적인 Spring MVC 컨트롤러인 @Controller와 Restful 웹 서비스의 컨트롤러인 @RestController 2종류로 분류

• 두 어노테이션의 차이점 : HTTP Response Body가 생성되는 방식 

 

• @Controller 

  - 주로 View를 반환하기 위해 사용함 

  - Data를 반환해야 하는 경우도 존재 ( JSON 형태로 데이터 반환 가능 )

 

• @RestController 

  - @Controller에 @ResponseBody가 추가된 것

  - 주로 JSON 형태로 객체 데이터를 반환하기 위해 사용함 

  - 데이터를 응답으로 제공하는 REST API를 개발할 때 주로 사용하며, 객체를 ResponseEntity로 감싸서 반환함  

 


 

◉ Spring Framework에서 비동기 통신, 즉 API 통신을 구현하기 위한 어노테이션 

▪︎ HTTP(HyperText Transfer Protocol) 통신이란?
  - HTTP 통신 방식은 기본적으로 '요청과 응답(request, response)'로 이루어져 있음 
  - 즉, 클라이언트가 요청(HttpRequest)을 서버에 보내면 서버는 클라이언트에게 응답(HttpResponse)하는 구조 

▪︎ HttpRequest, HttpResponse 구조 
  - HttpRequest 구조 : start line / headers / body 세 부분으로 구성
  - HttpResponse 구조 : status line / headers / body 세 부분으로 구성 
  - 클라이언트와 서버 간의 HTTP 통신에서 요청과 응답을 보낼 때, 필요한 데이터를 담아서 보내는 공간이 body! 
     요청하는 요청 본문을 requestBody, 응답하는 응답 본문을 responseBody라고 함 
  - body에 담기는 데이터 형식은 여러 종류가 있으나, 가장 대표적으로 사용되는 것이 JSON이며, xml 형식도 사용됨 

 

• 요청 (클라이언트 → 서버) : @RequestBody

   응답 (서버 → 클라이언트) : @ResponseBody 

   ⇒ 이와 같은 어노테이션을 명시함으로써 MessageConverter를 통한 데이터 변환 과정을 거치게 됨 

 

• @RequestBody 

  - 클라이언트에서 서버로 JSON 형식의 requestBody로 요청 데이터를 전송했을 때, Java에서는 해당 JSON 형식의 데이터를 받기 위해서 JSON 기반의 HTTP Body를 자바 객체로 반환 

  - 즉, HTTP를 요청할 때 응답에 해당하는 값을 @RequestBody 애너테이션이 붙은 대상 객체인 AddArticleRequest에 매핑함 

 

• @ResponseBody

  - 요청된 데이터를 처리 후, 서버에서 클라이언트로 다시 응답데이터를 보낼 때 자바 객체를 JSON 기반의 HTTP Body로 변환 

      


 

◉ ResponseEntity 

 - Spring Framework에서 제공하는 클래스 중 HttpEntity 클래스가 존재 

 - HttpEntity 클래스 : HTTP 요청(Request) 또는 응답(Response)에 해당하는 HttpHeader HttpBody를 포함하는 클래스

                                   → HttpEntity 클래스를 상속받아 구현한 클래스가 RequestEntity, ResponseEntity 클래스

 - RequestEntity 클래스 : 사용자의 HttpRequest에 대한 응답 데이터를 포함하는 클래스 → HttpStatus, HttpHeaders, HttpBody를 포함

 


 

HTTP Status Code(HTTP 상태코드) 

 - 클라이언트가 보낸 HTTP 요청에 대한 서버의 응답을 코드로 표현한 것으로, 해당 코드로 요청의 성공 / 실패 / 실패요인 등을 있음 

 - 응답코드 

응답코드 설명 
200 OK 요청이 성공적으로 수행되었음 
201 Created 요청이 성공적으로 수행되었고, 새로운 리소스가 생성되었음 
400 Bad Request 요청 값이 잘못되어 요청에 실패했음 
403 Forbidden 권한이 없어 요청에 실패했음 
404 Not Found 요청 값으로 찾은 리소스가 없어 요청에 실패했음 
500 Internal Server Error 서버 상에 문제가 있어 요청에 실패했음 

 


▪︎ JPA의 save( ) 메서드 : 기본적으로 잘 처리되면 저장한 결과를 확인하기 위해 테이블의 저장결과를 다시 검색(Select)한 레코드를 반환함 → 그 결과를 통해 잘 저장되었음을 확인할 수 있음 

 

▪︎ BlogService가 save( ) 메서드를 잘 처리한 후, 반환한 Article 객체를 Spring Boot가 자동으로 응답하기 위해서는 응답객체에 등록해야 함 → ResponseEntity에 현재 처리 상황을 등록하면 자동으로 다음 처리가 이루어짐 

▪︎ 응답 상황은 정상적인 처리(HttpStatus.CREATED=200계열)인 경우를 의미 

▪︎ HttpStatus.CREATED 상태 : 내부에서 자동전송 하므로 보낼 응답 결과(savedArticle)를 내용 속성에 등록하면 됨

 

 

 


3) API 실행 테스트하기 

 

◉ 서비스 메서드, 컨트롤러 메서드를 구현 즉, API를 완성했으니 API가 잘 동작하는지 테스트해보자.  

    - 실제 데이터를 확인하기 위해 H2 콘솔을 활성화해야 함 

       → application.yml 파일 수정 

 

 

① application.yml 수정 

# 서버 포트를 웹 기본포트인 80으로 설정 
server:
  port: 80

# 뷰 모델 템플릿 엔진을 jsp로 변경   
spring:
  mvc:
    view:
      # jsp 파일 시작 경로 지정 : /scr/main/webapp/WEB-INF/view/를 의미 
      prefix: /WEB-INF/view/
      suffix: .jsp 
  # 데이터베이스 모델을 ORM 기술인 JPA 사용     
  jpa:
    # jpa로 데이터베이스 처리하는 실제 sql 명령문을 로그로 보이도록 설정 
    show-sql: true
    properties:
      hibernate:
        # JPA 기술 스펙의 실제 구현 패키지인 hibernate에서 sql 생성 시 정렬해서 보이도록 설정 
        format-sql: true   
    defer-datasource-initialization: true   
  
  # H2 활성화시키기 위해 코드 추가       
  datasource:
    url: jdbc:h2:mem:testdb
 
  h2:
    console:
      enabled: true

 

 

② 스프링 부트 서버 실행 후, 포스트맨 실행 

  - HTTP 메서드 : POST

  - URL : http://localhost/api/articles

  - Body : JSON 

  - 요청 창에 테스트용 데이터를 JSON 형식으로 작성 후, Send 클릭 시 응답 값 확인 가능  

 

③ H2 데이터베이스에 잘 저장되었는지 확인 

  - 웹 브라우저에서 'localhost/h2-console'에 접속

     단, 스프링 부트 서버는 여전히 실행 중인 상태여야 함 

  - H2 콘솔 로그인 화면이 출력 → 각 항목의 값을 잘 확인해 입력한 뒤 Connect를 클릭해 로그인 

  - 스프링 부트 서버 안에 내장되어 있는 H2 데이터베이스에 접속하고 데이터 확인 가능 

 

 

 

④ [SQL statement:] 입력 창에 'SELECT * FROM ARTICLE'을 입력한 뒤 [Run]을 클릭하여 쿼리 실행 

     → H2 데이터베이스에 저장된 데이터 확인 가능 

   - 또한, 왼쪽 화면에서 'ARTICLE'이라는 테이블 확인 가능 

     → 애플리케이션을 실행하면 자동으로 생성한 엔티티 내용을 바탕으로 테이블이 생성되고, 테스트로 요청한 POST 요청에 의해 데이터가 실제로 저장됨을 확인할 수 있음 

 

 

 


4) 반복 작업을 줄여 줄 테스트 코드 작성하기 

 

◉ H2 콘솔에 접속해 쿼리 입력하여 데이터가 저장되어있는지, 그것이 실제로 들어있는지 확인하는 작업을 매번 테스트하기에 번거로움

   → 작업을 줄여줄 코드를 작성해보자 

 

 

 

① BlogApiController 클래스 클릭 후, New - Others - test - JUnit Test Case 클릭 

      : src/test/java/패키지 아래에 BlogApiControllerTest.java 파일 생성 

 

▪︎ BlogApiController.java

package org.choongang.controller;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.List;
import org.choongang.domain.Article;
import org.choongang.dto.AddArticleRequest;
import org.choongang.repository.BlogRepository;
import org.junit.jupiter.api.BeforeEach;
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.AutoConfigureMockMvc; // 테스트용 MVC 환경 자동설정 
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import com.fasterxml.jackson.databind.ObjectMapper;

// 스프링부트 테스트를 이용한 테스트를 할 경우, 반드시 '@SpringBootTest' 애너테이션 추가 
@SpringBootTest 	// 테스트용 애플리케이션 컨텍스트 
@AutoConfigureMockMvc 	// MockMVC 생성 
class BlogApiControllerTest {
	// 멤버변수 선언 
	@Autowired 
	protected MockMvc mockMvc;
	
	// 객체 데이터들(DTO, VO) 사이의 데이터 매핑 
	@Autowired 
	protected ObjectMapper objectMapper;
	
	// 가상 테스트 웹 환경 공간을 생성할 때 필요한 객체 
	@Autowired 
	private WebApplicationContext context;
	
	// 이 이후에는 테스트에 필요한 객체들이 나옴 
	@Autowired 
	BlogRepository blogRepository;
	
	@BeforeEach 	// 테스트 메서드 실행 전, 매번 실행되는 메서드로 초기화 담당 
	// mockMVC 객체 생성 및 초기화 
	public void mockMvcSetUp() {
		this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
		
		// 새로 테스트 메서드 실행 전, 기존 DB 데이터 초기화 
		blogRepository.deleteAll();
		
	}
}

▪︎ ObjectMapper 클래스 

   - 이 클래스로 만든 객체 : 직렬화 또는 역직렬화할 때 사용함 

   - 직렬화(Serialize) : 자바 시스템 내부에서 사용하는 자바 객체 → 외부에서 사용할 수 있도록 JSON 데이터로 변환

   - 역직렬화(Deserialize) : JSON 데이터 → 자바 객체로 변환 

   - 직렬화 / 역직렬화를 사용하는 이유

     : 복잡한 데이터 구조의 클래스의 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 바로 직렬화, 역직렬화가 가능함 

       ( 기본 조건 : java.io.Serializable 인터페이스를 상속받은 객체여야 함 ) 

 

 

▪︎ 블로그 글 생성 API 테스트하는 코드 작성 

  : given - when - then 패턴 생성 

given 블로그 글 추가에 필요한 요청 객체를 만듦
when 블로그 글 추가 API에 요청을 보냄 
이 때 요청 타입은 JSON이며, given절에서 미리 만들어둔 객체를 요청 본문으로 함께 보냄 
then 응답 코드가 201 Created인지 확인함
Blog를 전체 조회해 크기가 1인지 확인하고, 실제로 저장된 데이터와 요청 값을 비교함 
	//// 기능 테스트 //// 
	@DisplayName("addArticle : 블로그 글 추가에 성공한다.")
	@Test
	public void addArticle() throws Exception{
		
		// * given
        	//   : 블로그 글 추가에 필요한 요청 객체를 만듦.
		//  - 웹으로 RestAPI를 테스트하는 것 → API 경로 Uri 필요 
		//  - 아티클을 추가하므로 추가하는 내용 → 즉 title, content 변수에 테스트할 데이터 추가 
		final String url = "/api/articles";
		final String title = "title";
		final String content = "content";
		//  - AddArticleRequest DTO 클래스에 데이터를 추가한다. 
		final AddArticleRequest userRequest = new AddArticleRequest(title, content);
		
		//  - userRequest 객체를 JSON으로 직렬화 
		final String requestBody = objectMapper.writeValueAsString(userRequest);
		
		
		// * when
		//   : 설정한 내용을 바탕으로 블로그 글 추가 API에 요청 전송 
		//  - 위에서 설정한 조건값을 기초로 해서 인터넷을 통하여 
		//   WAS 서버에 요청하는 것과 같은 가상 테스트 실행을 한다.  
		//   설정한 내용을 가상 테스트 컨텍스트에 요청 전송한다. 
		//  - mockMvc : 가상 테스트 웹 컨텍스트 
		//  - perform : 다음 요청 메서드를 수행하라 
		//  - contentType : 전송하는 데이터 형식 지정 
		//  - MediaType : 전송하는 데이터 형식 지정 
		//  - MediaType.APPLICATION_JSON_VALUE : 보내는 데이터형식 JSON 객체 형식 
		//  - content : 실제 요청 시 보낼 데이터 
		
		ResultActions result = mockMvc.perform(post(url)
				.contentType(MediaType.APPLICATION_JSON_VALUE)
				.content(requestBody));
		
		
		// * then
		//   : 우리가 기대하는 값과 같은지 비교 
		//  - result : 위의 테스트 요청 실행 결과
		//  - andExpect : 다음의 결과를 기대한다.
		//  - status() : 응답 결과
		//  - isCreated() : 응답코드 201 JSON 등의 리소스가 만들어졌다.
		result.andExpect(status().isCreated());
		
		// 위의 실행결과가 기대값( 진짜로 DB에 잘 들어갔는지? )이 성립되는지 확인해보자 
		// 레포지토리의 findAll() : JPA에서 모든 레코드를 가져오는 메서드 
		// → SELECT * FROM Article ;
		List<Article> articles = blogRepository.findAll();
		
		// @BeforeEach에서 테스트 WAS 서버를 초기화하고 
		// 기존에 이미 들어있는 데이터를 모두 삭제했으므로, 
		// addArticle()가 잘 실행되었다면, 레코드 크기가 1일 것이다. 
		// article.size() : 리스트 컬렉션의 크기 구하기 
		assertThat(articles.size()).isEqualTo(1);
		// articles.get(인덱스번호) : articles의 첫번째 레코드를 가져와라 
		assertThat(articles.get(0).getTitle()).isEqualTo(title);
		assertThat(articles.get(0).getContent()).isEqualTo(content);
	}

 

 

• MockMvc

  - 실제 객체와 비슷하지만, 테스트에 필요한 기능만 가지는 가짜 객체를 만들어서 애플리케이션 서버에 배포하지 않고도 스프링 MVC 동작을 재현할 수 있는 클래스를 의미 

  - MockMvc의 메서드 

    perform( ) : 요청을 전송하는 역할 → 결과로 ResultActions 객체를 반환 

    ResultActions 객체의 andExpect( ) 메서드로 리턴 값을 검증하고 확인 가능 

 

• post( ) : HTTP 메서드 결정 → 인자로 url 경로 이용 

• contentType( ) : 요청을 보낼 때 JSON, XML 등 다양한 타입 중 하나를 골라 요청을 보냄 

 → contentType(MediaType.APPLICATION_JSON_VALUE)

    : 요청을 JSON TYPE의 데이터만 담고 있는 요청을 처리하겠다는 의미 

 

 

 

▪︎ 테스트 실행 결과