FIRST
F (Fast) - 빠른 속도:
- 테스트는 빠르게 실행되어야 함
- 시스템이 커지면 단위 테스트도 실행하는데 오래 걸리기 때문
- 속도를 빠르게 하기 위해서 보통 3가지의 방법이 사용된다.
- 선택적 컨텍스트 로딩: @WebMvcTest, @DataJpaTest와 같은 애너테이션을 사용해 필요한 부분만 로드하여 전체 애플리케이션 컨텍스트 로딩(스프링 애플리케이션이 실행될 때 모든 필요한 빈(bean)과 의존성을 설정하고 초기화하는 과정)을 피함.
- 의존성 모킹: @MockBean으로 외부 서비스와 의존성을 모킹하여 불필요한 리소스 로딩을 줄임.
- 병렬 테스트 실행: 테스트를 병렬로 실행해 멀티코어 프로세서를 효과적으로 활용함으로써 테스트 시간을 단축.
- JUnit5 설정 파일 작성: junit-platform.properties 파일을 src/test/resources 디렉토리에 생성하고, 다음 설정을 추가함.
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.config.strategy = dynamic
- parallel.enabled 로 설정하여 병렬로 실행하는 것을 허용함
- parallel.default = concurrent로 설정하여 병렬 실행을 허용함
- paralle.config.stategey를 dynamic으로 설정하여 스레드 풀 크기를 동적으로 설정한다.
=> 다만 병렬로 실행하면 동시성 문제가 있을 수도 있음.
I (Independent) - 독립성:
- 각 테스트는 다른 테스트와 독립적으로 실행될 수 있어야 함.
- 테스트 간의 상태 공유 피하기: 각 테스트는 고유한 상태에서 실행되어야 하며, 다른 테스트에 영향을 미치지 않도록 해야 함
package com.example.testdemo.unit02;
public class Calculator {
private int result;
public int add(int a, int b) {
result = a + b;
return result;
}
public int getResult() {
return result;
}
}
package unit02;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import com.example.testdemo.unit02.Calculator;
// 테스트 클래스
public class CalculatorTest {
@Test
void testAdd() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result);
}
@Test
void testAddWithoutSharedState() {
Calculator calculator = new Calculator();
int result = calculator.add(5, 7);
assertEquals(12, result);
}
}
- 각 test에서 객체를 따로 생성해서 다른 테스트와 값이 공유 되지 않음.
- Mocking 사용: 외부 의존성을 모킹하여 테스트 환경을 제어하고, 다른 테스트 결과에 영향을 주지 않도록 해야함.
package order;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import unit02.order.model.Order;
import unit02.order.repository.OrderRepository;
import unit02.order.service.OrderService;
public class OrderServiceTest2 {
@Mock
private OrderRepository orderRepository;
@InjectMocks
private OrderService orderService;
public OrderServiceTest2() {
MockitoAnnotations.openMocks(this);
}
@Test
void testCreateOrder() {
Order order = new Order();
order.setUserId(1L);
order.setProductName("Product A");
when(orderRepository.save(any(Order.class))).thenReturn(order);
Order result = orderService.createOrder(1L, "Product A");
assertEquals(1L, result.getUserId());
assertEquals("Product A", result.getProductName());
verify(orderRepository, times(1)).save(any(Order.class));
}
@Test
void testGetOrderById() {
Order order = new Order();
order.setId(1L);
order.setUserId(1L);
order.setProductName("Product A");
when(orderRepository.findById(1L)).thenReturn(Optional.of(order));
Order result = orderService.getOrderById(1L);
assertNotNull(result);
assertEquals(1L, result.getId());
verify(orderRepository, times(1)).findById(1L);
}
@Test
void testGetOrderById_NotFound() {
when(orderRepository.findById(2L)).thenReturn(Optional.empty());
Order result = orderService.getOrderById(2L);
assertNull(result);
verify(orderRepository, times(1)).findById(2L);
}
}
- 모킹은 소프트웨어 테스트에서 실제 객체나 의존성을 대체하는 가짜 객체(Mock)를 사용하는 기법을 의미하며, 모킹을 통해 테스트 대상 클래스의 외부 의존성을 제거하고, 테스트 환경을 제어할 수 있음.
- 이를 통해 위의 코드에서 디비나 외부 API의 의존성을 호출하지 않고 예상되는 값들을 제어하여 불러오게끔 할 수 있음. (이를 통해 손쉬운 단위테스트 가능)
- 고유한 데이터 사용: 테스트마다 고유한 입력 데이터를 사용하여 테스트 결과의 충돌을 방지해야함
package order;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import unit02.order.model.Order;
import unit02.order.repository.OrderRepository;
import unit02.order.service.OrderService;
public class OrderServiceTest {
private OrderService orderService;
@BeforeEach
void setUp() {
OrderRepository orderRepository = new OrderRepository();
orderService = new OrderService(orderRepository);
}
@Test
void testCreateOrderForUser1() {
Order order = orderService.createOrder(1L, "Product A");
assertEquals(1L, order.getUserId());
}
@Test
void testCreateOrderForUser2() {
Order order = orderService.createOrder(2L, "Product B");
assertEquals(2L, order.getUserId());
}
}
- 여기서는 OrderService를 테스트할 때 서로 다른 사용자 ID와 제품을 사용하여 독립적으로 테스트를 실행함.
R (Repeatable) - 반복 가능성
- 테스트는 여러 번 반복 실행하더라도 항상 동일한 결과를 제공해야 함.
- 위의 계산기 예제처럼, 매번 동일하게 테스트해도 같은 값을 뱉어냄.
S (Self-checking) - 자체 검증 가능성:
- 테스트는 자체적으로 결과를 검증할 수 있어야 함.
- 사람이 수동으로 결과를 확인하지 않아도, 테스트 자체가 예상 결과와 실제 결과를 비교하여 올바르게 동작했는지를 판단하는 것
- 테스트를 포함한 CI과정을 예를 들 수 있음.
- 예를 들어서, 개발자가 신규 커밋을 하면, 젠킨스(Jenkins)는 저장소를 모니터링하다가 새로운 커밋이 발생하면 스프링 부트 애플리케이션을 빌드하고, 사전에 정의된 테스트를 자동으로 실행함. 테스트가 성공하면 빌드가 성공적으로 완료되고, 문제가 발견되면 젠킨스가 즉시 실패를 보고하여 개발자가 신속하게 대응할 수 있음.
T (Timely) - 적시성:
- 테스트는 개발 과정 중 적시에 작성되고 실행되어야 함.
- 팀 내 rule이나 습관을 말하는 듯. ... .
같이 참고하면 좋은 영상.
728x90
반응형
'TDD' 카테고리의 다른 글
TDD 사이클 (0) | 2024.09.16 |
---|---|
Test Smell 처리 방법 (0) | 2024.09.09 |
Mock과 Stub의 차이 (0) | 2024.09.09 |
좋은 테스트의 요건 - RIGHT BICEP (0) | 2024.08.31 |
단위 테스트의 기초 (0) | 2024.08.21 |