test (2)

 

 

 

 

 

 

Hamcrest

 

 

 

➤ Hamcrest 란?

 

 

▶️ Hamcrest란?

 

🍉 Hamcrest 는 JUnit 기반의 단위 테스트에서 사용할 수 있는 Assertion Framework 이다.

🍉 JUnit에서 Assertion을 위한 다양한 메서드를 지원한다.

 

 

▶️ Hamcrest를 사용하는 이유

 

🍉 Assertion을 위한 매쳐(Matcher)가 자연스러운 문장으로 이어져서 가독성이 향상된다.

🍉 테스트 실패 메세지를 이해하기 쉽다.

🍉 다양한 Matcher를 제공한다.

 

 

 

➤ JUnit Assertion ➡️ Hamcrest Assertion 적용하기

 

🍇 JUnit Assertion 사용시

public class HelloJUnitTest {
    @DisplayName("Hello JUnit Test")
    @Test
    public void assertionTest() {
        String expected = "Hello, World!";
        String actual = "Hello, JUnit";

        assertEquals(expected, actual);
    }
}

콘솔 출력 결과

expected: <Hello, World!> but was: <Hello, JUnit>
Expected :Hello, World!
Actual   :Hello, JUnit

 

 

🍇 Hamcrest의 Matcher 사용시

public class HelloHamcrestTest {
    @DisplayName("Hello Junit Test using hamcrest")
    @Test
    public void assertionTest1() {
        String expected = "Hello, World!";
        String actual = "Hello, Hamcrest";

        assertThat(actual, is(equalTo(expected)));
    }
}

콘솔 출력 결과

Expected: is "Hello, World!"
     but: was "Hello, Hamcrest"
Expected :Hello, World!
Actual   :Hello, Hamcrest

 

 

▶️ assertThat(actual, is(equalTo(expected)));

 

  • JUnit의 assertEquals(expected, actual) 와 비슷하지만 더 쉽다.

 

 

 

▶️ JUnit의 Assertion vs Hamcrest Assertion 테스트 결과

 

Hamcrest의 Matcher를 사용하면 사람이 읽기 편한 자연스러운 Assertion 문장을 구성할 수 있다. ➡️ 가독성이 많이 높아진다.

테스팅 실패 메시지를 이해하기 더 쉽다.

 

 

더 많은 Hamcrest Matcher

Custom Matcher

 

 

 

 

슬라이스 테스트 (Slice Test)

 

 

➤ 슬라이스 테스트란?

 

각 계층에 구현한 기능들이 잘 동작하는지 특정 계층만 잘라서(Slice) 테스트하는 것

 

 

※ 스모크 테스트(Smoke Test)

소프트웨어의 본격적인 테스트에 앞서, 구축된 테스트 환경에서 테스트가 가능한지를 판단하기 위해 주요 모듈을 간단하게 테스트하는 것이다. 세세한 테스트를 하는 것이 아니라 테스팅 전에 큰 결함은 없는지 확인하는 과정이라고 볼 수 있다.

 

 

➤ API 계층 테스트

 

API 계층 테스트 대상은 대부분 클라이언트의 요청을 받아들이는 핸들러인 Controller 이다.

 

 

▶️ @SpringBootTest

 

Spring Boot 기반의 애플리케이션을 테스트하기 위한 Application Context를 생성한다.

 

 

▶️ @AutoConfigureMockMvc

 

Controller 테스트를 위한 애플리케이션의 자동 구성 작업을 해준다.

MocMvc 기능을 사용하기 위해서는 이 애너테이션을 추가해야 한다.

 

 

▶️ MockMvc

 

Controller 테스트 클래스에 DI 로 주입받은 MockMvc 는 Tomcat 같은 서버를 실행하지 않고 Spring 기반 애플리케이션의 Controller를 테스트할 수 있는 완벽한 환경을 지원해준다.

➡️ Spring MVC 테스트 프레임워크

MockMvc 객체를 통해 Controller를 호출해서 손쉽게 테스트를 진행할 수 있다.

 

 

 

▶️ Given

 

🍊 request body 에 포함시키는 요청 데이터와 동일한 역할을 하게끔 작성한다.

🍊 dto의 테스트하고자 하는 객체를 생성한다.

