티스토리 뷰

Rest API Controller 생성 및 단위 테스트

 

[목표] ➤ 테스트 코드 작성 / 단위 테스트 실행

TDD ? Unit Test?

  • TDD : Test-Driven-Development
    • 테스트 코드 먼저 작성하면서 시작 (Red - Green - Refactor)
  • Unit Test : 기능단위의 테스트 코드 작성 (TDD의 첫번째 단계) 순수한 테스트 코드 작성 행위
    • 단위 테스트는 문제 발견이나, 추후 리팩토링 같은 수정사항 발생 시 기존 기능 올바르게 작동하는지 확인 도움
    • 중간중간 체크함으로써 불확실성 줄여준다. (+ 시스템에 대한 문서로 사용 가능)
    • 매번 일일히 tomcat 올려서 출력 체크하는 것도 일이니까... 결과만 도출되는 단위테스트 이용하면 빠름

프레임워크 : xUnit

- 테스트 코드 작성 돕는 프레임 워크. 개발 환경에 따라 x 달라짐 (여기선 JUnit)


Hello Controller 테스트 코드 작성

1. 패키지 생성 및 java Class 생성

  • java 디렉토리 하위에 생성
  • 패키지 명: com.jojoldu.admin (➝ 웹사이트 주소: admin.jojoldu.com - 주로 웹사이트 주소 역순으로 생성)
  • 생성된 패키지 하위에 Application 클래스 생성

  • Applicaiton Class : 앞으로 만들 프로젝트의 메인 클래스
  • @SpringBootApplication
    • 스프링 부트 자동설정, Bean 읽기/생성 모두 자동 설정
    • @SpringBootApplication이 있는 위치부터 설정 읽음!!! ➔ 항상 프로젝트 최상단에 위치하도록 주의
  • SpringApplicaiton.run
    • 내장 WAS(웹 애플리케이션 서버) 실행 (권장)
      • 외부에 별도로 두지 않고, 어플리케이션 실행 시 내부에서 실행
      • 항상 서버에 톰캣 설치할 필요 X, jar 파일로 실행하면 ok (톰캣 없이도 배포, 서비스 가능!)
      • 항상 같은 환경에서 스프링부트 배포 가능하게 함

2.  Web 패키지 생성 

  • 하위 패키지로 web package 생성
  • web package : Controller 관련 모든 Class 담는 패키지

3.  테스트 할 Controller 생성 [HelloController]

  • HelloController 생성 : web에 테스트용 컨트롤러 생성

  1. @RestController Annotation
    • JSON 반환 Controller로 만들어줌
  2. @GetMapping Annotation
    • 사용자가 /hello 입력 시 문자열 hello (탬플릿 명) 반환함 

4.  테스트 코드 생성 및 실행 (결과 확인)

이후 transform 선택

@Autowired
private MockMvc mvc; // 스프링 MVC 테스트 시작점 / Http method API 테스트

@Test
public void hello가_리턴된다() throws Exception {
    String hello = "hello";

    mvc.perform(get("/hello")) //"/hello" 주소로 HTTP GET 요청 (MockMvc method)
            .andExpect(status().isOk()) //HTTP Header의 Status 검증 (200 / 그 외)
            .andExpect(content().string(hello)); //응답 본문 내용 검증
            //체이닝--> 여러 검증 한번에 선언
}
  • @ExtendWith(SpringExtension.class)
    •  스프링부트 테스트 --- JUnit 사이 연결자 역할 (내장 실행자 외 다른 스프링 실행자인 SpringExtension 실행)
  • @WebMvcTest
    • Web(Spring MVC)에 집중 - @Controller, @ControllerAdvice... 등 사용 가능 (= 컨트롤러 클래스 주입 받기 가능)
    • 대신 Web 집중 == @Service, @Component, @Repsoitory 등은 사용 불가능
    • 통합 테스트를 하기 위해선 @SpringBootTest를 사용한다.
  • @Autowired
    • 스프링 컨테이너에서 관리하는 빈 주입받음 (DI) ; 필요한 의존 객체의 타입에 해당하는 빈을 찾아 주입
    • 주로 Constructor, setter, 필드 3가지 경우에 Autowired 어노테이션 사용함
    • 여기선 테스트 하기 위해 사용하는 MockMvc를 주입 받기위해 사용됨
  • @Test
    • Test 어노테이션을 추가해줌으로써, 하위 메소드를 test 메소드로 인식하고 테스트 함
    • 즉 테스트 만드는 모듈 역할. 테스트 결과로 pass/fail 도출함
