[52일차] 단위 테스트,JUnit,Hamcrest

유태형·2022년 7월 12일
0

코드스테이츠

목록 보기
52/77
post-thumbnail

오늘의 목표

  1. 단위 테스트
  2. JUnit
  3. Hamcrest



내용

단위 테스트

에플리케이션 테스트란 특별한 또 다른 과정이 아니라, 스프링을 만들어보고 예제를 실행해 가면서, 출력 로그, HTTPResponseBody확인,데이터베이스 테이블 확인등 구현한 애플리케이션이 예상한 값을 출력하는지 확인하는 모든 과정을 테스트 과정이라고 합니다.

지금은 학습하는 입장이라 구동시켜보고 테스트 해보는 것이 어려운 일이 아니지만 다양한 기능을 제공하게 되고 여러 사용자를 처리해야 될 정더로 애플리케이션이 커지면 그때마다 실행해서 확인하는 것은 쉬운일이 아닐 것입니다.

단위 테스트에서 단위에 대하여 생각하기 위해 어떤 기준으로 정해야 할지 생각해 봐야 합니다.

  • 기능 테스트 : 테스트의 범위중 가장 크고, 가장 큰 단위입니다. 애플리케이션을 이용하는 사용자입장에서 애플리케이션이 제공하는 기능이 올바르게 동작하는지를 테스트합니다.
    주로 전문 QA가 테스트하며 종종 프론트엔드 개발자도 테스트를 진행하기도 합니다. 구현한 애플리케이션과 연관된 대상이 많기 때문에 단위 테스트로 부르기는 힘듭니다.

  • 통합테스트 : 클라이언트 측 툴 없이 개발자까 짜 놓은 테스트 코드를 실행시켜서 이루어 지는 경우입니다. 클라이언트 사이드 와는 관련이 없어지지만, 여전히 다른 계층과 데이터베이스와는 관계가 있으므로 단위 테스트라고 할 수 없습니다.

  • 슬라이스 테스트 : API계층, 서비스 계층, 데이터 엑세스 계층이 각각 슬라이스 테스트 대상이 되므로 각각 다른 계층관 관련은 없어지지만, 외부 HTTP요청이나, 데이터베이스, 외부 서버와 연동되기도 하므로 단위 테스트라고 볼 수 없습니다.

단위 테스트라고 하기엔 외부와 연결되어 있고, 통합 테스트라고 하기엔 계층별로 나뉘어 져 있으므로 부분 통합 테스트 라고도 합니다.

  • 단위 테스트 : 계층별로도 나뉘어져 있고, 외부 연결과도 독립거으로 작동하는, 비즈니스 로직에서 사용하는 클래스들이 독립적으로 테스트하기 가장 좋은 대상이므로 보통 단위 테스트 코드는 메서드 단위로 대부분 작성 됩니다.

데이터베이스의 상태가 테스트 이전과 이 후가 동일하게 유지될 수 있다면 데이터베이스가 연동되어도 단위 테스트라고 할 수 있습니다.

  • 테스트 케이스(Test Case) : 입력 데이터, 실행 조건, 기대 결과를 표현하기 위한 명세서 입니다.


F.I.R.S.T 원칙

단위 테스트 작성에 참고할 수 있는 가이드 로 F.I.R.S.T원칙이 존재합니다.

F.I.R.S.T설명
Fast(빠르게)테스트 케이스는 빠르게 실행할 수 있어야 합니다.
Independent(독립적)어떤 테스트 케이스가 먼저 실행되어도 실행 순서와 상관이 없이 각각의 테스트 케이스는 독립적이어야 합니다.
Repeatable(반복 가능)어떤 환경에서든 반복해서 같은 결과를 확인할 수 있어야 합니다. 외부 리소스가 연동될 경우 테스트 결과에 영향을 줄 수 있어 끊어주는 것이 바람직합니다.
Self-validating(셀프 검증)단위 테스트는 성공 또는 실패 결과를 보여주어야 합니다.
Timely(시기 적절)구현하고자 하는 기능들을 업그레이드 할 때 마다 그때 그때 테스트 케이스 역시 단계적으로 업그레이드 해야 합니다.