🍊 Gson(JSON 변환 라이브러리) 을 이용하여 dto 객체를 JSON 포맷으로 변환해준다.

 

 

▶️ When

 

🍊 HTTP request 에 대한 정보를 MockMvcRequestBuilders 클래스를 이용해서 빌더 패턴으로 채워준다.

🍊 MockMvc 객체의 perform() 메서드 호출

🍊 post() 메서드 : HTTP POST 메서드와 request URL을 설정한다. (다른 http 메서드도 가능)

🍊 accept() 메서드 : 클라이언트 쪽에서 리턴 받을 응답 데이터 타입으로 JSON 타입 설정

🍊 contentType() 메서드 : 서버 쪽에서 처리가능한 Content Type으로 JSON 타입 설정

🍊 content() 메서드 : request body 데이터를 설정한다. (Gson을 이용해 변환된 Json 문자열)

🍊 perform() 이 최종적으로 ResultActions 타입을 리턴한다.

 

⚠️ post() 메서드 사용하기 위해 import 해올 때 잘못된 경로의 post를 import하지 않도록 주의한다.

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

 

 

▶️ Then

 

🍊 ResultActions 객체를 이용해서 전송한 request 에 대한 검증을 수행한다.

🍊 andExpect() 메서드 : 파라미터로 입력한 매처(Matcher)로 예상되는 기대 결과를 검증한다.

🍊 status().isCreated() : response status 가 201(Created)가 맞는지 검증한다. (is뒤에 원하는 response status를 넣으면 된다.)

🍊 andReturn() : 디버깅 용도로 response로 전달되는 응답 데이터를 출력할 때 사용한다.

🍊 jsonPath() : response body의 각 property 중에서 응답으로 전달 받는 특정 값이 response body로 전송한 값과 동일한지 검증할 수 있다. jsonPath()를 사용하면 JSON 형식의 개별 property에 손쉽게 접근할 수 있다.

 

심화학습) jsonPath()

 

 

Member Cotroller의 getMember 테스트 예시 코드

@Test
void getMemberTest() throws Exception {
        // given
        long memberId = member1.getMemberId();
        Member member = repository.findById(memberId).get();
        String content = gson.toJson(String.valueOf(memberId));

        // when
        ResultActions actions =
                mockMvc.perform(
                        get("/v11/members/" + memberId)
                                .accept(MediaType.APPLICATION_JSON)
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(content)
                );

        // then
        MvcResult result = actions
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data.email").value(member.getEmail()))
                .andExpect(jsonPath("$.data.name").value(member.getName()))
                .andExpect(jsonPath("$.data.phone").value(member.getPhone()))
                .andReturn();
}

 

 

 

✔️ response body 응답 데이터에 한글이 깨질 경우

 

resources 패키지 안에 application.yml에 들어가서 아래의 코드를 추가한다.

server:
  servlet:
    encoding:
      force-response: true

 

 

 

 

➤ 데이터 액세스 계층 테스트

 

 

데이터 액세스 계층 테스트를 위해 테스트하려는 repository를 DI 로 주입한다.

 

 

▶️ @DataJpaTest

 

🥭 Repository 기능을 정상적으로 사용하기 위해 Configuration을 Spring이 자동으로 해주게 된다.

🥭 @Transactional 애너테이션을 포함하고 있기 때문에 하나의 테스트 케이스 실행이 종료되는 시점에 데이터베이스에 저장된 데이터는 rollback 된다.

🥭 여러 개의 테스트 케이스를 한꺼번에 실행시켜도 하나의 테스트 케이스가 종료될 때마다 데이터 베이스의 상태가 초기 상태를 유지한다.

 

참고) @DataJpaTest 애너테이션 doc

 

 

▶️ Repository DI로 주입

 

@Autowired
private MemberRepository memberRepository;

 

 

▶️ Given

 

테스트 할 Entity 정보를 준비한다.

 

 

▶️ when

 

정보를 테스트하고자 하는 메서드를 불러서 테스트한다.

 

 

▶️ Then

 

정보가 잘 저장되었는지 검증(assertion) 한다.

 

 

 

 

테스팅 유닛 예제 코드(깃허브)

 

 

 

 

 

감사합니다.

오개념에 대한 지적은 늘 환영입니다. 🤩

 

 

 

 

 

 

 

 