MockMvc ?
⚬ 등장 배경 : 객체를 테스트 하기위해 세워놓은 허수아비 같은 느낌. 실제로 테스트하려면 메모리에 올려야 하는  데 전부 다 올리자니 부담이 크고... 내가 체크하고자 하는 기능만 가지도록 비슷하되 가벼운 객체를 만들어 모킹함.
⚬ MockMvc
  - 서블릿 컨테이너를 모킹한 객체. request를 발생시키는 도구 사용, 직접 호출 건너뛰기 가능 (컨트롤러 호출해줌)
    ⚬  서블릿 컨테이너 : 서블릿들의 생성, 실행, 파괴 담당 (Client의 Request 받고 Response 가능하게 웹 서버와
        소켓 만들어서 통신함 EX. 톰켓)
    ⚬  서블릿
        - 웹 어플리케이션에서 사용자의 요청 처리 → 결과 사용자에게 다시 전송하는 일종의 프로그래밍 기술
        - Servlet 클래스의 구현 규칙 따름 (ex. 서블릿 라이프 사이클 위해 필수 메소드 3가지 정의 필요)
          (필수 메소드 3가지 : (1) init, (2) service, (3) destroy ... 말 그대로 생성, 실행, 파괴)
        - 주 기능 : (1) HTTP Request(요청) 받아 처리 (2) Request에 대한 Response(응답)
           (톰켓 방식과 같이 사용자에게 request 들어오면 컨테이너에 객체 만들어 올리고 동적 페이지 생성 후 응답
            다시 내부 객체에 응답 보냄 →  http 응답 사용자에게 전송)
        - HTML 변경 시 재컴파일 필요함 (이래서 테스트 코드를 적극 활용하는 건가?)

 

 

#  테스트 결과가 맞는지 직접 실행해서 확인해보자

Application.main() 실행 결과

hello가 잘 return된다. 테스트가 맞다면 맞는걸로^^


롬복 설치 및 적용하기

# bulid.gradle의 dependencies 수정 (추가)

뭐뭔데

gradle 버전 5 이상부터는 의존성 추가 방식이 아래와 같이 수정됐다. 

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

  • lombok 플러그인은 이미 설치된 상태여서 별도의 설치는 생략했다.

lombok을 사용하기 위해선 enable annotation processing을 체크해줘야 함

✅ 이때 롬복은 프로젝트마다 설정 필요 ⇨ 새로운 프로젝트를 시작할 때마다, build.gradle 에 라이브러리 추가 + Enable annotation ~ 설정 체크는 계속 해줘야 한다. 
✍ 패키지 가져오기(import) 단축키 : Alt + Enter

# 롬복으로 리팩토링 하기

Dto Package

  • 모든 응답 Dto   Dto 패키지에 추가할 예정
  • web package 하위에 dto package 생성 → HelloResponseDto 생성 

@Getter

 - 하위에 선언된 모든 필드 → get 메소드 자동으로 생성해줌 (private이므로 getter/setter 설정 필수)

@RequiredArgsConstructor

  - 하위에 선언된 모든 final 필드가 포함된 생성자 생성 (final 없으면 XX)

 

Dto 테스트 해보기

  • 생성한 HelloResponseDto에 롬복 작동 확인용 테스트 코드 작성
  • main과 동일한 경로에 동일한 패키지   HelloResponseDtoTest 생성
✅ 테스트용 코드 작성법 : (1) given (2) when (3) then

