1. 테스트
1-1. TDD (Test Driven Development)
- 테스트 주도 개발
- 테스트 코드를 먼저 작성 → 작성한 테스트 코드를 통과하게끔 실제 코드 작성하는 개발 방법
- TDD 주요 원칙
- 테스트 실패 확인
- 실제 코드를 구현하기 전 테스트 케이스를 작성함
- 요구사항을 테스트로 변환함으로써 명확한 목표 설정 가능!
- 테스트 통과 가능 최소 코드 작성
- 테스트 통과에 꼭 필요한 최소한의 코드만 작성함
- YAGNI (You Aren’t Gonna Need It) 원칙 적용
- 코드 개선 리펙토링
- 테스트 통과한 최소 코드를 개선함
- 중복 제거, 가독성 향상을 목표 ⇒ 설계가 개선되고 유지보수성이 증가함!
1-2. 테스트 유형
1-2-1. 단위 테스트
- 메서드, 클래스와 같이 개별 코드 단위를 검증하는 테스트
- 격리된 환경에서 실행
1-2-2. 통합 테스트
- 여러 단위 (메서드, 클래스)를 조합하여 상호작용을 검증하는 테스트
- 모듈 간 인터페이스 테스트
1-2-3. 시스템 테스트
- 전체 시스템의 end-to-end 기능을 검증하는 테스트
- 비기능적 요구사항 (성능, 보안, 사용성 등) 테스트함
ex) 스트레스 테스트, 부하 테스트 등
1-2-4. 인수 테스트
- 최종 사용자 관점에서 검증하는 테스트
- 비즈니스 요구사항이 충족되는지 여부를 확인함
2. JUnit 5
- JUnit : 자바를 위한 대표적인 단위 테스팅 프레임워크
- 스프링 부트 버전 2.2 이상 → 자동으로 JUnit 5 의존성 추가됨
2-1. 테스트 메서드
- @Test 어노테이션 붙여야 함
- 접근제어자 : private 이면 안됨!
- 반환 타입 : void 여야 함!
- 메서드는 파라미터를 받지 않아야 함
- 테스트가 Assertion 예외를 발생시키지 않으면 성공!
2-2. JUnit 테스트의 생명주기
2-2-1. 테스트 진행 과정
- 테스트 클래스 인스턴스를 새로 생성함
- 생성한 인스턴스가 가지고 있는 테스트 환경준비 (setup) 메서드를 모두 찾아 호출함
- 테스트 환경준비 (setup) 메서드 : 각 테스트가 실행되기 전에 호출되는 메서드 ⇒ 테스트에 필요한 초기 설정 가능!
- 테스트 메서드 호출함
- 클래스 인스턴스가 가지고 있는 테스트 환경정리 (teardown) 메서드를 모두 찾아 호출함
- 테스트 환경정리 (teardown) 메서드 : 각 테스트가 종료된 후에 호출되는 메서드
⇒ 테스트로 사용한 리소스 해제하거나 데이터 삭제 가능!
- 테스트 환경정리 (teardown) 메서드 : 각 테스트가 종료된 후에 호출되는 메서드
2-2-2. 생명 주기 어노테이션
- @Test : 실제 테스트 케이스 정리
- @BeforeEach : 각 테스트 메서드가 실행되기 전에 호츌
- @AfterEach : 각 테스트 메서드가 실행된 후에 호출
- @BeforeAll : 테스트 클래스 (모든 테스트 메서드) 가 실행되기 전에 호출 / static 으로 선언해야 함!
- @AfterAll : 테스트 클래스 (모든 테스트 메서드) 가 실행된 후에 호출 / static 으로 선언해야 함!
- 코드로 확인
public class JUnitCycleTest {
@BeforeAll
public static void beforeAll() {
System.out.println("JUnitCycleTest.beforeAll");
}
@BeforeEach
public void beforeEach() {
System.out.println("JUnitCycleTest.beforeEach");
}
@AfterAll
public static void afterAll() {
System.out.println("JUnitCycleTest.afterAll");
}
@AfterEach
public void afterEach() {
System.out.println("JUnitCycleTest.afterEach");
}
@Test
public void test1() {
System.out.println("JUnitCycleTest.test1");
}
@Test
public void test2() {
System.out.println("JUnitCycleTest.test2");
}
@Test
public void test3() {
System.out.println("JUnitCycleTest.test3");
}
}
/* 테스트 실행 결과
JUnitCycleTest.beforeAll
JUnitCycleTest.beforeEach
JUnitCycleTest.test1
JUnitCycleTest.afterEach
JUnitCycleTest.beforeEach
JUnitCycleTest.test2
JUnitCycleTest.afterEach
JUnitCycleTest.beforeEach
JUnitCycleTest.test3
JUnitCycleTest.afterEach
JUnitCycleTest.afterAll
*/
2-3. Assertions Method
- 테스트 코드 내에서 특정 조건이 참인지를 확인하는 메서드
⇒ 테스트 케이스의 실행 결과가 예상된 결과와 일치하는 지 확인 가능!
- 대표적으로 두 라이브러리 존재
- Junit (Assertions) : JUnit 5에서 제공하는 기본 assertion 메서드
- AssertJ (Assertions) : AssertJ 라이브러리에서 제공하는 assertion 메서드
2-3-1. Junit (Assertions)
- 아래 import 문으로 메서드 가져옴
import static org.junit.jupiter.api.Assertions.*;
- 대표적인 메서드
- assertEquals(기댓값, 실제값) : 기댓값과 실제값이 일치하면 통과
- assertNotEquals(기대x값, 실제값) : 기대x값과 실제값이 일치하지 않으면 통과
- assertTrue(조건) : 조건이 참이면 통과
- assertFalse(조건) : 조건이 거짓이면 통과
- assertNull(Object obj) : 객체가 null 이면 통과
- assertNotNull(Object obj) : 객체가 null 이 아니면 통과
- assertSame(기댓값, 실제값) : 기댓값과 실제값이 동일한 객체를 참조하면 통과
- assertNotSame(기대x값, 실제값) : 기대x값과 실제값이 동일한 객체를 참조하지 않으면 통과
- assertArrayEquals(기대 array, 실제 array) : 기대 array와 실제 array의 요소 순서와 값이 모두 동일하면 통과
- assertThrows(기대 예외, executable) : executable이 기대한 exception을 발생시키면 통과
2-3-2. AssertJ (Assertions)
- 아래 import 문으로 메서드 가져옴
import static org.assertj.core.api.Assertions.*;
- 대표적인 메서드 (마지막 메서드 제외하고 assertThat(실제값) 뒤에 메서드 체이닝 형식으로 붙임)
- .isEqualTo(기댓값) : 실제값이 기댓값과 같으면 통과
- .isNotEqualTo(기대x값) : 실제값이 기대x값과 다르면 통과
- .isTrue() : 조건 이 참이면 통과
- .isFalse() : 조건이 거짓이면 통과
- .isNull() : 객체가 null이면 통과
- .isNotNull() : 객체가 null이 아니면 통과
- .isSameAs(기댓값) : 실제값과 기댓값이 동일한 객체를 참조하면 통과
- .isNotSameAs(기대x값) : 실제값과 기대x값이 동일한 객체를 참조하지 않으면 통과
- .containsExactly(기대 array) : 배열이 기대 array와 요소 순서와 값이 모두 동일하면 통과
- assertThatThrownBy(() -> executable).isInstanceOf(기대 예외) : executable이 기대한 예외를 발생시키면 통과
3. given-when-then 패턴
- 테스트 구조를 명확하게 나타낼 수 있음
- Given
- 테스트에서 전제 조건을 설정하는 단계
ex) 테스트에 필요한 변수, 객체 등의 생성 등
- 테스트에서 전제 조건을 설정하는 단계
- When
- 실제 테스트하려는 로직을 실행하는 단계
- Then
- 테스트 결과를 검증하는 단계
- 기댓값과 실제값이 일치하는지 비교 → 테스트 성공 여부 판단
- 예시 코드
@Test
public void sumTest(){
//given
int a = 1;
int b = 2;
//when
int result = a+b;
//then
Assertions.assertEquals(a+b, result);
}
4. 단위 테스트와 Mockito
- 단위 테스트
- 개별 모듈이나 메서드 등의 동작을 확인하는 테스트
- 격리된 환경에서 진행하며 의존성을 최소화함!
- Mockito
- 테스트를 위해 사용되는 Mocking 프레임워크
- (주로 단위 테스트에서) 의존성 객체를 모의(=가짜)로 만들어서 실제 객체를 대체 ⇒ 테스트 수행하도록 도와줌!
import static org.mockito.Mockito.*;
📌 mock 객체
: mockito로 생성한 모의 객체 (실제 객체의 행동을 모방)
- 주요 기능
- Mock (모킹) : 실제 객체를 모방하는 모의 객체를 생성해서 객체의 행동을 모방함
- Stub (스텁) : 실제 메서드를 호출하는 것이 아닌 미리 정의해 둔 값을 반환하도록 설정
- verification (검증) : 특정 메서드가 호출되었는지 혹은 몇 번 호출되었는지 검증 가능
- chaining (체이닝) : 메서드 호출 체이닝 가능
4-1. (단위 테스트) 직접 mock 객체 생성, 주입
- mock(mocking할 클래스) : mocking할 클래스에 대해 mock 객체 생성
public class MemberServiceTest {
@Test
@DisplayName("Mock 예제 테스트")
public void testService() {
//given
// 의존성 객체를 모킹
MemberRepository memberRepository = mock(MemberRepository.class);
// Member 엔티티 생성
String name = "홍길동";
int age = 20;
Member member = Member.builder().name(name).age(age).build();
// 메서드 스텁 설정 -> 임의로 생성한 member 리턴!
when(memberRepository.findById(1L)).thenReturn(Optional.ofNullable(member));
// 테스트할 객체 생성 -> 모킹한 의존성 객체 주입
MemberService memberService = new MemberService(memberRepository);
//when
// 메서드 호출
Member foundMember = memberService.findById(1L);
// then
// 결과 검증 -> JUnit
assertEquals(name, foundMember.getName());
// 결과 검증 -> AssertJ
assertThat(foundMember.getAge()).isEqualTo(age);
// 메서드 호출 검증
verify(memberRepository, times(1)).findById(1L);
}
}
4-2. (단위 테스트) 어노테이션으로 자동 mock 객체 생성, 주입
- @Mock : mock 객체를 자동 생성해주는 어노테이션
- @InjectMocks : 인스턴스 자동 생성 + 필요한 의존관계에 대해 mock 객체를 자동 주입해줌
- MockitoAnnotations.openMocks(this) : 현재 클래스에서 사용된 모든 @Mock, @InjectMocks 등의 어노테이션 처리
⇒ mock 객체 초기화
public class MemberServiceTest {
@Mock // mock 객체 자동 생성
private MemberRepository memberRepository;
@InjectMocks // 인스턴스 생성 후 mock 객체 관련 의존관계 자동 주입
private MemberService memberService;
@BeforeEach // 각 테스트 케이스 실행 전 수행됨
void setUp() {
MockitoAnnotations.openMocks(this); // mock 객체 초기화
}
@Test
@DisplayName("Mock 예제 테스트")
public void testService() {
//given
// Member 엔티티 생성
String name = "홍길동";
int age = 20;
Member member = Member.builder().name(name).age(age).build();
// 메서드 스텁 설정 -> 임의로 생성한 member 리턴!
when(memberRepository.findById(1L)).thenReturn(Optional.ofNullable(member));
//when
// 메서드 호출
Member foundMember = memberService.findById(1L);
// then
// 결과 검증 -> JUnit
assertEquals(name, foundMember.getName());
// 결과 검증 -> AssertJ
assertThat(foundMember.getAge()).isEqualTo(age);
// 메서드 호출 검증
verify(memberRepository, times(1)).findById(1L);
}
}
5. 통합 테스트
- 여러 모듈 or 컴포넌트 등을 결합하여 전체 시스템의 동작을 검증하는 테스트
⇒ 모듈 간 상호작용 및 인터페이스의 정확성 확인 가능함!
📌 단위 테스트 vs 통합 테스트
단위 테스트 : 위의 코드 예시와 같이 의존관계 최소화를 위해 mock 객체 사용, 주입
통합 테스트 : 여러 클래스, 컴포넌트들의 상호작용을 테스트하는 것 ⇒ 실제 등록된 빈등을 통해 의존관계 주입
5-1. 관련 어노테이션 정리
5-1-1. @SpringBootTest
- 스프링 부트에서 통합 테스트를 수행하기 위한 어노테이션
- 스프링 어플리케이션의 전체 context를 로드함 ⇒ 모든 빈, 설정, 컴포넌트가 실제 환경에서 동작하는 것처럼 테스트 가능함!
@SpringBootTest
class MemberApiControllerTest {
// 테스트 코드 작성
}
5-1-2. @AutoConfigureMockMvc
- 스프링 부트에서 제공, MockMvc 설정을 자동으로 가능하게 해줌
- 실제 웹 서버를 가동하지 않고도 HTTP 요청 ↔ 응답을 테스트 가능하게 함! (컨트롤러 테스트 환경)
- @Autowired 를 통해 MockMvc 인스턴스 주입 가능
@SpringBootTest
@AutoConfigureMockMvc
class MemberApiControllerTest {
@Autowired
private MockMvc mockMvc;
// 테스트 코드 작성
}
5-1-3. MockMvc
- 웹 서버 가동 없이 스프링 MVC의 동작을 재현
- HTTP 요청에 대한 시뮬레이션을 제공함
- GET : get(url)
- POST : post(url)
- PUT : put(url)
- DELETE : delete(url)
- HTTP 요청 설정 메서드
- .header(key, value) : 요청 헤더 설정
- .contentType(타입) : 요청 본문의 타입 설정
- .content(문자열) : 요청 본문의 내용 설정
- .param(key, value) : 요청 파라미터 설정
- HTTP 응답 검증 메서드
- status() : 응답 코드 확인 메서드
- .isOk(), .isCreated(), .isBadRequest() 등
- header() : 응답 헤더 확인 메서드
- .string(key, value) 등
- jsonPath(”$.필드”).value(실제값) : JSON 응답의 필드 값이 실제값과 일치하는 지 확인
- jsonPath(”$[인덱스].필드”).value(실제값) : 리스트 응답에서 해당 인덱스의 JSON 의 필드 값이 실제값과 일치하는 지 확인
- status() : 응답 코드 확인 메서드
📌 MockMvc 이용 HTTP 요청, 응답 테스트 예시
MockMvc의 perform() 메서드 사용!
⇒ perform() 메서드의 반환 타입은 ResultActions 임
- GET 요청 예시 : get()
- POST 요청 예시 : post()
'Spring Boot' 카테고리의 다른 글
[Spring Boot] 스프링 로깅 방법 (SLF4J, Logback) (0) | 2024.09.15 |
---|---|
[Spring Boot] 주소 입력으로 날씨 정보 출력 예제 프로젝트 (Geocoder API 2.0, open weather map API) (0) | 2024.09.08 |
[Spring Boot] Jpa Auditing (+ BaseEntity) 로 엔티티 관련 이벤트 자동 기록하기 (3) | 2024.09.07 |
[Spring Boot] JpaRepository 개념, 사용 방법 (쿼리 메서드, @Query, JPQL, Native Query) (1) | 2024.09.05 |
[Spring Boot] ResponseEntity 사용 이유, 사용 방법 (1) | 2024.09.05 |