SpringBoot 원리 5)DI와 테스트
- 개발자 테스트 : 개발자가 자기가 만든 코드가 잘 돌아가는지 테스트
- 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
}
- 단위테스트(유닛 테스트) : 자바 코드 검증
- 수행속도 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 패턴]
=> HelloController를 건들이지 않고 HelloService에 적용되는 클래스를 바꿔주는게 가능
-
- 스프링 컨테이너가 뜰때 스프링이 assembler 역할을 하면서 클래스 레벨 의존관계 맞춰줌
-
Assembler한테 참조하는 클래스가 어떤건지 정보만 주면 됨!
-
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:
해결방법
-
설정 파일 만들어 주입 관계 직접 지정
-
@Primary : 후보가 두개면 어떤 것이 @Autowired인지 선택될까
=> 얘가 붙은 애가 먼저 주입되고, 그다음 차례에 HelloSevice 호출하는 부분에서 나머지 걔를 주입
@Service
@Primary
public class HelloDecorator implements HelloService{
// HelloService를 의존함과 동시에 다른 클래스(HelloService)를 의존
}
-
자바 코드
-
Proxy 패턴
: 실체 대신 그 앞에 대신할 수 있는걸 갖다 놓는다
=> HelloController는 원래 내가 사용한 로직있는 클래스로 보지만 실제론 HelloProxy가 대리해서 여러가지 부가적인 기능
( vs Decorator 패턴 : 기존 오브젝트에 동적으로 새로운 기능 객체를 추가해주는 것 )
ex) 실제 로직코드가 복잡해 on demand 로 만들어야하는 경우 (lazy loading)
ex) local 이 아니라 API 를 타고 멀리있는 서버의 객체를 호출하는 경우
=> 개발하는 쪽에선 Proxy를 실행시켜 local에 있는 객체 다루듯이 하고 실행때 원격 객체 호출은 Proxy 가 담당
댓글남기기