⚬ given : 검증할 때 사용할 데이터 (비교 당할 데이터)
⚬ when : 검증할 사항 (제대로 작동하는지를 검증할 메서드는 어떤 것?)
⚬ then : 검증 부분 (어떤 방식으로 검증할 것인지?)
public class HelloResponseDtoTest {

    @Test //HelloResponseDto의 getter, constructor가 잘 생성되는지 확인 위해 annotation 추가
    public void 롬복_기능_테스트() {

        //(1)given - 검증할 때 사용할 데이터
        String name = "test";
        int amount = 1000;
        //final로 선언해준 필드 name, amount 를 테스트에 사용

        //(2)when - 검증할 사항 (뭘 검증할건지)
        HelloResponseDto dto = new HelloResponseDto(name, amount);
        //롬복 적용이 잘 됐는지 - Getter, Constructor이 잘 작동하는지 확인하기 위해 given data 넣어준 객체 생성 

        //(3)then - 어떤 방식으로 검증?
        assertThat(dto.getName()).isEqualTo(name); //getName으로 name 필드 꺼내와 맞는지 비교 (isEqualto 메서드 이용)
        assertThat(dto.getAmount()).isEqualTo(amount);


    }
}

- Assertion을 Static import 해줌 - Assertions 없이 바로 메서드 사용 가능하게 해줌

  (근데 교재를 보니 애초에 import 자체를 Assertions에서 assertThat만 해줬던거였다.... ㅎㅎ.. 아나)

- 특이한점은 Junit의 assertThat이 아닌 assertj를 사용한 것 (추가 라이브러리 필요 없는 등 장점이 더 많다고 함...)

  (자매품 : Junit의 assertEquals("Expected", "Actual") 로 비교도 가능)

 

⇨ isEqualTo의 인자와 일치하면 통과 ( assertThat("Actual").isEqualTo("Expected")이므로)


# assertj 

- 테스트 검증 라이브러리 

-  Assertion을 api로 제공함

Assertions 
- org.assertj.core의 api
- 빌드 툴에서 빌드시, 테스트 케이스를 통과하지 않으면 다음단계로 넘어가는 것이 방지됨
⚬ assertThat(argument) 
   - 검증하고자 하는 대상을 argument로 받음 → 주로 .isEqual과 엮어서 사용 (같으면 pass)
   - 메소드 체이닝이 지원되므로 해당 메소드 뒤 바로 .isEqual 이어서 사용 가능함
⚬ isEqualTo(argument)
   - 동등 비교 메소드
   - 선행 메소드 assertThat의 argument와 isEqualTo의 인자가 같으면 성공(통과)

롬복이 잘 적용되는 것을 확인 가능하다.

# HelloController에 ➝  ResponseDto 추가

  • @RequestParam(A) : A는 API를 호출하는 곳에서 넘겨준 값들
@GetMapping("/hello/dto")
    public HelloResponseDto helloDto(@RequestParam("name") String name, @RequestParam("amount") int amount) {
        //사용자가 api로 넘긴 name, amount이름으로 넘긴 파라미터를 캐치함
        return new HelloResponseDto(name, amount); //객체에 넣고 생성해 return
    }

# 추가된 API를 테스트하는 코드 HelloControllerTest에 추가

@Test
public void helloDto가_리턴된다() throws Exception {
    String name = "hello";
    int amount = 1000;

    mvc.perform(
            get("/hello/dto") //"/hello/dto" 주소로 HTTP GET 요청 (MockMvc method)
                            .param("name", name) //요청 파라미터 - name와 비교
                            .param("amount", String.valueOf(amount)))
            .andExpect(status().isOk())//HTTP Header의 Status 검증 (200 / 그 외)
            .andExpect(jsonPath("$.name", is(name)))//JSON 응답값 중 name 필드 검증
            .andExpect(jsonPath("$.amount", is(amount)));//JSON 응답값 중 amount 필드 검증

}

위 코드를 추가해준다. 

잘 작동된다!


 

# Week. 1 공부

