1. JUnit5
- Java에서 독립된 단위테스트(Unit Test)를 지원해주는 프레임워크이다.
- 단정(assert) 메서드로 테스트 케이스의 수행 결과를 판별한다.(ex: assertEquals(예상값, 실제값))
- jUnit4 이후부터는 테스트를 지원 어노테이션을 제공한다.(@Test @Before @After 등)
- @Test 메서드가 호출할 때 마다 새로운 인스턴스를 생성하여 독립적인 테스트가 이루어지게 한다.
- JUnit테스트는 테스트 대상 클래스와 테스트 클래스는 같은 패키지 내에 있어야 한다. 이를 고려하여 위와 같이 설정 하였으니 참고 하자.
- 스프링 이니셜라이져로 프로젝트를 생성한 경우 “spring-boot-starter-test”가 자동으로 있는데 이 경우 의존성을 추가해줄 필요가 없다.
- “spring-boot-starter-test” 에 포함된 라이브러리
- JUnit 5
- JUnit 4와의 하위 호환성을위한 빈티지 엔진 포함
- 이에 별다른 설정 없이 Junit5도 사용 가능
- 스프링 테스트 및 스프링 부트 테스트
- AssertJ
- Hamcrest
- Mockito
- JSONassert
- JsonPath
- Ctrl + Shift + T를 누르면 편하게 해당 클래스의 테스트 클래스를 생성할 수 있음.
1.1 어노테이션
- @Test : @Test를 메서드에 붙이면 테스트 메서드로 인식하고 개별적으로 테스트가 수행됨.
- @DisplayName : 테스트 메서드에 이름을 지정
- @Disabled :
- @Disabled를 붙히면 해당 @Test는 무시됨.
- JUnit4 : @Ignore
- @BeforeAll :
- 테스트 클래스의 모든 테스트 메소드 실행 전에 딱 한 번만 실행
- static
- JUnit4 : @BeforeClass
- @BeforeEach :
- 각각의 테스트 메소드가 실행되기 전에 매번 호출
- JUnit4 : @Before
- @AfterAll :
- 테스트 클래스의 모든 테스트 메소드 실행된 후에 딱 한 번만 실행
- static
- JUnit4 : @AfterClass
- @AfterEach :
- 각각의 테스트 메소드가 실행된 후에 매번 호출
- JUnit4 : @After
- @ExtendWith : 확장을 선언적으로 등록할 때 사용
1.2 JUnit Assertions
- assertEquals & assertNotEquals :
- 두 값을 비교하여 일치 or 불일치 여부 판단
assertEquals(expected, actual);
assertEquals(expected, actual, "message");
- 실패시 메시지 출력
- expected, actual : String…
- assertArrayEquals
- 두 배열을 비교하여 일치 여부 판단
- 두 배열이 모두 null이어도 동일한 것으로 간주함
assertArrayEquals(expected, actual);
- expected, actual : char[]
- assertNotNull & assertNull
- • 객체의 null 여부 확인
assertNull(car);
assertNull(car, "message");
- 실패시 메시지 출력
- car : null
- assertNotSame & assertSame
- 두 변수가 동일한 객체를 참조하는지 확인
assertNotSame(cat, dog);
- dog, cat : Object
- assertTrue & assertFalse
- 특정 조건이 true인지 false인지 판단
assertFalse(condition, "5 is not greater then 6");
- condition : BooleanSupplier(람다식 가능), true, false
- fail
- 제공된 실패 메시지와 기본 원인으로 테스트에 실패 (걍 실패시키는 거임)
- 개발이 완료되지 않은 테스트를 표시하는 데 유용
- 메시지 출력
fail("message");
- assertAll
- 모든 Assertion이 실행되고 실패가 함께 보고되는 그룹화된 Assertion
- MultipleFailureError에 대한 메시지 문자열에 포함될 제목과 실행 가능한 스트림을 허용
- 실행 파일 중 하나에서 OutOfMemoryError가 발생한 경우에만 중단됨
- 메소드 내에서 인자로 람다식을 사용
-
여러 개의 람다식이 동시에 실행됨
@Test
public void test() {
assertAll(
"heading",
() -> assertEquals(4, 2 * 2, "4 is 2 times 2"),
() -> assertEquals("java", "JAVA".toLowerCase()),
() -> assertEquals(null, null, "null is equal to null")
);
}
- assertIterableEquals
- 예상 반복 가능 항목과 실제 반복 가능 항목이 동일한지 확인
- 두 Iterable은 동일한 순서로 동일한 요소를 반환해야 함
- 두 Iterable이 동일한 유형일 필요는 없음 (Collections)
- 아래에서 서로 다른 유형의 두 목록(LinkedList 및 ArrayList)이 동일한지 확인
@Test
public void test() {
Iterable<String> al = new ArrayList<>(asList("Java", "Junit", "Test"));
Iterable<String> ll = new LinkedList<>(asList("Java", "Junit", "Test"));
assertIterableEquals(al, ll);
}
- assertLinesMatc
- 예상 목록이 실제 목록과 일치하는지 확인
- assertEquals, assertIterableEquals와 다름
- 예상 줄이 실제 줄과 같은지 확인
- 같으면 다음 쌍으로 이동
- String.matches() 메서드로 검사
- fast-forward marker 확인
- 아래에서 두 목록에 일치하는 행이 있는지 검사
@Test
public void test() {
List<String> expected = asList("Java", "\\d+", "JUnit");
List<String> actual = asList("Java", "11", "JUnit");
assertLinesMatch(expected, actual);
}
- assertThrows
- 특정 예외가 발생하였는지 확인
- 첫 번째 인자는 확인할 예외 클래스
- 두 번째 인자는 테스트하려는 코드
@Test
void test() {
Throwable exception = assertThrows(
IllegalArgumentException.class,
() -> {
throw new IllegalArgumentException("Exception message");
}
);
assertEquals("Exception message", exception.getMessage());
}
- assertTimeout & assertTimeoutPreemptively
1.3 AssertJ Assertions
- assertThat
assertThat(타겟).메소드().메소드()
- 메소드 체이닝이 가능 (체이닝 가능 메소드)
- 실제 값
assertThat(타겟) 과 비교
- isEqualTo(Object e) : 실제 값이 e와 같은지 확인
- contains(CharSequence e) : 실제 값에 e가 있는지 확인
- startsWith(CharSequence e) : 실제 값의 시작이 e인지 확인
- isInstanceOf(Class<?> type) : 실제 값이 주어진 유형의 인스턴스인지 확인
- isNull() : 실제 값이 null 인지 확인
- as(String description) : description의 내용이 테스트 결과에 출력되도록 함.
- 자세한 메소드 내용은 여기에 더 있음.
- assertThatThrownBy
-
assertThatThrownBy(타겟).메소드().메소드()
- 타겟 : 람다식 ⇒ 실행 후 예외가 발생하는지 확인
- isInstanceOf(Class<?> type) : 실제 값이 주어진 유형의 인스턴스인지 확인
- hasMessageContaining(String e) :
-
assertThat[Exception]().isThrownBy(타겟)
- assertThatNullPointerException()
- assertThatIllegalArgumentException()
- assertThatIllegalStateException()
- assertThatIOException()
- assertThatExceptionOfType(Exception e)
- 체이닝 메소드 :
- withMessage(format, message) : 예외 메시지가 format과 message에 매치되는지 확인
- withMessageContaining(str) : 예외 메시지에 특정 문자열이 포함되어 있는지 확인합니다.
- withNoCause() : 예외의 원인(cause)이 없는지 확인합니다.
2. mockito (service)
- mock이란 실제 사용하는 모듈을 사용하지 않고 실제의 모듈을 "흉내"내는 "가짜" 모듈을 작성하여 테스트의 효용성을 높이는 데 사용하는 객체, mockito는 그 수많은 라이브러리중 하나
- 테스트 시간도 줄이면서 불필요한 리소스 소비를 막고 객체의 행동까지 테스트하는 개발자 마음대로 조정
- 실제료 DB에 안 다녀오고 가짜 모듈을 만들어서 테스트를 하는 도구
- 부하가 많이 걸리는 작업이나 아직 interface만 나온 경우에도 mock 객체로 만들어서 테스트할 수 있음.
- ex) service에 port들이 의존성 주입이 되어 있는 경우
- 사용하려면 @ExtendWith(MockitoExtension.class)를 클래스 상단에 적어줘야함.
- 의존성은 이미 spring boot에 이미 있음 Spring Initializr로 만들 때 자동으로 의존성이 추가됨
- testImplementation 'org.springframework.boot:spring-boot-starter-test'
2.1 @Mock :
- @Mock으로 만든 mock 객체는 가짜 객체이며 그 안에 메소드 호출해서 사용하려면 반드시 스터빙(stubbing)을 해야함.
- 하지 않는다면 원시 타입은 0, 래퍼 타입은 null을 반환한다.
2.2 @Spy :
- @Spy로 만든 mock 객체는 진짜 객체이며 메소드 실행 시 스터빙을 하지 않으면 기존 객체의 로직을 실행한 값을, 스터빙을 한 경우엔 스터빙 값을 리턴
2.3 @InjectMock :
- @InjectMock은 DI를 @Mock이나 @Spy로 생성된 mock 객체를 자동으로 주입해주는 어노테이션
- ex) 기존 코드에서 OrderService에 UserService와 ProductService가 의존성 주입되어 있다면 테스트에서 OrderService에 @InjectMock, UserService와 ProductService에 @Mock이나 @Spy를 쓴다면 OrderService에 알아서 두 클래스가 주입됨.
2.4 stubbing :
- ~~ 행동을 했을 때 ~~한 값이 나와야 한다고 지정 해놓는 것 (외부에서 지정)
- 테스트 호출 중 테스트 스텁은 테스트 중에 만들어진 호출에 대해 미리 준비된 답변을 제공하는 것
- mock 객체의 메소드를 실행했을 때 어떤 리턴 값을 리턴할지를 정의
2.4.1 OngoingStubbing 메소드 (진행 중인 스터빙)
- when에 넣은 메소드의 리턴 값을 정의해주는 메소드
- when({스터빙할 메소드}).{OngoingStubbing 메소드};
- thenReturn : 스터빙한 메소드 호출 후 어떤 객체를 리턴할 건지 정의 (리턴 값)
- thenThrow : 스터빙한 메소드 호출 후 어떤 Exception을 Throw할 건지 정의 (발생할 예외)
- thenAnswer : 스터빙한 메소드 호출 후 어떤 작업을 할지 custom하게 정의 (사용 권장 X)
- thenCallRealMethod : 실제 메소드 호출
2.4.2 stubber 메소드 :
- OngoingStubbing과 다르게 when에 스터빙할 클래스를 넣고 그 후에 메소드를 호출
- 스터빙이 반드시 실행되야 하는 경우 사용하는 메소드이기 때문
- 리턴 타입이 void인 것도 가능
- {Stubber 메소드}.when({스터빙할 클래스}).{스터빙할 메소드}
- doReturn : 스터빙 메소드 호출 후 어떤 행동을 할 건지 정의 (리턴 값)
- doThrow : 스터빙 메소드 호출 후 어떤 Exception을 throw할 건지 정의 (발생할 예외)
- doAnswer : 스터빙 메소드 호출 후 작업을 할지 custom하게 정의
- doNothing : 스터빙 메소드 호출 후 어떤 행동도 하지 않게 정의
- doCallRealMethod : 실제 메소드 호출
2.4.3 메소드 호출마다 바뀌는 스터빙 :
- OngoingStubbing 메소드를 메소드 체이닝으로 쓰면 메소드 호출마다 다른 스터빙을 호출할 수 있다.
- ex) when(productService.getProduct()).thenReturn(product).thenThrow(new RuntimeException());
2.5 verify :
2.5.1 verify 메소드
- verify(verify할 클래스, VerificationMode mode).{verify할 메소드}
- VerificationMode는 검증할 값을 정의하는 메소드
- times(n) : 몇 번이 호출됐는지 검증
- never : 한 번도 호출되지 않았는지 검증
- atLeastOne : 최소 한 번은 호출됐는지 검증
- atLeast(n) : 최소 n 번이 호출됐는지 검증
- atMostOne : 최대 한 번이 호출됐는지 검증
- atMost(n) : 최대 n 번이 호출됐는지 검증
- calls(n) : n번이 호출됐는지 검증 (InOrder랑 같이 사용해야 함) ??
- only : 해당 검증 메소드만 실행됐는지 검증
- timeout(long mills) : n ms 이상 걸리면 Fail 그리고 바로 검증 종료
- after(long mills) : n ms 이상 걸리는지 확인 (timeout과 다르게 시간이 지나도 바로 검증 종료가 되지 않는다.)
- description : 실패할 경우 나올 문구
2.5.2 메소드 호출 순서 검증 (InOrder)
- mock 객체를 사용
- InOrder를 InOrder("mock 객체명")으로 생성
- 검증하고 싶은 순서에 맞게 verify를 써주면 됨
- ex) inOrder.verify(userService).getUser();
- mock 객체를 사용했던 순서와 inOrder.verify(~~)의 순서가 다르면 검증 실패
- calls와 함께 사용
- verifyNoMoreInteractions(mock 객체) : 선언한 verify 후 해당 mock을 실행하면 fail
- verifyNoInteractions(mock 객체) : 테스트 내에서 mock을 실행하면 fail
2.6 스터빙 -> 검증 (assertThat) -> 검증 (verify)
- assertThat은 테스트 코드 내에서 예상 결과와 실제 결과를 비교하여 테스트를 수행하는 데 사용
- verify는 주로 목 객체(Mock)의 특정 메서드가 특정 횟수로 호출되었는지, 호출되지 않았는지 등을 검증하는 데 사용
2.7 BDD 스타일 API
- BDD (Behavior Driven Development)는 "행동"을 기준으로 하는 개발 방법론
- Given : 초기 context 값 (when -> given / 스터빙)
- When : 테스트하려는 조건 (when / 목 객체 사용)
- Then : 테스트 결과 (verify -> then / 검증)
2.8 고민
- 내부에서 새로 객체를 생성해서 save를 하면 스터브하는 객체와 내부에서 생성하는 객체가 달라서 오류가 발생한다. 그렇다고 any()로 대충 스터브하는 게 맞는 걸까
3. MovkMvc (Controller)
Cotroller를 테스트할 때 매번 스프링 애플리케이션을 실행하고 브라우저를 통해 서버로 요청한 후 결과 페이지를 확인해야하는 것은 시간이 많이 소요되며 Cotroller의 동작에는 브라우저나 WAS 처럼 우리가 프로그래밍 하지 않은 요소가 개입기 때문 테스트의 결과를 검증하는 것이 쉽지 않다.
그러나 MockMvc는 브라우저에서 발생하는 요청을 가상으로 만들고 컨트롤러가 응답하는 내용을 기반으로 검증을 수행하기 때문에 Controller 테스트를 쉽게 수행할 수 있다.
MovkMvc란?
- Spring MVC 테스트를 위한 가상의 웹 환경을 제공하는 클래스
- 웹 어플리케이션을 실제 애플리케이션 서버에 배포하지 않고 (실제 서블릿 컨테이너를 사용하지 않고) 테스트용 MVC환경을 만들어 요청 및 전송, 응답기능을 제공해주는 유틸리티 클래스다.
3.1 원리
- 테스트 케이스의 메서드는 DispathcherServlet에 요청할 데이터(요청경로나 요청파라미터)를 설정
- MockMvc는 DispathcherServlet에 요청을 보냄
- (DispathcherServlet은 테스트용으로 확장된 TestDispathcherServlet)
- DispathcherServlet은 요청을 받아 매핑정보를 보고 그에 맞는 핸들러(컨트롤러) 메서드 호출
- 테스트 케이스 메서드는 MovkMvc가 반환하는 실행결과를 받아 실행 결과가 맞는지 검증
3.2 과거와 현재의 사용 방법 차이
3.2.1 @ContextHierarchy 와 @ContextConfiguration의 사용 (과거)
- 위의 2개의 어노테이션을 사용해서 테스트용으로 Bean 주입을 했던 것 같다.
- 현재는
@Import 를 통해서 빈 주입 클래스를 지정할 수 있다.
3.2.2 MockMvc 설정
- 과거에는 각각의 테스트가 실행되기 전(
@Before)에 MockMvcBuilders를 통해서 ****mockMvc를 설정해주었다. MockMvcBuilders로 mockMvc를 설정하는 방법은 2가지가 있다.
webAppContextSetup(WebApplicationContext context)
- 과거에는 @WebAppConfiguration를 사용해서 WebApplicationContext를 @Autowired 해주었는데
- 현재는 @SpringBootTest를 사용하면 WebApplicationContext를 @Autowired 할 수 있음
standaloneSetup(Object... controllers)
- @Controller 인스턴스를 하나 이상 등록해야함.
- 인스턴스를 불러와 @Autowired 하거나 @InjectMock을 하여 인스턴스를 등록함.
public class WelcomeControllerTest {
@InjectMocks MessageRestController controller;
MockMvc mockMvc;
@Before
public void setUpMockMvc() {
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
}
@WebAppConfiguration
public class WelcomeControllerTest {
@Autowired WebApplicationContext context;
MockMvc mockMvc;
@Before
public void setUpMockMvc() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
}
- 현재는 @AutoConfigureMockMvc를 테스트 클래스 위에 지정해놓기만 하면 자동적으로 mockMvc의 설정이 완료됨.
- MockMvcBuilders를 수동으로 생성해야하는 번거로움이 사라짐
- mockMvc 사용하고 싶으면 mockMvc를 @Autowired 하여 선언하면 사용이 가능함.
- 심지어 @WebMvcTest에는 @AutoConfigureMockMvc가 내장되어 있음.
@SpringBootTest
@AutoConfigureMockMvc
class CommentLookUpControllerTest {
@Autowired MockMvc mvc;
}
3.3 사용 방법
- mockMvc 객체는 크게 3가지의 동작을 갖는다.
-
perform : 요청 즉 가상의 request를 처리한다.
- 요청은 MockHttpServletRequestBuilder를 통해 생성한다.
-
expect : 결과 즉 가상 response에 대해 검증한다.
- 검증의 항목은 ResultMatchers를 반환하는 handler(), status(), model, view() 등 메서드에 따른다.
-
do : 테스트 과정에서 콘솔 출력 등 직접 처리할 일을 작성한다.
- 실제 동작은 ResultHandler를 사용한다.
MockHttpServletRequestBuilder builder = get(”/”)
.param("a", "4.5")
.cookie(new Cookie("name", "홍길동"))
mockMvc.perform(builder)
.andExpect(status().is(200))
.andExpect(view().name("index"))
.andDo(print())
.andDo(log());
- 자세한 사용 방법은 아래의 링크를 들어가서 확인하여 사용하면 됨.
4. 참고 자료
4.1 JUnit
4.2 mockito
4.3 mockMvc