Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

이지은님의 블로그

250307 - Java Spring 테스트 전략과 실습, 테스트 중요도: Repository-Service-Controller 단위테스트, 통합테스트(@DataJpaTest, @ExtendWith, @WebMvcTest, @SpringBootTest) 본문

TIL

250307 - Java Spring 테스트 전략과 실습, 테스트 중요도: Repository-Service-Controller 단위테스트, 통합테스트(@DataJpaTest, @ExtendWith, @WebMvcTest, @SpringBootTest)

queenriwon3 2025. 3. 8. 02:45

 오늘 배운 

테스트코드의 작성방법과 실습내용을 정리해보았다.

 

 

<<목차>>

1. 테스트 커버리지의 종류

    1) Line Coverage

    2) Condition Coverage

2. 테스트 코드 실습

    1) 의존성

    2) given-when-then

3. 스프링 테스트

    1) 어노테이션 종류

    2) Mocking

    3) application-test.properties

4. 테스트 코드 실습

    1) Repository 테스트(@DataJpaTest)

    2) Service 테스트(@ExtendWith(MockitoExtension.class))

    3) Controller 테스트(@WebMvcTest(XxxController.class))

    4) 통합테스트(@SpringBootTest)

5. 테스트의 중요도

    1) 어떤 코드를 테스트 해야할까?

    2) 테스트의 의도 전달 방법

    3) 테스트 시나리오를 생각하자

    4) 테스트 안정망

    5) 테스트 코드의 요소

    6) TDD(Test-Driven Development, 테스트 주도 개발)

 

 

 


 

 

1. 테스트 커버리지의 종류

1) Line Coverage

: Line Coverage는 테스트가 소스 코드의 몇 퍼센트를 실행했는지를 측정하는 지표. 테스트 코드를 한번이라도 실행하면 해당 라인은 커버된 것으로 간주된다.

 

2) Condition Coverage

: Condition Coverage는 개별 조건식이 참과 거짓으로 평가되는 경우를 모두 테스트했는지를 측정하는 지표(분기점)

각 조건문 안의 모든 조건들은 각각 독립적으로 실행되어야 한다.

복합조건이 있는 경우, 각 서브 조건이 독립적으로 테스트 되어야 한다.

 

 

2. 테스트 코드 실습

1) 의존성

(테스트 코드와 스프링은 별개 —> 스프링을 사용하지 않아도 테스트가 가능)

 

bom이란?

JUnit이라는 프레임워크를 사용하는데,
Bill of Meterials(자재 명세서)라는 뜻인데, 의존성 버전 관리를 위해 쓴다.
버전을 통일시켜서 관리할 수 있도록 한다.

예를 들어 org.junit버전이 5.9.1로 설정 되었다면, 그 아래 org.junit.jupiter도 해당 버전으로 자동 설정되도록 한다.

그러나 스프링 부트에는 이미 호환되는 버전의 테스트 관련 라이브러리를 제공한다. 따라서 테스트 관련 의존성을 넣지 않더라도 테스트 코드를 작성할 수 있다.
dependencies {
    testImplementation platform('org.junit:junit-bom:5.9.1')
    testImplementation 'org.junit.jupiter:junit-jupiter'
}

 

 

H2(인메모리 데이터 베이스)

Repository, 통합 테스트 용으로 이번 실습에는 H2를 사용한다.
H2 의존성을 추가했다는 가정하에, 다른 DB 설정을 하지 않으면 자동으로 H2 데이터 베이스가 사용된다.
dependencies {
    testRuntimeOnly 'com.h2database:h2'
}

 

 

 

2) given-when-then

테스트 코드는 다음과 같은 과정을 거친다.

public class BasicTest {
    @Test
    @DisplayName("더하기 테스트")
    public void calTest() {
        // given
        int a = 1;
        int b = 3;

        // when
        int sum = a + b;

        // then
        assertEquals(4, sum);
        // assertThat(sum).isEqualTo(4);
    }
}

given과 when과 then순서로 테스트 코드가 진행되는데,

각 과정의 의미는 다음과 같다.

  • Given: 주어진 전제 조건을 정의하고 테스트 실행을 위한 준비
  • When: 테스트하려는 메서드나 기능을 실행하는 과정
  • Then: 메서드나 기능이 실행된 예상되는 결과가 나오는지 확인

 

