반응형

↓↓↓ 이전 내용

2023.12.30 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (6) - JUnit TEST 기본 설정 및 repository 계층 test 예제

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (6) - JUnit TEST 기본 설정 및 repository 계층 test 예제

↓↓↓ 이전 내용 2023.12.15 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (5)-사용자 정의 ResponseEntity [REST API] Spring Boot로 REST API CRUD 간단 구현 (5)-사용자 정의 ResponseEntity 이전 내

im-gonna.tistory.com

 

지난 시간 repository 계층의 test를 수행했다.

이번에는 service 계층의 test를 수행하겠다.

 

💡 Service layer test 클래스 자동 생성하기

 

직접 디렉토리를 생성하고 test 클래스를 작성해도 되지만, service 클래스에서 "create test" 버튼 하나로 test 클래스를 바로 생성할 수 있다.

 

create test를 누르면 다음과 같은 창이 뜬다.

 

JUnit5는 기본으로 설정되어 있으며, test class 이름도 알아서 지정해 주는데, 적절해 보이므로 그대로 사용할 것이다.

마찬가지로 설정/해제 메서드를 추가해준다. setUp과 tearDown에 해당한다.

그리고 class의 모든 메서드에 대한 단위 test를 위해 모두 ✔ 하여 추가해준다.

 

 

OK버튼을 누르면, 위와같이 test 디렉토리 하에 repository와 병렬하게 service 단이 생성되었고, 그 안에 test 클래스도 잘 생성되었다.

 

💡 CloudVendorService test 코드 작성하기

 

클래스를 들여다 보면, jUnit Jupiter assertion이 import되어 있는데, 우리는 저번 시간과 동일하게 기본 assertion을 사용할 것이기 때문에 이 부분은(이미 주석처리 되어있지만) 지워준다.

 

우리는 arg.com에서 제공하는 assertion 라이브러리를 사용할 것이다.

 

 

  • Mock 객체 생성하기

 

서비스 계층이 데이터베이스와 직접적으로 통신해서는 안되기 때문에, 데이터베이스 응답을 얻으려면 repository layer를 moking해야 한다. 이 말은 repository 클래스의 객체를 mock object (모의 객체)로서 생성하여 사용함으로써 데이터를 사용한다는 것이다.

 

가상 객체라고 생각하면 된다.

 

따라서 CloudVendorRepository의 경우 mock 객체로서 사용한다.

 

autoCloseable 객체를 사용하는 이유는 리소스를 자동 해제하는 기능으로서 자원을 안전하게 사용하기 위함이다.

 

 

  • 설정 메서드 setUp()

 

클래스 실행 시 전체 클래스에 대한 mock이 자동으로 열리도록 한다. 이는 Mockito의 openMocks 메서드를 사용한다.

그리고 repository test에서와 동일하게, service 객체와 cloudVendor 객체를 생성해준다.

 

  • 해제 메서드

해제 시에는 autoCloseable을 close해주면 된다.

 

  • create 메서드에 대한 test

  • cloudvendor와 repository에 대해서 mock 객체를 선언해준다.
  • when 메서드를 사용해서 cloudvendorrepository의 save메서드를 호출하였을 때 cloudvendor를 return하도록 하여, 저장이 성공한 것처럼 보이도록 설정한다.
  • asserthat 메서드를 통해 실제 service 클래스의 create 메서드를 호출한 결과가 Success와 같은지 확인한다.
  • 결과가 true이면 test가 통과된다. 

createCloudVendor()의 unit test 결과

 

 

  • update 메서드에 대한 test

    • update 메서드는 앞선 create와 유사하므로 실제 service 클래스에서 호출하는 메서드를 update로만 바꾸어 주면 된다.
    • 이 역시 호출결과가 Success를 반환하면 test 통과이다.

updateCloudVendor()의 unit test 결과

 

  • get 메서드에 대한 test

  • mock 객체를 생성한다.
  • when 메서드를 사용해서 cloudvendorrepository의 findById메서드를 호출하였을 때, cloudvendor를 return하도록 하는데, 이때 find결과가 있을수도 없을 수도 있으므로 optional로 nullable을 설정해준다.
  • asserthat 메서드를 통해 실제 service 클래스의 get 메서드를 호출하여 그 cloudvendor의 name과 클래스에서 선언한 cloudvendor의 name이 동일한지 확인한다.
  • 결과가 true이면 테스트는 통과이다.