given-when-then

BDD(Behavior Driven Development)라는 테스트 방식에서 사용하는 방법입니다.

가독성을 높이고 테스트 케이스를 짜임새 있게 구성하는 것을 도울 수 있습니다.

  • given
    • 테스트 준비 과정으로 필요한 조건들, 테스트 데이터가 포함됩니다.
  • when
    • 테스트 할 동작을 지정합니다.
  • then
    • 테스트 결과를 예사하는 값과 수행 결과 값을 비교하여 검증(Assertion)합니다.

Assertion(어써션)

Assertion은 테스트 결과를 검증할 때 주로 사용합니다.
테스트 결과가 참이길 바란다라는 의미로 해당 결과가 참인지를 비교합니다.




JUnit

assert

Spring에선 기본적으로 JUnit 테스트를 지원합니다.
SpringBoot Initializer로 프로젝트 생성시 build.gradletestImplementation 'org.springframework.boot:spring-boot-starter-test' 가 포함되어 있으며 JUnit도 포함되어 있습니다.

Junit은 기본적으로 src/main/java/...경로의 패키지가 아닌 src/test/java/...경로의 패키지에 존재합니다.

public class 테스트{
	@Test
    public void 테스트1(){
    
    }

	@Test
    public void 테스트2(){
    
    }
    
    @Test
    public void 테스트3(){
    
    }
}

단위 테스트는 메서드 단위로 작성한다는 것을 기억하시나요?
@Test에너테이션이 있는 메서드에 테스트 로직을 작성하면 테스트 할 수 있습니다.

@DsiplayName("테스트 케이스 이름")
@Test
public void 테스트(){
	...
   	
    assertEqauls(expected,actual);
}
  • @DisplayName("테스트 케이스 이름") : 테스트 수행시 해당 테스트의 이름을 지정할 수 있습니다.

  • assertEquals(expect,actual) : expected변수와 actual변수가 값이 동일하면 passed , 다르면 failed을 반환합니다, assertXXX형식으로 다양한 어써션 메서드를 JUnit은 제공합니다.

@DsiplayName("테스트 케이스 이름")
@Test
public void 테스트(){
	...
   	
    assertNotNull(변수,"에러 시 출력 메시지");
}
  • assertNotNull(변수,"에러 시 출력 메시지") : 변수가 Null이면 failed을 반환하고 지정한 메시지를 출력합니다. Null이 아니면 passed를 반환합니다.
@DsiplayName("테스트 케이스 이름")
@Test
public void 테스트(){
	...
   	
    assertThrows(예외Exception.class, () -> ....);
}
  • assertThrows(예외Exception.class, () -> ...) : 람다식에서 첫번째 매개변수로 지정한 예외가 발생하면 passed, 에러가 발생하지 않거나 발생하더라도 지정한 예외를 상속받지 않는 다른 예외가 발생하면 failed를 반환합니다.

여기서 예외Exception.class를 지정하지만 만약 람다식에서 예외Exception.class를 상속받는 상속Exception.class가 발생하였다면 passed를 반환합니다. 예외Exception.class를 상속받지 않은 다른Exception.class 예외가 발생하였다면 failed을 반환합니다.



Before / After

테스트를 수행하는 클래스는 테스트를 수행하는 @Test에너테이션 말고도 다른 에너테이션을 사용할 수 있습니다.

에너테이션설명
@BeforeEach테스트 케이스마다 초기화 작업을 실행합니다.
@BeforeAll모든 테스트를 시작전 초기화 작업을 한번만 실행합니다.
@AfterEach테스트 케이스마다 종료 작업을 실행합니다.
@AfterAll모든 테스트케이스 종료 후 종료 작업을 한번만 실행합니다.
public class 테스트{
	@BeforeEach
    public void initEach(){
    	System.out.println("BeforeEach");
    }
    @BeforeAll
    public void initAll(){
    	System.out.println("BeforeAll");
    }
    @AfterEach
    public void endEach(){
    	System.out.println("AfterEach");
    }
    @AfterAll
    public void endAll(){
    	System.out.println("AfterAll");
    }
    