@Test

테스트임을 명시 (테스트를 실행 가능하도록 있음)

 

@DisplayName(“테스트 이름”)

테스트 이름을 설정할 있다.

 

assertEquals(4, sum);
assertThat(sum).isEqualTo(4);

테스트한 값이 서로 같은지 확인할 있다.

 

 

 

3. 스프링 테스트

1) 어노테이션 종류

1️⃣ @DataJpaTest

JPA와 관련된 Component들만 모두 가지고와 Repository Layer 단위 테스트를 위한 Annotation이다.

기본적으로 In-memory DB(ex H2)를 사용하여 테스팅

요약: JPA 레포지토리 단위 테스트

 

2️⃣ @ExtendWith

Junit5 환경에서 확장기능을 사용할때 사용.

주로 MockitoExtension 과 함께 Service Layer, 클래스 단위 테스트에 사용

요약: 서비스 단위 테스트

@ExtendWith(MockitoExtension.class)
public class AuthServiceTest {
    ...
}

 

3️⃣ @WebMvcTest

스프링의 Web Layer(controller, filter 등)을 테스트하기 위한 Annotation이다.

@Controller, @ControllerAdvice 등 웹과 관련된 Bean만 로드하여 테스트를 수행한다.

요약: 컨트롤러 단위 테스트

 

4️⃣ @SpringBootTest

스프링 부트 전체를 테스트 수행하기 위해서 사용하는 Annotation이다.

서버를 실행하듯 모든 스프링 Context를 로드한다.

요약: 통합 테스트

 

 

 

2) Mocking

: 테스트 코드를 작성하면 테스트를 하기 위한 코드 이외의 의존 객체들이 존재하는 경우가 있을 때 사용 

예를 들면 Service 코드를 테스트 하는데 Repository가 필요한 경우.(가짜 객체로 정의해준다.)

 

Mocking을 통해 외부 의존성을 제거할 수 있고, (단위)테스트 범위를 준수 할 수 있으며, 에러 상황을 강제 발생 시킬 수 있다.

 

 

1️⃣ 행위 검증(Behavior Validation)

  • 테스트시 특정한 행위를 하였는가를 확인.
  • 테스트를 하려는 코드가 어떤 메소드를 호출하였는지 하지 않았는지, 몇번 수행했는지 확인.
  • Mockito verify() 메소드를 통해 검증
verify(refreshTokenRepository, times(1)).save(any(RefreshToken.class));

 

 

2️⃣ 상태 검증(State Validation)

  • 기능 수행 후 결과값이 기대값과 일치하는가를 확인하는 행위이다.
  • 반환값, DB 저장된 데이터 값에 대해 의도한대로 반환/저장이 되는지 확인.
// when
String createdToken = tokenService.createAccessToken(user);

// then
assertEquals(accessToken, createdToken);

 

 

 

3) application-test.properties

spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;
spring.datasource.username=root
spring.datasource.driver-class-name=org.h2.Driver

spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database=h2

default로 H2를 사용한다.

테스트용으로 mySQL로 테스트 가능

 

 

 

4. 테스트 코드 실습

보통은 Repository -> Service -> Controller 순서로 코드를 작성한다.

 

1) Repository 테스트(@DataJpaTest)

Repository 같은 경우는 과거에 비해 쿼리를 간단하게 가져가는 추세이기 때문에 그렇게 복잡한 로직이 잘 없다. 복잡한 로직이 없으므로 예외 경우의 수가 거의 없다. 따라서 중요도가 떨어진다 판단하여 우선순위에서 밀리는 경우가 많음

특히, JPA 단순 save(), findAll(), findById() 같은 메서드들은 이미 라이브러리에서 검증이 끝난 메서드이므로 테스트를 하는 것이 크게 의미 없을 있다.

// Repository의 조회 테스트

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void 이메일로_사용자를_조회할_수_있다() {
        // given
        String email = "asd@asd.com";
        User user = new User(email, "password", UserRole.USER);
        userRepository.save(user);

        // when
        User foundUser = userRepository.findByEmail(email).orElse(null);

        // then
        assertNotNull(foundUser);
        assertEquals(email, foundUser.getEmail());
        assertEquals(UserRole.USER, foundUser.getUserRole());
    }
}

 

Repository Test 필드에서는 @Autowired사용