단위 테스트(Unit Test)

 

 

➤ 단위 테스트(Unit Test) 란?

 

컴퓨터에서 단위 테스트는 소스 코드의 특정 모듈이 의도된 대로 잘 작동하는지 검증하기 위해 모든 함수와 메서드에 대한 테스트 케이스를 작성하는 절차를 말한다.

 

애플리케이션에서 Postman 같은 툴을 사용해서 테스트하면 정확하게 어느 부분에서 문제가 발생하는지 알기 어렵다.

Java에서는 메서드 같이 아주 작은 단위를 가지는 기능들을 테스트할 수 있다.

Spring도 역시 계층별로 테스트할 수 있는 테스트 기법을 지원해준다.

 

일반적으로 단위 테스트는 최대한 독립적이고 작은 단위인 것이 좋다.

 

참고) 유닛테스트 위키

 

 

 

▶️ 기능 테스트

 

  • 테스트 단위가 가장 큰 테스트
  • 애플리케이션을 사용하는 사용자 입장에서 애플리케이션이 제공하는 기능이 올바르게 동작하는지 테스트
  • 주로 테스트하는 주체는 개발자가 될수도 있지만 일반적으로 테스트 전문 부서(QA) 또는 외부 QA 업체가 되기도 한다.
  • API 툴이나 데이터베이스까지 연관되어 있어서 개발한 애플리케이션과 연관된 대상이 많기 때문에 단위 테스트라고 볼 수 없다.

 

 

▶️ 통합 테스트

 

  • 클라이언트 측 툴 없이 개발자가 작성한 테스트 코드를 실행시켜 이루어지는 경우가 많다.
  • 주로 개발자가 테스트의 주체가 된다.
  • 여러 계층이 연관되어 있고 DB까지 연결되어 있어서 독립적인 테스트가 가능하다고 볼 수 없으므로 단위 테스트라고 볼 수 없다.

 

 

▶️ 슬라이스 테스트

 

  • 애플리케이션을 특정 계층으로 쪼개어서 하는 테스트를 의미한다.
  • API 계층, 서비스 계층, 데이터 액세스 계층이 각각 슬라이스 테스트의 대상이 될 수 있다.
  • HTTP 요청이 필요하고, 외부 서비스가 연동되거나 데이터 액세스 계층은 DB와 연동되어 있기 때문에 단위 테스트라고 보기 어렵다.
  • Mock(가짜) 객체를 사용하여 계층별로 끊어서 테스트 할 수 있기 때문에 어느 정도 테스트 범위를 좁힐 수 있다.
  • 애플리케이션의 일부만 테스트하기 때문에 부분 통합 테스트라고 부르기도 한다.

 

 

👉🏻 DB를 사용하면 왜 단위 테스트라고 보기 힘든것일까?

통합 테스트나 슬라이스 테스트에서 DB와 연동된다고 해서 무조건 단위 테스트라고 부르기 어려운 것이 아니다. 데이터베이스의 상태가 테스트 전후로 동일하게 유지될 수 있다면 데이터베이스와 연동된다해도 단위 테스트에 포함될수는 있다.

 

 

 

▶️ 단위 테스트

 

  • 핵심 로직(비즈니스 로직)에서 사용하는 클래스들이 독립적으로 테스트하기 가장 좋은 대상이기 때문에 단위 테스트라고 부른다.
  • 의도한 대로 잘 동작하는지 확인하기 위해서는 메서드를 테스트하는 것이 좋다.
  • 단위 테스트 코드는 대부분 메서드 단위로 작성된다.

 

 

 

➤ 단위 테스트를 해야하는 이유?

 

  • 구현한 코드가 의도한대로 동작하는지 결과를 빠르게 확인할 수 있다.
  • 문제점을 쉽게 찾을 수 있다.
  • 언제라도 단위 테스트를 믿고 리팩토링을 할 수 있다.
  • 버그 리포트를 전달 받을 경우 버그가 발생한 기능의 테스트 케이스를 돌려보면서 문제가 발생한 원인을 단계적으로 찾아가기가 용이하다.

 

 

 

※ 테스트 케이스(Test Case) 란?

 

테스트를 위한 입력 데이터, 실행 조건, 기대 결과를 표현하기 위한 명세를 의미한다.