    @Test
    public void test1(){
    
    }
    @Test
    public void test2(){
    
   	}
   	@Test
    public void test3(){
    
    }
}

각각의 전처리 후처리 에너테이션들과 , 테스트케이스 3개를 실행해보면 다음의 결과와 같습니다.

BeforeAll
BeforeEach
AfterEach
BeforeEach
AfterEach
BeforeEach
AfterEach
AfterAll

BeforeAll이 가장 먼저 실행되고 그다음 각 테스트케이스 전,후로 BeforeEach 와 AfterEach가 실행됩니다. 마지막으로 AfterAll이 실행되고 테스트가 종료됩니다.



Assumption

Assumption은 ~로 가정한다 할때 가정에 해당합니다.

public class 테스트{
	@DisplayName("테스트 케이스 이름")
    @Test
    public void 테스트케이스1(){
    	assumeTrue(매개변수);
        ...
        assertTrue(..);
    }
}

assumeTrue(매개변수) : 주어진 매개변수가 trueassumeTrue()아레의 나머지 로직들을 실행하고, false면 아래의 나머지 로직들이 실행되지 않습니다.

특정 조건에서만 작동하는 선택적인 테스트 케이스가 필요할때 유용하게 사용될 수 있습니다.




Hamcrest

Hamcrest는 JUnit 기반의 단위 테스트에서 사용할수 있는 프레임워크입니다.

Junit도 Assertion의 다양한 메서드를 사용할 수 있지만 Hamcrest가 더 많이 사용됩니다.

  • Assertion을 위한 매처(Matcher)가 자연스러운 문장으로 가독성이 향상 됩니다.
  • 향상된 가독성으로 테스트 실패 메시지를 이해하기 쉬워집니다.
  • 다양한 Matcher를 제공합니다.
public class 테스트{
	@DisplayName("테스트 케이스 이름")
    @Test
    public void 테스트케이스(){
    	String expected = "Test";
        String actual = "Hello";
        
        assertThat(actual, is(equalTo(expected));
    }
}

HamcrestassertThatJUnitAssertion메서드와 유사하지만 차이가 존재합니다.

assertThat(actual, is(equalTo(expected)는 assert that actual is equal to expected(실제값이 예상값과 동일하다고 가정합니다)라는 하나의 문장으로 자연스럽게 읽혀집니다. 메서드와 매개변수등이 약간 다르고 또 에러 메시지도 조금 다르게 출력합니다.

expected: <Test> but was: <Hello>
Expected :Test
Actual   :Hello

Junit의 결과 메시지

Expected: is "Test"
	 but: was "Hello"

Hemcrest의 결과 메시지

@DisplayName("테스트 케이스 이름")
@Test
public void 테스트케이스1(){
	...
    assertThat(변수, is(notNullValue()));
}

Hamscret를 사용해서 Not Null 테스트를 하기위해서는 is(), notNullValue() 메서드를 함께 사용할 수 있습니다. 또한 자연스럽게 읽혀질수 있습니다. assert that 변수 is not null value(변수는 null 값이 아니다고 가정합니다.)

@DisplayName("테스트 케이스 이름")
@Test
public void 테스트케이스1(){
	Throwable actual = assertThrows(예외Exception.class, () -> ...);
    assertThat(actual.getCause(), is(equalTo(null)));
}

Hamscret만으로 Assertion 구성 하기 힘들기 때문에 JUnitAssertion 메서드를 이요해서 검증을 하기도 합니다.

JunitassertThrows()를 이용하여 예외 발생 여부를 확인하고 HamscretassertThat()을 이용하여 추가로 검증을 진행했습니다.

또 Hasmcret만으로 검증하기 위해서는 Custom Matcher를 직접 구현해서 사용할 수도 있습니다.




후기

실력이 좋은 개발자 일수록 테스트케이스를 자주 그리고 많이 사용한다는 것을 김영한 저자님께 들었던 기억이 있습니다. 아직 테스트 케이스를 잘 활용한다고 할 수 없지만 기본적이고 또 원리적으로 이해할 수 있는 시간이어서 아주 흡족스러웠습니다.




GitHub

private!

profile
오늘도 내일도 화이팅!

0개의 댓글