자바/스프링 부트

섹션5. DI와 테스트, 디자인 패턴

UroJem 2023. 2. 9. 21:39

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

웹개발을 할 때 먼저 요구사항이 정리되고 기획서가 나오면 기획서에 따른 디자인과 화면 퍼블리싱이 들어가게 된다.

그 때 백단 개발자들은 DB정의를 하면서 서비스 로직을 구현하고 화면에서 받아오는 값을 예상하여 로직을 실행하고 받아오는 값이 정상적으로 넘어오는지 단위 테스트를 진행한다.

 

이 때 주로 사용하기 위한 라이브러리가 JUnit5와 AssertJ이다.

 

https://donghyeon.dev/junit/2021/04/11/JUnit5-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C/

 

JUnit5 완벽 가이드

시작하기전

donghyeon.dev

https://insight-bgh.tistory.com/507

 

Junit5 기본 사용법 정리

1. Junit이란?? 자바 개발자가 가장 많이 사용하는 테스팅 기반 프레임워크다. JUnit5은 자바8 이상부터 사용이 가능하다. Junit4가 단일 jar이었던거에 비해서, JUnit5는 크게 3가지 모듈로 구성되어있다

insight-bgh.tistory.com

https://wecandev.tistory.com/168

 

[JUnit5] 기본 사용법 정리

1. 시작하며 JUnit5는 가장 인기 있는 단위 테스트 프레임 워크이다. JUnit5 의 기본 기능에 대해 알아보자 2. JUnit5 란? JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage JUnit Platform : 테스트를 실행해주는

wecandev.tistory.com

https://www.daleseo.com/assertj/

 

AssertJ 소개

Engineering Blog by Dale Seo

www.daleseo.com

 

@Test 애노테이션은 메서드가 단위 테스트로 사용하는 메서드임을 명시한다.

JUnit은 테스트 패키지 하위 클래스에서 @Test 애노테이션이 붙은 메서드를 단위테스트로 인식하여 실행시킨다.

 

package tobyspring.helloboot;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
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;

@DisplayName("API 테스트")
public class HelloApiTest {

	@DisplayName("메롱")
	@Test
	void helloApi(){

		// REST 방식으로 개발한 API의 테스트를 최적화하기 위해 만들어진 클래스
		// Http 요청 후 데이터를 응답받을 수 있는 템플릿 객체로 ResponseEntity와 함께 자주 사용한다.
		TestRestTemplate rest = new TestRestTemplate();

		// 스프링에서 제공하는 클래스 중 HttpEntitiy 클래스가 존재하는데
		// Http req/res 이루어질 때 Http 헤더와 바디를 포함하는 클래스
		// ResponseEntity는 HttpEntity를 상속 받는다.
		// HttpRequest에 대한 응답 데이터를 가지고 HttpStatus, Header, Body를 포함한다.
		final ResponseEntity<String> res =
			rest.getForEntity("http://localhost:8080/hello?name={name}", String.class, "Spring");

		// 응답 검증 3가지 확인
		// status code 200
		// 응답 HttpStatus code가 enum 이니 단순 int가 아닌 해당 객체와 비교해야 함
		Assertions.assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
		// header(cotent-type) text/plain
		// header 전체를 담은 컬렉션 리턴되어 첫번째 헤더 컨텐트 타입의 text/plain 인지 확인
		Assertions.assertThat(res.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)).startsWith(MediaType.TEXT_PLAIN_VALUE);
		// body hello Spring
		Assertions.assertThat(res.getBody()).isEqualTo("Hello Spring");

	}

	@DisplayName("API 실패했을 경우 테스트")
	@Test
	void failsHelloApi(){

		TestRestTemplate rest = new TestRestTemplate();

		// 파라미터 값을 넘기지 않았을 때 IllegalArgumentException 난다
		final ResponseEntity<String> res = rest.getForEntity("http://localhost:8080/hello?name=", String.class);

		// status code 500이 일어나면 성공
		Assertions.assertThat(res.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);

	}

}

https://easybrother0103.tistory.com/64

 

[Spring Boot] ResponseEntity와 TestRestTemplate

