Spring Framework/토비의 스프링

Chapter 05. DI와 테스트, 디자인 패턴

계란💕 2023. 7. 4. 23:53

테스트 코드를 이용한 테스트

  • 서버를 띄우고 http 요청과 응답을 확인할 수 있다. 
  • 그런데 매번 직접 확인하는 것은 번거로운 일이다. ex) 헤더, 바디 등
  • 테스트 코드를 사용하면 효율적이다. 
  • Spring initializer를 통해 애플리케이션을 처음 세팅하면 "test" 폴더 안에  TestClass 가 생성된다. 
  • 애플리케이션이 기본적으로 스프링 부트를 기반으로 작동한다. 
  • API 요청 라이브러리 RestTemplate vs TestTemplate
    • RestTemplate: 정상이 아닌 경우, 예외를 던진다. 
    • TestRestTemplate: 응답 자체를 그대로 가져오기 때문에 에러가 난 경우 사용할 때 적합하다. status code, content-type 이 뭔지 파악할 수 있어 편리하다. 
  • Assertions.assertThat()을 이용해서 검증하면 편리하다. 
  • Response
    • getHeaders()를 이용하면 헤더를 담은 컬렉션을 리턴한다. 

 

 

  Ex) 

<hide/>
@Test
void helloApi() {
    TestRestTemplate rest = new TestRestTemplate();
    ResponseEntity<String> response = rest.getForEntity(
        "http://localhost:8080/hello?name={name}", String.class, "Spring");
    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
    assertThat(response.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)).startsWith(
        MediaType.TEXT_PLAIN_VALUE);
    assertThat(response.getBody()).isEqualTo("Hello! Spring");
}

 

 


DI와 단위 테스트

  • Service 클래스 안에 어떤 메서드가 있다면 이 로직을 검증하는 방법은 어렵지 않다. 
  • 직접 API 테스트를 하지 않기 때문에 속도가 빠르고 원하는 대상만 골라서 자바 코드를 직접 테스트하면 된다.
  • 서버를 띄우지 않고도 고립된 테스트가 가능하다는 게 장점이다. 
    • 고립된 테스트: HelloController, HelloService가 있는 경우에 대해 HelloController의 결함에 상관 없이  HelloService를 테스트하거나 HelloService  결함에 상관없이 HelloController를 테스트하는 걸 말한다. 
<hide/>
@Test
void sayHello() {
    SimpleHelloService helloService = new SimpleHelloService();
    String ret = helloService.sayHello("Test");
    assertThat(ret).isEqualTo("Hello! Test");
}

 

속도가 훨씬 줄었다.

 

 

  Ex) 고립된 테스트

  • 컨트롤러 테스트
    • 아래와 같이 만들면 컨트롤러에만 국한시켜서 테스트할 수 있다. 서비스 클래스 코드의 결함에는 관련이 없는 테스트이다. 
<hide/>
@Test
void hello() {
    HelloController helloController = new HelloController(name -> name);
    String ret = helloController.hello("test");
    assertThat(ret).isEqualTo("test");
}

 

  • 컨트롤러
    • null인 경우 예외처리된다. 
<hide/>
@GetMapping("/hello")
public String hello(String name) {
    return helloService.sayHello(Objects.requireNonNull(name));
}

 

  • assertThatThrownBy()를 이용하면 실패 케이스에 대해서 적절한 예외가 터지는지 확인할 수 있다. 
    • 컨트롤러 메서드의 반환형을 보면 위와 같이 null이면 에러 처리되고 있다. 
<hide/>
@Test
void failsHelloController() {
    HelloController helloController = new HelloController(name -> name);
    assertThatThrownBy(
        () -> {
            String ret = helloController.hello(null);
        }).isInstanceOf(NullPointerException.class);
}

 

  • api test - 500 error
<hide/>
@Test
void failHelloApi() {
    TestRestTemplate rest = new TestRestTemplate();
    ResponseEntity<String> response = rest.getForEntity(
        "http://localhost:8080/hello?name=", String.class);
    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
}

 


DI를 이용한 Decorator, Proxy 패턴

  • 데코레이터 패턴을 적용해서 핼로 데코레이터 클래스를 만들 계획이다. 
    • 컨트롤러 - 서비스 사이에 핼로 데코레이터 클래스를 위치시킨다. 
  • 현재 컨트롤러가 서비스 클래스에 의존하고 있다. 

 

  • 데코레이터 클래스를 만들면 다음과 같은 에러가 난다. 
    • 이 상태로 서버를 띄우면
    • Parameter 0 of constructor in tobyspring.helloboot.HelloController required a single bean, but 2 were found:
    • 와 같은 에러가 난다. 
    • HelloController  빈이 하나만 필요한데 두 개나 만들어졌기 때문이다. 
    • 만약 이 두 개의 빈을 생성해두고서 우선 순위로 쓰려는 빈을 등록하고 싶은 경우는 @Primary 를 사용하면 된다. 
    • 데코레이터 위에 @Primary를 붙이면 대코레이터 클래스를 우선적으로 갖다 쓴다. 

 

  • 테스트
<hide/>
@Test
void helloDecorator() {
    HelloDecorator decorator = new HelloDecorator(name -> name);
    String ret = decorator.sayHello("Test");
    Assertions.assertThat(ret).isEqualTo("*Test*");
}

 

  • 프록시패턴: 실제 빈을 최대한 지연시켜서 가져와야할 때 사용할 수 있다. 

 

 

출처 - 인프런 토비의 스프링

https://www.inflearn.com/course/%ED%86%A0%EB%B9%84-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%9D%B4%ED%95%B4%EC%99%80%EC%9B%90%EB%A6%AC/dashboard