getCloudVendor()의 unit test 결과

 

  • get all 메서드에 대한 test

  • 앞선 get메서드와 로직은 비슷하나, all 이므로 list단위로 반환한다는 차이가 있다.
  • 따라서 리스트 중 첫번째 cloudVendor의 phoneNumber를 비교하도록 한다.
  • 결과로 true를 반환하면 test는 통과이다.

getAllCloudVendor()의 unit test 결과

 

  • delete 메서드에 대한 test

  • 이 메서드의 test에서는 mock을 생성할 때, 실제로 repository 클래스의 메서드를 사용할 수 있도록 설정해준다.
  • 이를 위해서 Mokito의 CALLS_REAL_METHODS를 인자로 함께 넣어준다.
  • 또한 doAnswer를 사용해, cloudVendorRepository가 호출되었을 때 실제로 Method를 사용하도록 하고 deleteById 메서드를 호출하도록 한다.
  • asserThat으로 service의 delete메서드를 수행하고 나서 Success를 반환해야 한다.
  • 반환한다면 test는 통과한다.

deleteCloudVendor()의 unit test 결과

 

service test 클래스의 test 실행 결과 모두 통과하는 것을 확인하였다.

service 단의 모든 unit test가 성공적

 

 

test시에는 내 코드가 실제로 구현한 모든 코드를 test할 수 있는지 확인할 필요가 있다.

보통 90%이상이면 옳다고 판단한다.

IntelliJ에서는 아래와 같이 test coverage를 확인할 수 있도록 기능을 제공하고 있다. 

test coverage 확인 결과 91%

 

service class의 test coverage 확인 결과 91%가 cover되는 것을 확인하였다.

그럼 어느 부분을 cover하지 못했을까?

실제 service class의 코드를 살펴보자.

 

각 메서드마다 test가 cover된 부분은 왼쪽에 초록색 바로 표시가 된다. 

내리다 보니, getCloudVendor 메서드 쪽에서 중간에 빨간색 바를 확인하였다.

 

왼쪽을 보면 throw~ 예외처리 부분에 대한 test의 범위는 빨간색으로 표시되면서 포함되지 않은 것을 확인할 수 있다.

 

이렇게 test covergae를 통해서 내가 짠 test 코드가 실제로 구현의 어느 부분까지 cover할 수 있는지 확인 가능하다.

 

 

오늘은 service layer의 test code를 구현해보았다.

 

다음 시간에는 Controller의 test를 구현해보자.

 

↓↓↓ 다음 내용

2024.01.10 - [Web Application/Backend] - [REST API] Spring Boot로 REST API 프로젝트 (8) - JUnit TEST controller 계층 test 예제

 

[REST API] Spring Boot로 REST API 프로젝트 (8) - JUnit TEST controller 계층 test 예제

↓↓↓ 이전내용 2024.01.05 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (7) - JUnit TEST service 계층 test 예제 [REST API] Spring Boot로 REST API CRUD 간단 구현 (7) - JUnit TEST service 계층 test 예

im-gonna.tistory.com

 

반응형
반응형

이전 내용↓↓↓

2023.11.11 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (2)

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (2)

지난 시간, 간단한 model을 생성하고, 해당 model을 클래스 내에서 직접 생성하고, 클라인언트로부터 받은 정보를 통해 CRUD를 구현해 보았다. ↓↓↓ 2023.11.08 - [Web Application/Backend] - [REST API] Spring Boot

im-gonna.tistory.com

 

DB연동에 이어서, db에 있는 정보를 CRUD 처리해보자.

 

💡 Model을 entity로 설정
  • model인 CloudVendor를 entity로써 하나의 테이블로 선언해주기 위해, CloudVendor 클래스를 수정해준다.

  • CloudVendor를 Entity로 선언해주고, Table 이름은 "cloud_vendor_info"로 설정하여 생성 시 이 이름으로 table이 생성된다.
  • 이 테이블의 primary key를 vendorId로 설정해주기 위해, vendorId위에 @Id 를 추가해준다.

 

💡 CloudVendor Repository 생성하기
  • CloudVendor Entity에 대한 데이터베이스 작업인 CREATE, READ, UPDATE, DELETE를 수행하기 위해서 JpaRepository 인터페이스를 상속받아(extends) 사용한다.

  • 일단 repository 폴더를 생성해주고, 인터페이스로 CloudVendorRepository 파일을 추가한다.

  • CloudVendorRepository를 생성할 때, JpaRepository에 Entity이름과, Entity의 pk 데이터타입을 이용해서 define할 수 있다.

 