레포지토리에서 조회 검증시 먼저 레포지토리에 저장해야한다.(given)

userRepository.save(user);

 

 

2) Service 테스트(@ExtendWith(MockitoExtension.class))

서비스 테스트 부터는 테스트하지 않을 객체와 테스트를 할 객체를 구분해야한다.

그래서 테스트 대상 외의 의존 객체를 @mock, 테스트를 할 객체를 @InjectMocks로 설정해야한다.

 

1️⃣ Service 조회 테스트(성공)

// Service의 조회 테스트(성공)

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock // 테스트 대상 아님
    private UserRepository userRepository;

    @InjectMocks // 테스트 대상
    private UserService userService;

    @Test
    void User를_ID로_조회할_수_있다() {
        // given
        String email = "asd@asd.com";
        long userId = 1L;
        User user = new User(email, "password", UserRole.USER);
        ReflectionTestUtils.setField(user, "id", userId);

        given(userRepository.findById(anyLong())).willReturn(Optional.of(user));

        // when
        UserResponse userResponse = userService.getUser(userId);

        // then
        assertThat(userResponse).isNotNull();
        assertThat(userResponse.getId()).isEqualTo(userId);
        assertThat(userResponse.getEmail()).isEqualTo(email);
    }

 

Mocking 객체는 given 작성할 있다.

given(userRepository.findById(anyLong())).willReturn(Optional.of(user));

Given은 테스트 대상 객체가 아닌 의존 받는 객체의 입력값 리턴값을 설정해줄 수 있다. 보통 입력값은 거의 anyLong()으로 불특정 할 수 있는 매개변수를 주어야한다.

 

given과정에서 객체의 상태를 정의해줄때 ReflectionTestUtils.setField 사용할 있다.

ReflectionTestUtils.setField(user, "id", userId);

이를 통해 불필요한 생성자의 생성을 줄일 수 있고 객체들을 설정해 줄 수 있다.

 

Reflect에 대하여

requestDto에는 기본생성자가 자동으로 동작하기 때문에 따로 생성해 줄 필요가 없었다.(reflect)

그러나 dto에 테스트만을 위해 @NoArgsContructor나, @AllArgsContructor를 써주기도 한다. 하지만 record 객체(불변 객체)의 경우 이러한 생성자들이 기본으로 제공된다.(엔티티에는 사용하지 않는다)

 

 

2️⃣ Service 조회 테스트(실패)

    // Service의 조회 테스트(실패)

    @Test
    void 존재하지_않는_User를_조회_시_InvalidRequestException을_던진다() {
        // Given
        long userId = 1L;
        given(userRepository.findById(anyLong())).willReturn(Optional.empty());

        // When & Then
        assertThrows(InvalidRequestException.class,
                () -> userService.getUser(userId),
                "User not found"
        );
    }
}

 

에러를 검증할 때는 assertThrows 주로 사용한다.

 

 

3️⃣ Service 삭제 테스트(성공)

    // Service의 삭제 테스트(성공)
    @Test
    void User를_삭제할_수_있다() {
        // given
        long userId = 1L;
        given(userRepository.existsById(anyLong())).willReturn(true);
        doNothing().when(userRepository).deleteById(anyLong());

        // when
        userService.deleteUser(userId);

        // then
        verify(userRepository, times(1)).deleteById(userId);
    }

deleteUser 무엇을 리턴하지 않는다. 그래서 doNothing().when() 사용한다.(mock 객체가 아무것도 리턴하지 않을때 사용, = given() 비슷한 기능)

 

doNothing().when(userRepository).deleteById(anyLong());

—> 행위검증을 한다.

verify(userRepository, times(1)).deleteById(userId);

 

 

4️⃣ Service 삭제 테스트(실패)

    @Test
    void 존재하지_않는_User를_삭제_시_InvalidRequestException를_던진다() {
        // given
        long userId = 1L;
        given(userRepository.existsById(userId)).willReturn(false);

        // when & then
        assertThrows(InvalidRequestException.class, () -> userService.deleteUser(userId));
        verify(userRepository, never()).deleteById(userId);
    }
}

 

유저를 삭제하지 못했다는것을 검증하기 위해 never() 사용한다.

verify(userRepository, never()).deleteById(userId);

 

 

3) Controller 테스트(@WebMvcTest(XxxController.class))

