[인프런 워밍업 클럽 2기 - BE] 2주차 발자국

[인프런 워밍업 클럽 2기 - BE] 2주차 발자국

image

이 블로그는 정보근님의 입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기 강의 기반으로 코드작성과 코드설명을 적었습니다

 

이번 2주차 발자국 내용은 spring 테스트 코드에 대한 내용이 대부분이다.

아무래도 Spring에서 가장 중요한 요소는 테스트 코드라고 생각되어 강의를 들으면서 썼던 TEST 코드에 대해 하나하나씩 파헤쳐 볼 예정이다 !


package com.hyup.portfolio.domain.repository
	
	import com.hyup.portfolio.domain.entity.Project
	import org.springframework.data.jpa.repository.JpaRepository
	import java.util.Optional
	
	interface ProjectRepository : JpaRepository<Project, Long> {
	    fun findAllByIsActive(isActive: Boolean): List<Project>
	
	    override fun findById(id: Long): Optional<Project>
}

 

위 코드에서 궁금한 내용은 ?

[ QUESTION ] findById를 왜 override를 해서 사용하는 이유

 

-> JpaRepository가 이미 제공하는 메서드를 다시 선언하기 때문에 오버라이드를 해야함
-> override를 활용해서 재정의 하면 기능적으로 안좋다.

[ WHY ?] override를 활용해서 재정의 하면 기능적으로 안좋은 이유


1. 기본 동작의 일관성 문제

  • JpaRepository에서 제공하는 findById는 기본적인 CRUD 동작을 안정적이고 효율적으로 처리하는데,
    이를 재정의하면 예상치 못한 문제를 발생시킬 수 있음.

  • 다른 개발자들이 코드를 이해할 때, 어려움을 안겨줄 수 있음


2. 유지보수성 저하

  • JPA의 업데이트나 변경 사항이 발생했을 때 호환성 문제가 발생할 가능성이 높음

  1. 기능적 안정성 감소

     

    • Spring Data Jpa는 트랜잭션 관리, 데이터베이스 연결 관리, 성능 최적화 등을 내부적으로 처리. 이를 재정의 하면 내부적으로 처리되던 여러 최적화나 기능이 무시될 수 있음

 

[ SOLVE ] 그럼 override를 활용하지 않고 어떤 방법을 사용할 수 있나?

  1. CUSTOM Repository 사용

    interface ProjectRepository : JpaRepository<Project, Long> {
    	    fun findAllByIsActive(isActive: Boolean): List<Project>
    		
    	    override fun findById(id: Long): Optional<Project>
    }
interface CustomProjectRepository {
    fun findByIdCustom(id: Long): Optional<Project>
}

 

  1. 쿼리 메서드 활용

    interface ProjectRepository : JpaRepository<Project, Long> {
        fun findAllByIsActive(isActive: Boolean): List<Project>
    
        fun findByIdAndIsActive(id: Long, isActive: Boolean): Optional<Project>
    }

@DataJpaTest 메서드

@DataJpaTest 메서드를 타고 들어가면 @Transactional 이라는 어노테이션 존재

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface 
# DataJpaTest 어노테이션이 포함하고 있는 어노테이션 

이중 @Transactional 이라는 어노테이션은 테스트 메소드 하나를 하나의 트랜잭션으로 보고 메소드가 종료될 때 그 트랜잭션에서 발생한 작업들을 모두 롤백해줌

 

[ QUESTION ] 트랜잭션에서 발생한 작업들을 모두롤백해야 하는 이유

[ ANSWER ]

  1. 테스트 간 독립성 보장

테스트는 독립적으로 수행되어야 하고, 하나의 테스트가 다른 테스트에 영향을 주지 않아야 함. 트랜잭션을 롤백하면, 테스트 내에서 발생한 모든 데이터 변경이 원래 상태로 돌아가기 때문에 데이터베이스 상태가 항상 초기 상태로 유지

  1. 데이터 일관성 유지

테스트가 끝난 후에도 트랜잭션을 롤백하지 않으면, 테스트 중에 삽입되거나 수정된 데이터가 데이터베이스에 남아 있게 된다. 이렇게 되면 이후의 테스트나 실제 어플리케이션 실행에 영향을 미쳐, 데이터의 일관성이 깨질 수 있음

  1. 테스트 성능 향상

테스트 중 데이터베이스에 많은 데이터를 삽입하거나 수정하는 경우, 이를 실제로 영구 저장하는 것보다 트랜잭션을 롤백하는 것이 성능 면에서 유리.


