Spring Test Framework (항해일지 20일차)

김형준·2022년 5월 29일
0

TIL&WIL

목록 보기
20/45
post-thumbnail

1. 학습일지


1) 테스트 방법론

  • 버그란?

    • 소프트웨어가 예상하지 못한 결과를 내는 것으로
    • '소스 코드'나 '설계과정에서의 오류' 때문에 발생함
  • 개발 코드 배포 전, 버그를 찾아내는 방법

  • 블랙박스 테스팅:

    • 소프트웨어 내부 구조나 동작원리를 모르는 블랙박스와 같은 상태에서, 즉 웹 서비스의 사용자 입장에서 동작을 검사하는 방법입니다!
    • 누구나 테스트 가능하다
    • 기능이 증가될 수록 테스트의 범위가 증가
    • 테스트 하는 사람에 따라 테스트 퀄러티가 다를 수 있음 → QA 직군이 있는 이유
  • 개발자 테스트:

    • 1) UI 테스트는 spring 기동하여 구현된 웹에서 직접하는 것: 클라이언트 코드가 다 작성 되어야만 검증 가능
    • 2) API 호출 앱(ARC, Postman 등): 여러 API를 한번에 검증하긴 어려움
    • 3) ✔스프링 테스트 프레임워크:
      • 개발자가 직접 "본인이 작성한 코드"를 검증하기 위해 "테스트 코드"를 작성 "개발자 본인이 작성한 코드는 본인이 가장 잘 안다."
      • 빠르고 정확한 테스트 가능 (예상 동작 VS 실제 동작)
      • 테스트 자동화 가능 (배포 절차 시 테스트 코드가 수행되어 동작 검증)
      • 리팩토링 후 기존 동작에 대한 보증수표!!!
      • 개발 시간이 오래 걸림 / 테스트 코드를 유지보수하는 비용 발생
        -> 스프링에서는 테스트 코드 작성을 잘 할 수 있는 환경을 제공해준다.

2) JUnit을 이용한 단위 테스트

  • 단위테스트란? 프로그램을 작은 단위로 쪼개서 각 단위가 정확하게 동작하는지 검사하고 이를 통해 문제 발생 시 정확하게 어느 부분이 잘못되었는지를 재빨리 확인할 수 있게 해준다.
  1. Development: 개발
  2. Unit Tests (단위 테스트): 개발자 테스트
  3. QA Testing:
    • 블랙박스 테스팅
    • 주로 QA 팀이 Production 환경과 유사한 환경(Stage)에서 테스팅
  4. Production: 실 서비스 운영 환경
    -> 버그 발견 시간이 늦어짐에 따라 비용이 기하급수적으로 커진다!!
  • JUnit이란? 자바 프로그래밍 언어용 단위 테스트 프레임워크
    • build.gradle에 JUnit 사용을 위한 환경설정이 이미 되어있다.
    • (디폴트로 제공되는 것만 봐도 TDD의 효용성은 이미 입증된 것 같다.)
dependencies {
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}
  • Edge case 고려하기
    • Edge case는 알고리즘이 처리하는 데이터의 값이 알고리즘의 특성에 따른 일정한 범위를 넘을 경우에 발생하는 문제를 가리킨다.
    • 따라서 테스트 시 엣지 케이스를 최대한 많이 넣어보는 것을 추천한다.

  • TDD(Test-Driven Development)
    • AS-IS) 설계 → 개발 → 테스트 (→ 설계 수정)
    • TO-BE) 설계 → 테스트 (→설계 수정) → 개발
    • 테스트를 먼저 만들고 테스트를 통과하기 위한 것을 짜는 것
    • 즉, 만드는 과정에서 우선 테스트를 작성하고 그걸 통과하는 코드를 만들고를 반복하면서 제대로 동작하는지에 대한 피드백을 적극적으로 받는 것이다. (🔗출처 -> 좋은 글인 것 같다. 나중에 또 읽어보기❗)

3) Mock Object 활용하기

  • Mock object (가짜 객체)? 이상적으로, 각 테스트 케이스는 서로 분리되어야 한다. 이를 위해 가짜 객체(Mock object)를 생성하는 것도 좋은 방법이다.
  • 예를들어 Service를 유닛테스트 하고자 할 경우, 서비스에 DI 되어있는 녀석들을 Mock Object로 만들어줌으로써 온전히 Service만 테스트할 수 있는것이다.
  • 단, MockRepository와 같이 테스트할 클래스 외 나머지 클래스는 스프링을 기동 안한다는 가정하에 직접 구현해줘야 한다.