3.4.x이전 사용한 코드 @MockBean: Bean Mocking   
MockMvc: 스프링 MVC 컨트롤러를 테스트할 있게 해주는 모의 HTTP 요청/응답 도구
3.4.x이후 사용한 코드 @MockitoBean: Bean Mocking 
MockMvcTester: MockMvc 래핑(상위 추상화) 버전, AssertJ 통합될 있음

—> 3.3.x 버전 기준 설명

 

AssertJ란
JUnit5을 보완하기 위해 나온 메서드 체이닝을 통한 fluent API 검증 라이브러리.
@Test
void test() {
    // JUnit 기본 검증
    User user = new User("asd@asd.com", "password", UserRole.USER);
    assertNotNull(user);
    assertEquals("asd@asd.com", user.getEmail());
    assertEquals("password", user.getPassword());
    assertEquals(UserRole.USER, user.getUserRole());

    // AssertJ 검증(권장)
    assertThat(user)
            .isNotNull()
            .extracting(User::getEmail, User::getPassword, User::getUserRole)
            .containsExactly("asd@asd.com", "password", UserRole.USER);
}

 

 

1️⃣ Controller 조회 리스트 성공

import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void User_목록_조회_빈리스트() throws Exception {
        // given
        given(userService.getUsers()).willReturn(List.of());

        // when & then
        mockMvc.perform(get("/users"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$").isEmpty());
    }

 

컨트롤러 테스트시에는 service Mock객체로 설정해야하는데 이는 @MockBean 사용한다.(3.4이상은 @MockitoBean) -> given 사용됨

@MockBean
private UserService userService;

 

그리고 검증은 assertThat() 대신 MockMvc 사용해야한다.

@Autowired
private MockMvc mockMvc;

 

 

mockMvc.perform(get("/users"))
    .andExpect(status().isOk())
    .andExpect(jsonPath("$").isEmpty());

 

 throws Exception을 넣어줘야하는 이유가 되는 부분이다.

 

mockMvc.perform(get("/users"))

: MockMvc를 사용하여 GET /users 요청을 보낸다.(컨트롤러의 응답의 경로가 맞는지에 대한 테스트)

.andExpect(status().isOk())

: HTTP 응답 상태가 200 OK인지 검증한다.

.andExpect(jsonPath("$").isEmpty());

: 응답의 JSON 데이터를 검증하는 부분이다. $는 루트를 뜻하고, isEmpty()는 응답본문이 비어있어야함을 의미한다. 

 

 

토큰이 응답값이라면?(코틀린 코드)

assertThat(mvcResult)
    .hasStatusOk()
    .hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON)
    .bodyJson()
    .extractingPath("$.token") // 키값을 가져옴
    .isEqualTo(dummyToken);

 

 

 

2️⃣ Controller 다건 조회 성공

    @Test
    void User_목록_조회() throws Exception {
        // given
        long userId1 = 1L;
        long userId2 = 2L;
        String email1 = "user1@a.com";
        String email2 = "user2@a.com";
        List<UserResponse> userList = List.of(
                new UserResponse(userId1, email1),
                new UserResponse(userId2, email2)
        );
        given(userService.getUsers()).willReturn(userList);

        // when & then
        mockMvc.perform(get("/users"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.length()").value(2))
                .andExpect(jsonPath("$[0].id").value(userId1))
                .andExpect(jsonPath("$[0].email").value(email1))
                .andExpect(jsonPath("$[1].id").value(userId2))
                .andExpect(jsonPath("$[1].email").value(email2));
    }

목록을 다음과 같이 확인 가능하다.

 

 

3️⃣ Controller 단건 조회

    @Test
    void User_단건_조회() throws Exception {
        // given
        long userId = 1L;
        String email = "a@a.com";

        given(userService.getUser(userId)).willReturn(new UserResponse(userId, email));

        // when & then
        mockMvc.perform(get("/users/{userId}", userId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(userId))
                .andExpect(jsonPath("$.email").value(email));
    }
}

==> 결국 검증은 어떤 메서드와 경로에서 어떤 상태코드를 날리고, 어떤 응답을 주는지 검증하는 것

 

 

4) 통합테스트(@SpringBootTest)

1️⃣ 통합테스트 환경 설정

@ActiveProfiles("test") 
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 
public class UserIntegrationTest {
	...
}

테스트 전용 프로파일을 사용하여 테스트 환경을 분리한다.

랜덤 포트에서 애플리케이션을 실행하여 통합 테스트를 수행한다.

 

 

2️⃣ 의존성 주입

@LocalServerPort
private int port;

@Autowired
private TestRestTemplate restTemplate;

@Autowired
private JwtUtil jwtUtil;

int port: Spring Boot가 실행하는 랜덤 포트 번호를 가져온다.

TestRestTemplate: HTTP 요청을 보낼 때 사용하는 테스트용 REST 클라이언트이다.

 

사용할 객체를 @Autowired건다.

 

 

3️⃣ 회원가입

// 회원가입 행위
String signupUrl = "http://localhost:" + port + "/auth/signup"; 
SignupRequest signupRequest = new SignupRequest("user@example.com", "password", "USER"); 
ResponseEntity<SignupResponse> signupResponse = restTemplate.postForEntity(signupUrl, signupRequest, SignupResponse.class);

다음과 같이 회원가입의 행동을 실행한다.

 

restTemplate.postForEntity(signupUrl, signupRequest, SignupResponse.class);

다음과 같이 테스트용 객체에게 url, 요청dto, 응답dto 매개 변수값을 전달한다.(요청을 생성한다.)

 

// 회원가입 검증
assertThat(signupResponse.getStatusCode()).isEqualTo(HttpStatus.OK);   //(1)
SignupResponse signupBody = signupResponse.getBody();   
assertThat(signupBody).isNotNull();   //(2)
String token = signupBody.getBearerToken(); 
assertThat(token).isNotBlank();    // (3)

(1) 상태값이 설정한 성공의 상태값인지 확인한다.

(2) 응답값의 body를 가져와서 null인지 아닌지 확인한다.

(3) 응답의 token을 가져와서 토큰이 비었는지 아닌지 확인한다.

 

 

4️⃣ JWT에서 유저 정보값 추출

// JWT 에서 userId 추출 
String rawToken = jwtUtil.substringToken(token); 
Claims claims = jwtUtil.extractClaims(rawToken); 
Long userIdFromToken = Long.valueOf(claims.getSubject()); 
assertThat(userIdFromToken).isNotNull();

 

 

 

5️⃣ 유저 단건 조회 API 요청

String url = "http://localhost:" + port + "/users/" + userIdFromToken;
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", token);
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);

url 생성하고 /users/{userId} 엔드포인트를 GET 요청으로 호출한다. 그리고 Authorization 헤더에 JWT 토큰을 추가하여 인증을 처리할 있도록 한다.

 

ResponseEntity<UserResponse> userResponse = restTemplate.exchange(
        url,
        HttpMethod.GET,
        requestEntity,
        UserResponse.class
);

TestRestTemplate 사용하여 메서드와 GET요청을 보내고, 요청 객체, 응답할 타입으로 변환 시킨다.

 

restTemplate.exchange(URI, 사용 메서드, 요청값(여기서는 토큰), 변환할 응답 객체)

형태로 사용한다.

 

 

 

6️⃣ 응답 상태를 확인 검증

assertThat(userResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
UserResponse responseBody = userResponse.getBody();
assertThat(responseBody).isNotNull();
assertThat(responseBody.getId()).isEqualTo(userIdFromToken);
assertThat(responseBody.getEmail()).isEqualTo("user@example.com");

응답 상태 코드가 200 OK인지 확인

응답 객체(UserResponse)가 null이 아닌지 확인

userId가 회원가입한 유저와 동일한지 확인

이메일이 회원가입 시 입력한 값과 일치하는지 확인

 

 

 

5. 테스트의 중요도

1) 어떤 코드를 테스트 해야할까?

  • 단위 테스트를 할 때는 비용대비 효과를 고려하여 불필요한 시간 낭비를 줄여야 함
  • 테스트 중요도는 코드의 복잡도, 도메인 관련 및 의존성으로 판단
  • 컨트롤러를 테스트 할때: 외부에 검증해야할 때 등.. 으로 제한되어 중요도가 떨어진다.
  • 지나치게 복잡한 코드는 테스트 대상 보다는 리팩토링 대상이 될 수 있다.
  • 도메인 모델 및 알고리즘이 테스트의 중요도가 높다.

 

2) 테스트의 의도 전달 방법