REST API에 대한 기본적인 이해를 하고 있다고 가정한다. 1. ResponseEntity 스프링에서 제공하는 클래스 중에서 HttpEntity라는 클래스가 존재하는데 이는 Http 리퀘스트/리스폰스가 이루어질 때 Http 헤더

easybrother0103.tistory.com

잘못된 파라미터 값이 들어왔을 때 에러코드가 들어오는지 테스트할 수 있다.

 

DI와 단위 테스트

API 요청 테스트가 아닌 자바 로직을 테스트하는 경우 서버를 키지 않고 빠르게 테스트 할 수 있다.

Http 요청 / 응답이 정상적으로 작동되는지 확인하고 컨트롤러 로직과 서비스 로직도 정상 작동되는지 확인했지만 네트워크를 통해 서버로 Http 요청, 응답 받고 값을 파싱한 후 바인딩하는 작업을 거치니 시간이 걸리는데 이보다 간편하게 서버로 Http 요청받고 보내는 과정을 생략하면 자바코드로 이루어진 로직을 확인만 하면 된다.

테스트 속도가 빨라지고 원하는 대상만 테스트하는 것이 가능하다.

 

package tobyspring.helloboot;

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

@DisplayName("HelloService 단위 테스트")
public class HelloServiceTest {

	@Test
	void simpleHelloService(){
		SimpleHelloService helloService = new SimpleHelloService();

		// 파라미터로 보낸 값이 정상적으로 리턴되는지 확인한다.
		String ret = helloService.sayHello("Test");

		Assertions.assertThat(ret).isEqualTo("Hello Test");
	}

}

 

수행 속도가 훨씬 빨라졌다.

 

package tobyspring.helloboot;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

// API 요청에 대한 응답을 화면 통채로 보내는 것이 아닌
// 데이터를 보내는 RestController
@RestController
public class HelloController {

	private final HelloService helloService;

	public HelloController(HelloService helloService) {

		this.helloService = helloService;

	}

	@GetMapping("/hello")
	public String hello(String name){

		// null 객체 뿐 아니라 null String이 들어왔을 때도 처리
		if(name == null || name.trim().length() == 0) throw new IllegalArgumentException();

		return helloService.sayHello(name);

	}

}
package tobyspring.helloboot;

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

@DisplayName("HelloController 단위 테스트")
public class HelloControllerTest {

	@Test
	void helloController(){
		
		// Anonymous 클래스 만들어서 바로 서비스 사용
		// 테스트에서 사용하는 수동 DI
		HelloController helloController = new HelloController(name -> name);

		final String ret = helloController.hello("Test");

		// return 받은 값이 "Test" 인지 확인
		Assertions.assertThat(ret).isEqualTo("Test");

	}

	@DisplayName("HelloController에 null이 들어갈 경우")
	@Test 
	void failsHelloController(){

		HelloController helloController = new HelloController(name -> name);

		// 예외 검증 기능 사용 파라미터 값이 null이 들어가면 IllegalArgumentException 나면 성공
		Assertions.assertThatThrownBy(() -> {
			helloController.hello(null);
		}).isInstanceOf(IllegalArgumentException.class);

		// null String이 들어갈 경우 IllegalArgumentException 나면 성공
		Assertions.assertThatThrownBy(() -> {
			helloController.hello("");
		}).isInstanceOf(IllegalArgumentException.class);

	}
}

 

 

 

 

DI를 이용한 Decorator, Proxy 패턴

DI(Dependency Injection) : 의존성 주입 객체가 서로 의존하는 관계가 되도록 의존성 주입하는 것. 객체지향에서 의존성이란 하나의 객체가 어떠한 다른 객체를 사용하고 있음을 의미. IOC에서 DI는 각 클래스 사이에 필요로하는 의존 관계를 Bean 설정 정보를 바탕으로 스프링 컨테이너가 자동 연결을해주는 것이다.

 

어떠한 객체를 사용하기 위해 new 키워드로 생성한 후 참조값을 변수에 담는 과정 또한 의존성 주입이다.