메서드 등 하나의 단위를 테스트하기 위해 작성하는 테스트 코드이다.

 

 

 

➤ F.I.R.S.T 원칙

 

단위 테스트를 위한 테스트 케이스를 작성하기 위한 가이드 원칙으로 F.I.R.S.T 원칙을 참고할 수 있다.

 

 

▶️ Fast(빠르게)

 

일반적으로 테스트 케이스는 빨라야 한다.

자주 돌려야 문제를 빨리 찾는데 너무 느려서 돌리기 힘들면 테스트 케이스를 작성하는 의미가 퇴색될 것이다.

 

 

▶️ Independent(독립적으로)

 

각각의 테스트 케이스는 독립적으로 동작해야 한다.

어떤 테스트 케이스를 먼저 실행해도 실행되는 순서와 상관없이 정상적인 실행이 보장되어야 한다.

 

 

▶️ Repeatable(반복 가능하도록)

 

어떤 환경에서도 반복해서 실행이 가능해야 한다.

외부 리소스나 외부 서비스와 연동하는 경우 동일한 테스트 결과를 보장하지 못하기 때문에 단위 테스트 시에는 외부와의 연동을 끊어주는 것이 바람직하다.

 

 

▶️ Self-validating(셀프 검증이 되도록)

 

성공 또는 실패라는 자체 검증 결과를 보여주어야 한다.

테스트 케이스 스스로가 결과가 옳은지 그른지 판단할 수 있어야 한다.

 

 

▶️ Timely(시기 적절하게)

 

테스트 하려는 기능 구현을 하기 직전에 작성해야 한다.

TDD(테스트 주도 개발) 방식에서는 기능 구현 전에 실패하는 테스트 케이스를 먼저 작성한다.

 

 

 

 

➤ JUnit 없이 비즈니스 로직에 단위 테스트 적용해보기

 

 

JUnit : Java 기반의 소프트웨어를 테스트하기 위한 표준 테스트 프레임워크

 

단위 테스트를 제일 쉽고 빠르게 적용할 수 있는 부분은 헬퍼(helper) 클래스 또는 유틸리티(utility) 클래스 이다.

 

 

 

▶️ Given-When-Then 표현 스타일

 

given-when-then 은 BDD(Behavior Driven Development)에서 사용하는 용어이다.

 

Given

  • 테스트를 위한 준비 과정을 명시한다.
  • 테스트에 필요한 전제 조건들이 포함된다.
  • 테스트 대상에 전달되는 입력 값(테스트 데이터)도 Given에 포함된다.

 

When

  • 테스트 할 동작(대상)을 지정한다.
  • 단위 테스트에선 일반적으로 메서드 호출 통해 테스트를 진행한다.

 

Then

  • 테스트의 결과를 검증하는 영역이다.
  • 예상하는 값과 테스트 대상 메서드의 동작 수행 결과 값을 비교해서 기대한대로 동작을 수행하는지 검증(Assertion)하는 코드가 포함된다.

 

 

✅ Assertion(어써션) 이란?

 

직역하면 단언, 단정 이라는 의미이다.

테스트에서는 어써션이라는 용어는 테스트 결과를 검증할 때 주로 사용한다.

예상하는 결과 값이 참이길 바라는 것이다.

단언문, 단정문이라고 표현하기도 한다.

조건문 등으로 직접 검증할 수 있다.

 

 

 

 

JUnit

 

 

➤ JUnit 이란?

 

Java 언어로 만들어진 애플리케이션을 테스트 하기 위한 Open source Test Framework로

Java의 표준 테스트 프레임워크라고 볼 수 있다.

TestNG라는 경쟁사도 있다.

Spring Boot의 디폴트 테스트 프레임워크는 JUnit이다.

 

 

추가학습) TestNG 란?

  • Cédric Beust가 2004년에 만든 자바 기반 testing framework 이다.
  • JUnit 과 NUnit 에서 영감을 받았다.
  • JUnit 과 오랫동안 경쟁한 테스팅 프레임워크이다.
  • NG는 Next Generation(차세대)라는 의미이다.
  • JUnit 과 다른 TestNG 만의 장점
    • group : Groups 주석으로 사용, 한번 컴파일 한 후엔 같은 그룹끼리 테스트가 가능하다.
    • multithread testing을 지원한다.

 

 

 