@Test
void 회원가입_이미_존재하는_이메일이면_실패() {
    // given: 기존에 존재하는 이메일
    // when: 회원가입 시도
    // then: 실패
}

테스트의 의도는 테스트 대상 + 행동 + 기대 결과가 포함되도록 작성

 

 

3) 테스트 시나리오를 생각하자

  • @Nested, @Order(순서제어가 필요할때)를 활용하여 테스트 코드 작성 의도와 시나리오를 알려줄 수 있음
  • 예외 케이스 → 정상 응답 순으로 테스트 코드의 시나리오를 전달함으로써 특정 모듈에 대한 비즈니스 예외를 쉽게 파악할 수 있음

 

@Nested 중첩클래스를 만들어 주는 방법을 통해 테스트를 병렬적으로 묶어줄수 있다. 

@Nested
class GetMovieTest {
	@Test
	public void 영화단건조회_조회불가() {
		…
	}
	
	@Test
	public void 영화단건조회_정상조회() {
		…
	}
}

 

 

 

4) 테스트 안정망

1️⃣ 기획 변경에 대해 변경된 코드의 검증

  • 기획 변경에 의해 기존 테스트는 실패하며, 실패된 테스트에 의해 검증되지 않은 코드를 배포하지 않을 수 있음
  • 기획 변경이 기존 로직과 충돌하는지 즉시 감지 가능하며, 정책 변경에 대한 검증을 수행할 수 있음