💡 CloudVendorService 생성하기
  • Service 단에서는 Repository에 접근하여 동작을 수행하도록 한다.

  • Service 폴더를 생성하고, CloudVendorService 라는 이름의 인터페이스를 먼저 생성한다.
  • 인터페이스를 implements하여 사용한 것이 CloudVendorServiceImpl이다.
  • 이렇게 하는 이유는, 인터페이스로 정의해서 서비스 구현체에 직접 의존하지 않고 인터페이스에 의존하게 하여 결합도를 낮추기 위함이다.
public interface CloudVendorService {//인터페이스로 정의해서 서비스 구현체에 직접 의존하지 않고 인터페이스에 의존하게 하여 결합도를 낮춤
    public String createCloudVendor(CloudVendor cloudVendor);
    public String updateCloudVendor(CloudVendor cloudVendor);
    public String deleteCloudVendor(String cloudVendorId);
    public CloudVendor getCloudVendor(String cloudVendorId);
    public List<CloudVendor> getAllCloudVendors();
}
  • CloudVendorService 인터페이스이다.
  • 구체적인 동작은 없고, 메서드 정의만 해두었다.
  • CloudVendor model을 CRUD하는 각각의 메서드이다.
    createCloudVendor : 입력으로 받은 cloudVendor 정보로 CloudVendor를 생성하는 메서드이다.
    updateCloudVendor : 입력으로 받은 cloudVendor 정보로 CloudVendor를 수정하는 메서드이다.
    deleteCloudVendor : 입력으로 받은 특정 cloudVendorId에 대한 CloudVendor 정보를 삭제하는 메서드이다.
    getCloudVendor : 입력으로 받은 특정 cloudVendorId에 대한 CloudVendor 정보를 읽어오는 메서드이다.
    getAllCloudVendor :  모든 CloudVendor 정보를 읽어오는 메서드로, List 형태로 가져온다.
@Service
public class CloudVendorServiceImpl implements CloudVendorService{
    CloudVendorRepository cloudVendorRepository;

    public CloudVendorServiceImpl(CloudVendorRepository cloudVendorRepository) {
        this.cloudVendorRepository = cloudVendorRepository;
    }

    @Override
    public String createCloudVendor(CloudVendor cloudVendor) {
        //more Business logic
        cloudVendorRepository.save(cloudVendor);
        return "Success";
    }

    @Override
    public String updateCloudVendor(CloudVendor cloudVendor) {
        //more Business logic
        cloudVendorRepository.save(cloudVendor);//jpa의 save 메서드의 경우, 이미 있는 값에 대해서는 변경된 내용만 추적해서 수정합니다
        return "Success";
    }

    @Override
    public String deleteCloudVendor(String cloudVendorId) {
        //more Business logic
        cloudVendorRepository.deleteById(cloudVendorId);
        return "Success";
    }

    @Override
    public CloudVendor getCloudVendor(String cloudVendorId) {
        //more Business logic
        return cloudVendorRepository.findById(cloudVendorId).get();
    }

    @Override
    public List<CloudVendor> getAllCloudVendors() {
        //more Business logic
        return cloudVendorRepository.findAll();
    }//이 안에 있는 모든 메서드를 implements 하기 전에는 빨간줄이 떠있음

}
  • CloudService를 implements한 것이 CloudVendorServiceImpl이다.
  • CloudVendorRepository 객체를 하나 생성하고, repository메서드를 이용해 각 메서드를 동작하도록 한다.
  • 처음에 모든 메서드를 implements하기 전에는 빨간 줄이 떠있게 된다. 빨간 표시를 누르면 자동으로 override할 수 있는 틀을 가져오는 버튼이 있다. 이 버튼을 누르면 override해야 할 메서드를 모두 가져와 준다.
  • 각 메서드에 맞는 repository의 메서드를 호출하여 데이터베이스 작업이 진행되도록 한다.
  • repository의 save() 메서드는 입력받은 Entity 정보를 삽입, 수정한다.
    해당하는 pk가 없었다면 삽입, 이미 있는 pk에 일부 정보만 바뀌었다면 수정 하도록 한다.
  • repository의 deleteById() 메서드는 입력받은 Entity의 pk를 통해 해당 정보를 삭제한다.
  • repository의  findById() 메서드는 입력받은 Entity의 pk를 통해 해당 정보를 가져온다.
  • repository의 findAll() 메서드는 모든 Entity 정보를 가져온다.

 

💡 CloudVendor Controller 수정하기
  • Controller의 CloudVendor Controller를 CloudVendorController로 클래스 이름을 수정한다.
  • Controller에서는 서비스 단의 메서드를 호출하여 CRUD를 수행하도록 한다.