인스턴스 생명주기

  1. PER_METHOD (기본값)

  • 각 테스트 메서드마다 새로운 인스턴스가 생성

  • 각 테스트 메서드는 독립적인 상태 유지

  • 테스트마다 새로운 인스턴스가 생성되므로, 테스트 클래스의 상태를 공유할 수 없고 테스트 간의 의존성도 없어야 함.

@TestInstance(TestInstance.Lifecycle.PER_METHOD) // 생략 가능 (기본 값)
class ExampleTest {
    
    @BeforeEach
    void setUp() {
        // 테스트 메서드 실행 전 호출됨
    }
    
    @Test
    void testA() {
        // testA 실행 시 새로운 인스턴스가 생성됨
    }
    
    @Test
    void testB() {
        // testB 실행 시 또 다른 새로운 인스턴스가 생성됨
    }
}

PER_METHOD는 각 테스트마다 독립적으로 실행되어서 서로의 상태에 영향을 주지 않지만 상태를 공유하는 것이 불가능하다.

 

2. PER_CLASS

  • 하나의 인스턴스만 생성되고, 모든 테스트 메서드에서 이 인스턴스가 재사용된다.

  • @BeforeAll을 사용하면 클래스 전체에서 한 번만 실행되므로 성능을 향상시킬 수 있음

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ExampleTest {

    @BeforeAll
    void initAll() {
        // 모든 테스트 메서드 실행 전 한 번만 호출됨
    }

    @Test
    void testA() {
        // testA 실행 시 동일한 인스턴스가 사용됨
    }

    @Test
    void testB() {
        // testB 실행 시에도 동일한 인스턴스가 사용됨
    }
}

PER_CLASS는 상태를 공유할 수 있어 성능이 향상되지만 테스트 간 인스턴스 상태가 공유되므로, 테스트 간 의존성이 발생함.


@BeforeAll, @AfterAll

  • @BeforeAll : PER_CLASS에서만 사용되며, 모든 테스트 전에 한 번만 실행

  • @AfterAll : PER_METHOD와 PER_CLASS 모두에 사용되고 모든 테스트 전에 한 번만 실행 됨

[QUESTION] @BeforeAll 이 PER_CLASS에서만 사용되는 이유, @AfterAll은 PER_METHOD, PER_CLASS 모두 사용가능한 이유

[ANSWER]

@BeforeAll은 클래스 수준에서 한 번만 실행되는 메서드. 즉 테스트 클래스의 모든 테스트 메서드가 실행되기 전에 딱 한 번 실행됨

PER_CLASS에서는 테스트 클래스에 대해 하나의 인스턴스만 생성되므로, @BeforeAll 메서드가 클래스 인스턴스에서 한 번 실행되고, 이후의 모든 테스트가 동일한 인스턴스를 사용

@AfterAll 은 모든 테스트 메서드가 실행된 후에 한 번만 실행되는 메서드이다. 여기서 PER_CLASS에서는 클래스 인스턴스가 하나만 생성되므로, 모든 테스트가 끝나고 한번만 호출되면 된다.

PER_METHOD에서는 각 테스트마다 새로운 인스턴스가 생성되지만, @AfterAll이 테스트 메서드가 모두 끝난 후에 한 번 호출되도록 관리할 수 있다.


미션 - REST API 설계

 

이번 미션에서는 REST API 설계하는 미션이 주어졌다.

 

HTTP 주요 메서드 정리 (GET / POST / PUT / PATCH / DELETE)

  • GET : 리소스 조회

  • POST : 요청 데이터 처리, 주로 등록에 사용

  • PUT : 리소스를 대체, 즉 덮어쓰기 수행

  • PATCH : 리소스 부분 변경

  • DELETE : 리소스 삭제



이번 미션과 발자국을 하면서 백엔드 개발자가 HTTP 주요 메서드에 대해 집중해야 된다는 것을 알 수 있었다. api를 설계할려면 어쩔수 없이 각 HTTP 메서드에 대해 알아야 URL을 설정할 때 어떻게 만들어야 되는지를 알 수 있기 때문이다. 이번주 는 개인 일정이 너무 많아서 아쉽게도 진도를 다 따라가지 못했다. 발자국은 만들어야 되기 때문에 여기서 내가 가장 공부해볼만한 것이 TEST 코드에 대해서 조사를 하는 것이기 때문에 열심히 하였다.

GET, POST, PUT, PATCH, DELETE 메서드에 대해서 조금이라도 공부할 수 있어서 좋았고, 따로 공부해서 블로그에 올려야 겠다고 생각하였다!

댓글을 작성해보세요.

채널톡 아이콘