🐢 스프링 프레임워크 등장 배경?

 - EJB : 애플리케이션 업무 로직 담당하는 자바 표준 기술 → 불편함 多 → 새로운 오픈소스 등장 : J2EE / ORM

   - J2EE (EJB 컨테이너 대체) : 그렇지만 얘도 컨테이너 안에서만 동작하는 객체구조...

   - ORM (EJB 빈 기술 대체, JPA 새 표준 정의)

  • 프레임워크 : 애플리케이션을 구성하는 근간이 되는 골격 코드 (아키텍처) → 구조 결정
  • 컨테이너 : 인스턴스 생명 주기 관리(생성/소멸 관리), 생성된 인스턴스들에게 추가적인 기능 제공
  •   - 스프링 컨테이너 : IoC(제어권 프레임워크에게), DI 원리 적용됨. 객체 생성, 소멸, 호출)
  • 빈 : 컨테이너에 의해 관리되는 인스턴스

🐢 스프링 vs. 그 외 백엔드 프레임 워크

- 스프링의 지향점이자 특이점 : 기술적 코드 / 비즈니스 코드 분리 + 우수한 객체지향 설계

 ⚬ 좋은 객체 지향

    - 객체 지향 특징 (추상화, 캡슐화, 상속, 다형성)을 보장하며 구현의 유연성을 높인다.

    - IoC, DI도 결과적으론 위와 같은 목적에 사용되는 것 (확장 가능한 설계, 선 역할 부여 후 객체 생성 → 이것들을 실현하기 위해 객체들 간 느슨한 결합이 요구되는데, 이때 DI가 사용됨)

    → SOLID(좋은 객체 지향 설계 5원칙)

        1. 단일 책임 원칙, 2. 개방-폐쇄 원칙(OCP), 3. 리스코프치환 원칙, 4. 인터페이스 분리 원칙, 5. DIP(DI 역전)

 

🐢 스프링과 스프링 부트의 차이?

  - Dependency 설정 난이도 : Spring < Spring boot 

    (의존관계 설정이 더 쉽고 버전 관리도 자동화. 코드 몇 줄짜리가 한 줄에 끝나는 경우도 있어 매우 간단함)

  - 비슷하게 Configuration의 설정 난이도 : Spring < Spring boot 

    (모든 어노테이션, 빈 등록 설정 << application.properties, application.yml에 설정)

  - boot에만 있는 AutoConfiguration ( @SpringBootApplication) : 외부 라이브러리, 내장 톰켓 서버 실행해줌

    (이를테면 어노테이션들을 스캔에 @Component, @Controller같은 객체들은 자동으로 Bean에 등록)

  - 배포의 용이함 : Spring (war 파일을 WAS에 담아 배포) < Spring boot (jar 파일을 넣은 뒤 java -jar로 실행)

    (앞서 말했듯 Boot 프레임워크는 톰캣과 같은 내장 WAS 가져서 jar로 배포해도 ㄱㅊ)

 

🐢 테스트 코드가 중요한 이유

   - 개발한 기능 테스트 : 자바의 main 메서드 or 웹 어플리케이션 컨트롤러 통해 해당 기능 실행하며 테스트

    → 실행 소요시간 큼 + 반복 실행 어려움 + 여러 테스트 한번에 실행 어려움

    ➔ JUnit / assertj로 테스트 코드 만들어서 테스트 코드 자체를 실행함으로써 해결 가능

   - 심지어 평소 과제할 때도 중간마다  system.out 메소드로 체크하는걸 생각해보면 당연히 중요한거 같기도...

   - 물론 추후 리팩토링, 유지보수 단계에서도 유용하게 사용될 듯 (부담이 적으니)

   + 테스트를 작성할 때는 서로 의존관계 없이 설계 돼야 함 (단위 테스트니까...)


생각보다 쓸게 많구먼.... 과정을 기록해 두면 좋을 것 같아서 그렇게 했는데 별로 효율적인 방법은 아닌 것 같다.

다른 기록방법을 모색해봐야 될 것 같음..ㅎㅎ......

최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
반응형