public class CloudVendorController {
    CloudVendorService cloudVendorService;

    public CloudVendorController(CloudVendorService cloudVendorService) {
        this.cloudVendorService = cloudVendorService;
    }

    //Read Specific Cloud Vendor Details
    @GetMapping("{vendorId}")
    public CloudVendor getCloudVendorDetails(@PathVariable("vendorId") String vendorId)//위 url 경로로부터 값을 받아와서 사용하므로 어노테이션 추가
    {
        return cloudVendorService.getCloudVendor(vendorId);
    }

    //Read All Cloud Vendor Details
    @GetMapping
    public List<CloudVendor> getCloudVendorDetails()//위 url 경로로부터 값을 받아와서 사용하므로 어노테이션 추가
    {
        return cloudVendorService.getAllCloudVendors();
    }

    //Create Cloud Vendor
    @PostMapping
    public String postCloudVendorDetails(@RequestBody CloudVendor cloudVendor)
    {
        cloudVendorService.createCloudVendor(cloudVendor);
        return "Cloud Vendor Created Successfully";
    }

    //Update Cloud Vendor
    @PutMapping//수정이라 하더라도 특정 id 안받아도됨-> jpa의 save 메서드가 이미 있는 값에 대해서는 변경을 하고, 없으면 삽입을 하는 두가지 역할을 수행하기 때문
    public String updateCloudVendorDetails(@RequestBody CloudVendor cloudVendor)
    {
        cloudVendorService.updateCloudVendor(cloudVendor);
        return "Cloud Vendor Updated Successfully";
    }

    @DeleteMapping("{VendorId}")
    public String deleteCloudVendorDetails(@PathVariable("VendorId") String vendorId)
    {
        cloudVendorService.deleteCloudVendor(vendorId);
        return "Cloud Vendor Deleted Successfully";
    }
}
  • CloudVendorService 객체를 하나 생성한다.
  • @PathVariable은 url로 받는 값을 메서드의 인자로 받아 사용하기 위한 어노테이션이다.
  • @RequestBody는 프론트 단에서 입력한 값을 가져와 메서드의 인자로 받아 사용하기 위한 어노테이션이다.
  • 각각의 CRUD 메서드에 맞는 Service 단의 메서드를 호출하여 수행한다.

 

💡 실행 결과
  • 첫 실행 후에는, 데이터 베이스에 Cloud Vendor Entity에 대한 테이블이 처음 생성되므로, 해당 테이블의 레코드는 Empty set 상태임을 확인할 수 있다.
  • POST MAN을 사용하여 정보를 입력한다.
  • post 모드에서 C1-C5  총 5개의 레코드를 삽입한다.

  • 실행 결과, 잘 삽입되었다는 메시지가 출력된다
Cloud Vendor Created Successfully

 

  • GET 모드에서 C3에 대한 값을 가져온다.

  •  실행 결과 C3에 대한 모델 값이 잘 반환된다.

  • GET 모드에서 모든 모델 값을 가져온다.

  • 실행 결과 모든 모델 값이 잘 반환된다.
  • PUT 모드에서 C4에 대한 모델 값을 address를 London으로 변경해본다.

  • 실행 결과 잘 update되었다는 메시지가 출력된다.
Cloud Vendor Updated Successfully
  • 그리고 GET 모드를 통해 검색한 결과, 변경된 값이 잘 반영된 것을 확인할 수 있다.

  • DELETE 모드를 통해 C3를 삭제해본다.

  • 실행 결과 잘 삭제되었다는 메시지가 출력된다.
Cloud Vendor Deleted Successfully

 

  • GET 모드에서 C3를 읽어들이려고 하니, 예외처리를 하지 않아 에러가 발생한다. 없는 값을 읽으려고 해서 발생하는 오류이다.

  • 이로써 모든 CRUD가 잘 동작하는 것을 확인하였다.
  • 실시간으로 powershell을 통해 db 상태를 확인하며 잘 동작하는 것도 확인하였다.

 

다음시간에는 예외처리를 위한 코드를 추가할 예정이다.

 

 

다음 내용↓↓↓

2023.11.22 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception

이전 내용↓↓↓ 2023.11.13 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (3) [REST API] Spring Boot로 REST API CRUD 간단 구현 (3) 이전 내용↓↓↓ 2023.11.11 - [Web Application/Backend] - [REST API] S

im-gonna.tistory.com

 

반응형

+ Recent posts