이 때 사용하려했던 객체를 변경해야하는 일이 있으면 소스코드를 수정해야하지만 이런 밀접한 관계를 느슨하게 만들어 주기 위해 KS 기술서같은 인터페이스를 정의하고 해당 인터페이스로 참조하고 있으면 인터페이스를 구현한 하위 객체들 중 어떤걸 받아서 쓰던 코드 수정 없이 넘겨줄 때만 맞는 객체만 넘겨주면 잘 동작한다.

스프링 컨테이너 에서는 인터페이스로 객체를 생성하는 과정이 있으면 컨테이너가 시작되면서 구현된 객체 중 적당한 Bean을 등록 생성하여 의존관계가 만들어진다.

 

 

Decorator Pattern

어떠한 로직을 수행할 때 메인 로직이 있고 메인 로직 수행 전이나 후에 추가적으로 수행해야 할 부가적인 일을 수행할 때

필요한 부분을 별도의 객체에 만들어서 동적으로 추가하는 패턴을 말한다.

데코레이터라는 말대로 장식해주는 일을 부가적인 로직을 추가할 수 있도록 설계한 방식이다.

은행업무에 관한 로직이라면 입금, 출금, 조회가 메인 로직이고 메인 로직 실행 전 유저를 인증하는 과정을 부가적인 일이라고 볼 수 있다.

 

https://coding-factory.tistory.com/713

 

[Design Pattern] 데코레이터 패턴(Decorator pattern)에 대하여

데코레이터(Decorator pattern) 패턴이란? 데코레이터 패턴(Decorator Pattenr)은 주어진 상황 및 용도에 따라 어떤 객체에 책임(기능)을 동적으로 추가하는 패턴을 말합니다. 데코레이터라는 말 그대로 장

coding-factory.tistory.com

 

컨트롤러가 서비스를 사용할 때 전 후 처리가 필요하다면 컨트롤러가 데코레이터 객체를 사용하고 데코레이터가 서비스를 사용하는 형태가 되는데 스프링 컨테이너 에서는 사이에 의존성 주입을 해준다.

 

package tobyspring.helloboot;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

@Service
// 같은 인터페이스를 구현한 객체가 2개 일 때 먼저 주입받을 수 있다.
@Primary
public class HelloDecorator implements HelloService{

	private final HelloService helloService;

	// 나를 제외한 HelloService인 SimpleHelloService를 불러온다
	public HelloDecorator(HelloService helloService){
		this.helloService = helloService;
	}
	@Override
	public String sayHello(String name) {
		return "*" + helloService.sayHello(name) + "*";
	}
}

 

Proxy Pattern

프록시는 대리인이란 뜻으로 무엇인가 대신 처리하는 의미. 실제 메인 로직이 구현된 객체의 부가적인 일을 처리하는 역할을 Proxy가 하게되는건데 메인로직이 회장의 역할 Proxy가 비서의 역할이라면 회장의 스케쥴상황을 회장에게 직접 물어보는게 아닌 비서에게 물어보고 회장이 직접 결제를 처리해야 하는 일이라면 비서를 거쳐 회장의 결제를 받아 업무를 진행하는? 결제를 올리기 직전에만 회장이 회사에 있으면 된다.

이렇게 메인 로직을 수행할 때 객체를 직접 참조하는게 아니라 객체 대행을 통해 대상객체에 접근하는 방식을 사용하면 해당 객체가 메모리에 존재하지 않아도 기본적인 정보를 참조하거나 설정할 수 있고 필요한 시점까지 객체의 생성을 미룰 수 있다.

메인로직을 거쳐야하는 객체가 메모리 리소스도 많이 잡아먹고 생성하는데 시간이 많이 드는 객체라면 스프링 컨테이너 올릴 때 만들어지는 것보다 실제로 사용할 때 생성되게 하는 것(Lazy Loading)이 효율적이다.

https://coding-factory.tistory.com/711

 

[Design Pattern] 프록시 패턴(Proxy Pattern)에 대하여

프록시 패턴이란? 프록시는 대리인이라는 뜻으로, 무엇인가를 대신 처리하는 의미입니다. 일종의 비서라고 생각하시면 됩니다. 사장님한테 사소한 질문을 하기보다는 비서한테 먼저 물어보는

coding-factory.tistory.com

 

 

 

 

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