2️⃣ 리펙토링 후 기존 기능이 깨지지 않았음을 보장

  • 기존 코드의 동작을 유지하면서도, 안심하고 코드를 수정할 수 있음
  • 특히 리팩토링할 때 코드가 정상적으로 동작하는지 즉시 확인 가능

3️⃣ 개발자에 대한 신뢰도가 매우 높아짐

  • 테스트에 의해 개발 단계에서 미리 버그를 감지하고 예방 함으로써 코드 신뢰도가 높아질 수 있음
  • QA(테스터)의 협업에서 신뢰도가 높아지며, 전체적인 업무 퀄리티가 상승함

 

 

5) 테스트 코드의 요소

1️⃣ Mock

: 단순 가짜 객체를 만드는 도구가 아니라 테스트를 더욱 강력하고 안정적으로 만들어주는 요소

  • 테스트의 독립성 유지: 의존성을 제거하고, 순수한 단위 테스트 가능
  • 특정 시나리오 강제: 예외 처리, 오류 발생 등을 쉽게 재현
  • 테스트 속도 개선: DB, 네트워크 호출 없이 빠르게 실행
  • 의존성이 많은 서비스에서 중요: 특정 계층만 집중적으로 테스트 가능
  • 행위 검증 가능: 특정 메서드가 실행되었는지 체크 가능

2️⃣ any

: 특정 입력값에 의존하지 않고 핵심 동작을 검증할 수 있으며, 특정 값이 중요한 게 아니라 제대로 호출되었는지를 확인하는 요소

  • 특정 값이 아니라 행위 자체를 검증하는 것이 핵심
  • 특정 필드 값과 관계없이 특정 로직이 호출되었는지만 확인
  • 매개변수 값을 일일이 신경 쓰지 않아도 되어, 값보다는 행위에 집중할 수 있음
  • 값이 아니라 동작을 검증해, 코드 변경에도 깨지지 않는 유연하고 안정적인 테스트를 만들 있음
public class BaseTest {
    @Test
    public void spy_test() {
        Movie movie = new Movie("테스트 무비", 2025);
        Movie movieSpy = spy(movie);
        Movie movieMock = mock();

        given(movie.getname()).wiLLReturn("테스트 무비2");
        given(moviespy.getName()).willReturn("테스트 무비2");
        given(movieMock.getName()).wilLReturn("테스트 무비");

        assertEquals("테스트 무비", movie.getName());
        assertEquals("테스트 무비", movieSpy.getName());
        assertEquals(2025, movieSpy.getProductionYear());
        assertEquals("테스트 무비", movieMock.getName());
    }
}

 

 

6) TDD(Test-Driven Development, 테스트 주도 개발)

TDD 테스트를 먼저 작성하고, 테스트를 통과하는 코드를 작성하는 개발 방식
TDD
에서는 테스트 코드 작성기능 구현리팩토링 순서로 진행한다.