4) Mockito mock을 사용한 단위 테스트

  • 위에서 언급한 대로 Mock Object는 실제로 구현해줘야 한다.
  • 💥Mockito는 이러한 비용을 줄이고자 Mock 객체를 쉽게 만들 수 있는 방법을 제공하는 것이다.
  • 사용 방법은 아래와 같다.
    • 테스트할 클래스의 테스트 클래스를 생성한다.
    • 클래스 선언부 위에 @ExtendWith(MockitoExtension.class)를 붙여준다.
    • 테스트 클래스 내부에서 해당 클래스 외 나머지 클래스에 @Mock을 붙여준다.
    • @Mock을 붙인 곳에서 나올 수 있는 결과를 명시해줘야 한다. 이를 사용 케이스를 추가한다고 한다.
ProductService productService = new ProductService(productRepository);
when(productRepository.findById(productId))
                .thenReturn(Optional.of(product));

5) 테스트 계층도

  • 단위 테스트란?
    • 하나의 모듈이나 클래스에 대해 세밀한 부분까지 테스트 가능
    • 모듈 간에 상호 작용 검증 못함
  • 통합 테스트란?
    • 두 개 이상의 모듈이 연결된 상태를 테스트
    • 모듈 간의 연결에서 발생하는 에러 검증 가능
  • E2E 테스트 (End to End Test)
    • 실제 사용자의 실행 환경과 거의 동일한 환경에서 테스트 진행 (=블랙박스 테스팅)

6) 스프링 부트를 이용한 통합 테스트

  • 원래 테스트 클래스에서는 Spring이 기동을 하지 않아 Bean을 찾을 수 없어 null이 들어간다.
  • 하지만 @SpringBootTest를 붙여주면 테스트 수행 시 스프링이 동작한다!!
  • 또한 @Order(1), @Order(2), ... 를 통해 테스트의 순서를 정할 수 있다.
  • @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 랜덤포트로 지정해준다
  • Bean에 등록된 Service, Repository를 사용할 수 있다.

7) 스프링 MVC 테스트 (Controller 테스트)

  • build.gradle에 testImplementation 'org.springframework.security:spring-security-test'를 추가하면,
  • SecurityMockMvcConfigurers를 임포트할 수 있다.
  • @WebMvcTest는 Controller를 테스트하는 것
  • 테스트 코드는 아래와 같은 방식으로 작성된다.
    @Test
    @DisplayName("로그인 view")
    void test1() throws Exception {
        // when - then
        mvc.perform(get("/user/login"))
                .andExpect(status().isOk()) // get방식의 응답은 200 status로
                .andExpect(view().name("login"))
                .andDo(print());
    }
    @Test
    @DisplayName("회원 가입 요청 처리")
    void test2() throws Exception {
        // given
        MultiValueMap<String, String> signupRequestForm = new LinkedMultiValueMap<>();
        signupRequestForm.add("username", "제이홉");
        signupRequestForm.add("password", "hope!@#");
        signupRequestForm.add("email", "hope@sparta.com");
        signupRequestForm.add("admin", "false");

        // when - then
        mvc.perform(post("/user/signup")
                        .params(signupRequestForm)
                )
                .andExpect(status().is3xxRedirection())
                .andExpect(view().name("redirect:/user/login"))
                .andDo(print());
    }
  • 시큐리티 상황을 전제하기에 권한이 필요한 요청에서는 MockUser도 따로 셋업해줘야 한다.
    private void mockUserSetup() {
        // Mock 테스트 유져 생성
        String username = "제이홉";
        String password = "hope!@#";
        String email = "hope@sparta.com";
        UserRoleEnum role = UserRoleEnum.USER;
        Users testUser = new Users(username, password, email, role);
        UserDetailsImpl testUserDetails = new UserDetailsImpl(testUser);
        mockPrincipal = new UsernamePasswordAuthenticationToken(testUserDetails, "", testUserDetails.getAuthorities());
    }

2. 코멘트

  • 지금 까지 스프링으로 개발해보며, 의미있는 테스트 코드를 돌려본적은 없는 것 같다.
  • 오늘 배운 개념으로 나중에 복잡한 기능을 구현할 때, TDD에 의거하여 선 테스트 후 개발 과정으로 시도해봐야겠다.
  • 이로써 스프링 심화강의 3주차까지 완료!
profile
BackEnd Developer

0개의 댓글