2 분 소요

  • 개발자 테스트 : 개발자가 자기가 만든 코드가 잘 돌아가는지 테스트

image-20230810194105952

  1. API 테스트 : 어플리케이션이 돌아가는지 (요청과 응답을 정상적으로 주고받는지)
  • 수행속도 down (요청과 응답을 다 parsing 해야해서)

  • 서버 미리 시작해야함

    ㄴ 테스트 하고자 하는 어플리케이션 실행 후 검증 실행해야 함 안그럼 예외 발생( I/O error on GET request for “http://localhost:8081/hello”: Connection refused)

package tobyString.helloboot;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

public class HelloAPITest {

    @Test
    void helloApi(){
        TestRestTemplate rest = new TestRestTemplate();
        // RestTemplate : API 요청을 호출해 응답을 가져와 수행할 수 있음 (요청과 응답 객체를 갖고 있는 클래스)
        // => 이걸 Test 목적으로 응답값 그대로를 가져오는 클래스 : TestRestTemplate (테스트 용 -> 요청과 응답에 대한 정보가 더 자세히 나타나있음)

        // ResponseEntity : 웹 응답의 모든 요소를 담고 있는 객체 => String : Body 부분이 String임(->Json, Dto..)
        // => 따로 cast 안해줘도 String 으로 돌려줌
        ResponseEntity<String> res
                = rest.getForEntity("http://localhost:8081/hello?name={name}", String.class, "Spring") ;



        // 응답 검증 (상태코드, 컨텐츠 타입,응답 바디)
        // => 테스트 하고자 하는 어플리케이션 실행 후 검증 실행해야 함 안그럼 예외 발생 ( I/O error on GET request for "http://localhost:8081/hello": Connection refused)
        Assertions.assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK) ;

      //  Assertions.assertThat(res.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)).isEqualTo(MediaType.TEXT_PLAIN_VALUE) ;
        Assertions.assertThat(res.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)).startsWith(MediaType.TEXT_PLAIN_VALUE) ;

        Assertions.assertThat(res.getBody()).isEqualTo("HelloSpring");
    } // helloApi

    @Test
    void failsHelloApi(){
        TestRestTemplate rest = new TestRestTemplate();

        ResponseEntity<String> res
                = rest.getForEntity("http://localhost:8081/hello?name=",String.class) ;

        Assertions.assertThat(res.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) ; // 500

    } // failsHelloApi

}

  1. 단위테스트(유닛 테스트) : 자바 코드 검증
  • 수행속도 up (자바 실행)
  • 서버를 동작하지 않고 (배포 x) 컨테이너 없이 (환경에 의존하지 않고) 간결하게 하나의 대상 (주로 하나의 클래스 단위)를 테스트하도록 만드는 코드 테스트
  • 예외 상황 테스트 코드
package tobyString.helloboot;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

public class HelloControllerTest {

    @Test
    void helloController(){
        // 생성자에 객체 어떻게 주입?
        HelloController helloController = new HelloController(name -> name);

        String ret = helloController.hello("Test") ;
        Assertions.assertThat(ret).isEqualTo("Test") ;
    }

    // 들어오는 요청 (API 동작) 이 제대로 된다는 보장이 없음 ex) Parameter 없어서 exeption
    // => 각 실패에 대한 Test 도 마련해놓아야한다!
    @Test
    void failsHelloController(){ // Exeption이 발생하면 test 성공
        HelloController helloController = new HelloController(name -> name);
        // 이것도 DI ! (스프링 대신 테스트 코드가 assembler 역할!)

        Assertions.assertThatThrownBy(()->{
           helloController.hello(null);
        }).isInstanceOf(IllegalArgumentException.class) ;

        Assertions.assertThatThrownBy(()->{
            helloController.hello("");
        }).isInstanceOf(IllegalArgumentException.class) ;

    } // assertThatThrownBy
}

[Decorator & Proxy 패턴]

image-20230811103136267

image-20230811103107995

=> HelloController를 건들이지 않고 HelloService에 적용되는 클래스를 바꿔주는게 가능

  • 스프링 컨테이너가 뜰때 스프링이 assembler 역할을 하면서 클래스 레벨 의존관계 맞춰줌

    Assembler한테 참조하는 클래스가 어떤건지 정보만 주면 됨!

image-20230811103535263

  • SimpleHelloService의 핵심 기능을 수정하지 않고(기존 object를 수정하지 않고) 추가하고자 하는 기능을 클래스로 동적으로 삽입

  • HelloService를 구현하는 Service 클래스를 두개 만들어줘도 오류나지 않음. 어째서?

public class HelloDecorator implements HelloService{
    
}

=> 빈 객체로 등록하지 않았기 때문!

HelloDecorator 가 빈 객체로 등록되지 않았으니 자연히 HelloService를 구현한 애는 SimpleHelloService 밖에 없음 => 단일 객체(single bean)니 자동으로 주입됨 (@Autowired)

​ ↓

@Service
public class HelloDecorator implements HelloService{
    
}

@Service (@Component 계승) 어노테이션을 붙여줘 빈 객체로 등록 !

=> error 정상적으로 발생 !

Parameter 0 of constructor in tobyString.helloboot.HelloController required a single bean, but 2 were found:

해결방법

  1. 설정 파일 만들어 주입 관계 직접 지정

  2. @Primary : 후보가 두개면 어떤 것이 @Autowired인지 선택될까

    => 얘가 붙은 애가 먼저 주입되고, 그다음 차례에 HelloSevice 호출하는 부분에서 나머지 걔를 주입

@Service
@Primary
public class HelloDecorator implements HelloService{
  // HelloService를 의존함과 동시에 다른 클래스(HelloService)를 의존
}
  1. 자바 코드

  2. Proxy 패턴

: 실체 대신 그 앞에 대신할 수 있는걸 갖다 놓는다

image-20230811110900669

=> HelloController는 원래 내가 사용한 로직있는 클래스로 보지만 실제론 HelloProxy가 대리해서 여러가지 부가적인 기능

( vs Decorator 패턴 : 기존 오브젝트에 동적으로 새로운 기능 객체를 추가해주는 것 )

ex) 실제 로직코드가 복잡해 on demand 로 만들어야하는 경우 (lazy loading)

ex) local 이 아니라 API 를 타고 멀리있는 서버의 객체를 호출하는 경우

=> 개발하는 쪽에선 Proxy를 실행시켜 local에 있는 객체 다루듯이 하고 실행때 원격 객체 호출은 Proxy 가 담당

댓글남기기