➤ JUnit 기본 작성법

 

Spring Boot Initializr 에서 Gradle 기반의 Spring Boot 프로젝트를 생성하고 오픈하면 기본적으로 test 디렉토리가 만들어진다.

그리고 기본적으로 스타터가 포함되고 JUnit도 포함되어 있다.

 

 

▶️ JUnit을 사용한 테스트 케이스의 기본 구조

 

애플리케이션에서 테스트 하고자 하는 대상(Target)이 존재하고

테스트 메서드 위에 @Test 애너테이션을 추가해준다.

 

 

 

▶️ Assertion 메서드 사용하기

 

✔️ assertEquals()

 

org.junit.jupiter.api.Assertions.assertEquals 를 import 해서 assertEquals 메서드를 사용할 수 있다.

 

 

 

✔️ assertNotNull()

 

  • Null 여부 테스트를 진행한다.
  • 첫 번째 파라미터 : 테스트 대상 객체
  • 두 번째 파라미터 : 테스트에 실패했을 때 표시할 메시지(생략 가능)

 

 

✔️ assertThrows()

 

  • 예외(Exception) 테스트
  • 원하는 예외를 던졌을 경우 테스트 passed
  • 결과 예외보다 상위 타입의 예외를 기대해도 동일하게 passed
  • 첫 번째 파라미터 : 발생이 기대되는 예외 클래스 (Exception.class)
  • 두 번째 파라미터 : 람다 표현식으로 테스트 대상 메서드를 호출 (Executable 타입)

 

 

※ Executable 함수형 인터페이스

assertThrows() 의 두 번째 파라미터인 람다 표현식은 JUnit에서 지원하는 Executable 함수형 인터페이스이다.

메서드 하나만 정의되어 있고 리턴값이 없다.

void execute() throws Throwable;

잠재적으로 Throwable 을 던질 수 있는 모든 일반 코드 블럭을 구현하는데 사용된다.

 

참고) Executable 공식문서

 

 

✔️ assertDoesNotThrow()

 

어떤 예외도 던지지 않는지 테스트 한다.

첫 번째 파라미터 : 람다 표현식으로 테스트 대상 메서드를 호출 (Executable 타입)

 

 

 

참고) JUnit5 Assertions 공식문서

 

 

 

▶️ 테스트 케이스 실행 전, 후처리

 

✔️ @BeforeEach

 

이 애너테이션을 추가한 메서드는 테스트 케이스가 각각 실행될 때 마다 케이스 실행 직전에 먼저 실행되어 초기화 작업 등을 진행할 수 있다.

 

 

✔️ @BeforeAll

 

클래스 레벨에서 테스트 케이스를 한꺼번에 실행 시키면 테스트 케이스가 실행되기 전에 한번만 초기화 작업을 한다.

⭐️ @BeforeAll() 에너테이션을 추가한 메서드는 정적 메서드(static method)여야 한다

 

 

 

▶️ 테스트 케이스 실행 후, 후처리

 

@AfterEach, @AfterAll 

@BeforEach, @BeforeAll() 과 동일하게 동작하는데 테스트 케이스 실행이 끝난 시점에 후처리 작업을 할 때 사용한다.

 

 

▶️ Assumption

 

Junit 5에 추가된 기능이다.

Assumption은 말 그대로 ‘가정’할 때 사용한다.

특정 환경에서만 테스트 케이스가 실행되도록 할 수있다.

 

org.junit.jupiter.api.Assumptions 안에서 메서드를 Import 해서 사용한다.

 

 

✔️ assumeTrue()

 

  • 파라미터로 입력된 값이 true 일 때 나머지 아래 로직을 실행한다.
  • 특정 OS 환경 등의 특정 조건에서 선택적인 테스트가 필요할 때 유용하다.

 

※ 실행환경의 OS name 검사하기

System.getProperty(“os.name”).startsWith(“Mac”)

assumeTrue(System.getProperty("os.name").startsWith("Mac"));

getProperty의 파라미터로 os.name을 String 타입으로 전달하면 실행환경의 OS 이름을 리턴한다.

startsWith는 해당 String이 파라미터로 전달하는 String으로 시작하는지 검사한다.

 

 

 

 

 

감사합니다.

오개념에 대한 지적은 늘 환영입니다. 😉

 

1