Cute Running Puppy
본문 바로가기
개발일기/Java

스프링 테스트 프레임워크 사용하기

by 징구짱 2023. 3. 11.
728x90

📌 TDD (Test-Driven Development)

테스트 코드를 먼저 작성하고 실제 동작하는 코드를 개발하는 순서로 개발하는 개발 방법론

설계 → 개발 → 테스트 에서 설계 → 테스트 → 개발 순서로!

 

 

📌 Given - When - Then Pattern

테스트 코드를 작성하는 가장 대표적인 방법론

Given - 준비 When - 실행 Then - 검증 이렇게 단계별로 테스트코드를 나누어서, 매우 직관적으로 작성 할 수 있습니다.

 

 

💡 JUnit

 자바 프로그래밍 언어 용 단위 테스트 프레임워크

프로그램을 작은 단위로 쪼개서 각 단위가 정확하게 동작하는지 검사하고 이를 통해 문제 발생 시 정확하게 어느 부분이 잘못되었는지를 재빨리 확인할 수 있게 해준다.

  • build.gradle 파일에 JUnit 사용을 위한 환경설정이 이미 되어있음

 

  • 테스트 파일 생성

테스트 파일을 생성할 파일내에서 Alt + Insert 후 Test 클릭 

 

OK를 클릭하여 Test 파일을 생성해줍니다.

 

 

  • 어노테이션

생성한 클래스에 어노테이션 추가

@Nested     // 테스트 코드를 작성하고 확인을 했을 때 눈에 잘 보일 수 있게 계층구조로 보여줌
@DisplayName("회원이 요청한 관심상품 객체 생성")	// 설정대로 보임
class CreateUserProduct {

...

}

 

기본값 설정 메서드에 어노테이션 추가

@BeforeEach 	// 테스트 하는데 필요한 부분을 미리 설정
void setup() {

...

}

 

테스트할 메서드에 어노테이션 추가

@Test				// 테스트 할 메서드
@DisplayName("정상 케이스")	// 설정대로 보임
void createProduct_Normal() {

...

    assertNull(product.getId());
    assertEquals(userId, product.getUserId());
        
}

Unit에서 제공해주는 검증 함수

asserNull  : 주어진 인자(product.getId())가 null이어야 테스트를 통과시켜주는 함수

assertEquals : 주어진 인자 두개가 같아야 테스트를 통과시켜주는 함수

 

실패 케이스

@Test
@DisplayName("실패 케이스 null")
void fail1() {

...

    Exception exception = assertThrows(IllegalArgumentException.class, () -> {	// 예상하는 예외처리를 설정
        new Product(requestDto, userId);
    });

    assertEquals("저장할 수 있는 상품명이 없습니다.", exception.getMessage());	 // 우리가 설정한 메세지와도 같아야함
}

 

더보기

예시

class ProductTest {

    @Nested
    @DisplayName("회원이 요청한 관심상품 객체 생성")
    class CreateUserProduct {
        private Long userId;
        private String title;
        private String image;
        private String link;
        private int lprice;

        @BeforeEach
        void setup() {
            userId = 100L;
            title = "오리온 꼬북칩 초코츄러스맛 160g";
            image = "https://shopping-phinf.pstatic.net/main_2416122/24161228524.20200915151118.jpg";
            link = "https://search.shopping.naver.com/gate.nhn?id=24161228524";
            lprice = 2350;
        }


        @Test
        @DisplayName("정상 케이스")
        void createProduct_Normal() {
            // given
            ProductRequestDto requestDto = new ProductRequestDto(
                    title,
                    image,
                    link,
                    lprice
            );

            // when
            Product product = new Product(requestDto, userId);

            // then
            assertNull(product.getId());
            assertEquals(userId, product.getUserId());
            assertEquals(title, product.getTitle());
            assertEquals(image, product.getImage());
            assertEquals(link, product.getLink());
            assertEquals(lprice, product.getLprice());
            assertEquals(0, product.getMyprice());
        }
        

        @Nested
        @DisplayName("실패 케이스")
        class FailCases {
            @Nested
            @DisplayName("회원 Id")
            class userId {
                @Test
                @DisplayName("null")
                void fail1() {
                    // given
                    userId = null;

                    ProductRequestDto requestDto = new ProductRequestDto(
                            title,
                            image,
                            link,
                            lprice
                    );

                    // when
                    Exception exception = assertThrows(IllegalArgumentException.class, () -> {
                        new Product(requestDto, userId);
                    });

                    // then
                    assertEquals("회원 Id 가 유효하지 않습니다.", exception.getMessage());     // 우리가 설정한 메세지와도 같아야함
                }

                @Test
                @DisplayName("마이너스")
                void fail2() {
                    // given
                    userId = -100L;

                    ProductRequestDto requestDto = new ProductRequestDto(
                            title,
                            image,
                            link,
                            lprice
                    );

                    // when
                    Exception exception = assertThrows(IllegalArgumentException.class, () -> {
                        new Product(requestDto, userId);
                    });

                    // then
                    assertEquals("회원 Id 가 유효하지 않습니다.", exception.getMessage());
                }
            }
        }
    }
}

 

 

왼쪽 재생 버튼을 클릭하여 전체 혹은 각 메서드 별로 테스트 진행이 가능하다.

실행하면 아래 계층구조로 결과를 확인할 수 있다.

 

Mockito mock 을 사용한 단위 테스트

  • 실제 객체와 겉만 같은 객체. 동일한 클래스명, 함수명
  • 실제 DB 작업은 하지 않음
  • mock 객체를 반환하여 서비스로직만을 배타적으로 테스트 가능

일단 먼저 그래들 dependencies에 추가해줍니다.

// 테스트 케이스를 위해 가짜 객체(Mock object)를 생성
testImplementation 'org.mockito:mockito-core:4.8.0'
testImplementation 'org.mockito:mockito-junit-jupiter:4.8.0'

class ProductServiceTest {
    @Mock 		// 모킹할 객체 표기
    ProductRepository productRepository;

    @InjectMocks 	// 모킹한 객체 주입 (가짜 객체가 들어감)
    ProductService productService;
    
    @Test
    @DisplayName("관심 상품 희망가 - 최저가 이상으로 변경")
    void updateProduct_Success() {
    
    ...
    
      // 가짜 객체를 만들어 서비스 로직만 테스트 가능
      ProductRequestDto requestProductDto = new ProductRequestDto(  ...  );
        
      when(productRepository.findByIdAndUserId(productId, userId)) // 모킹한 객체가 특정 조건으로 호출되면 
                .thenReturn(Optional.of(product));		   // 일괄적으로 다음과 같이 동작하도록 지정

      assertDoesNotThrow( () -> {
            productService.updateProduct(productId, requestMyPriceDto, user);
      });
    }
 }
728x90