토비의 스프링 정리

토비의 스프링 - 6.8 트랜잭션 지원 테스트

ksb-dev 2022. 10. 20. 22:26

6.8.1 선언적 트랜잭션과 트랜잭션 전파 속성

  • REQUIRED로 전파 속성을 설정하면 앞에서 트랜잭션이 진행되고 있으면 새로운 트랜잭션을 시작하지 않고, 기존의 트랜잭션에 참여를 함
  • REQUIRED 전파 속성을 가진 메소드를 결합해 다양한 크기의 트랜잭션 작업을 만들 수 있음
  • A 메소드에서 B 메소드를 호출하는데, A와 B 작업이 모두 완료되어야만할 때, REQUIRED 전파 속성을 사용해야 함

  • AOP를 이용해 코드 외부에서 트랜잭션 기능을 부여해주는 방법을 선언적 트랜잭션(Declarative Transaction)이라 함
  • 대조적으로, TransactionTemplate나 개별 데이터 트랜잭션 API를 사용해 직접 코드 안에서 사용하는 방법을 프로그램에 의한 트랜잭션(Programmatic Transaction)이라 함
  • 특수한 상황을 제외하고, 선언적 트랜잭션 방법을 사용하는 것을 추천함

6.8.2 트랜잭션 매니저와 트랜잭션 동기화

  • AOP 덕분에 트랜잭션 부가기능을 간단히 애플리케이션 전반에 적용할 수 있음
  • AOP의 중요한 기술적 기반은 트랜잭션 추상화
  • 트랜잭션 추상화의 핵심은 트랜잭션 매니저트랜잭션 동기화
  • PlatformTransactionManager 인터페이스를 구현한 트랜잭션 매니저를 통해 구체적인 트랜잭션 기술의 종류에 상관없이 일관된 트랜잭션 제어가 가능
  • TransactionSynchronization을 사용하는 트랜잭션 동기화는 같은 DB Connection을 공유하게 함
  • 즉, 트랜잭션 동기화 기술은 DB Connection을 공유하는 특성을 이용하여 트랜잭션 전파 속성에 따라 참여할 수 있도록 함

6.8.3 트랜잭션 매니저를 이용한 트랜잭션용 트랜잭션 제어

  • 테스트와 특별한 이유가 있다면 트랜잭션 매니저를 통해 트랜잭션을 제어할 수 있음
  • 간단한 테스트 메소드
public class UserServiceTest {
		...
    @Test
    public void transactionSync(){
        userService.deleteAll();
        userService.add(users.get(0));
        userService.add(users.get(1));
    }
}
  • 간단한 테스트 메소드에서 실행되는 트랜잭션은 3개임
  • 각 메소드마다 트랜잭션이 독립적으로 작용함
  • 트랜잭션 매니저를 사용하여 세 개의 메소드를 하나로 묶을 수 있음
  • 메소드가 실행하기 전에 트랜잭션 매니저에서 강제로 트랜잭션을 시작하면 됨
public class UserServiceTest {
    ...
    @Test
    public void transactionSync(){
        //기본 값 사용
        DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
        TransactionStatus txStatus = transactionManager.getTransaction(txDefinition);

        userService.deleteAll();
        userService.add(users.get(0));
        userService.add(users.get(1));

        transactionManager.commit(txStatus);
    }
}

6.8.4 트랜잭션 동기화 검증

  • 트랜잭션 속성을 변경해서 검증할 것임
public class UserServiceTest {
    ...
    @Test
    public void transactionSync(){
        //기본 값 사용
        DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
        txDefinition.setReadOnly(true);
        ...
    }
}
  • 트랜잭션 이라면 롤백도 가능해야 함
public class UserServiceTest {
    ...
    @Test
    public void transactionSync(){
        userDao.deleteAll();
        assertThat(userDao.getCount(), is(0));

        //기본 값 사용
        DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
        TransactionStatus txStatus = transactionManager.getTransaction(txDefinition);

        userService.add(users.get(0));
        userService.add(users.get(1));
        assertThat(userDao.getCount(), is(2));

        transactionManager.rollback(txStatus);

        assertThat(userDao.getCount(), is(0));
    }
}

6.8.5 롤백 테스트

  • 롤백 테스트란, 테스트가 끝나면 무조건 롤백을 하는 테스트를 말함
  • 복잡한 데이터를 바탕으로 동작하는 통합 테스트에서 DB의 상태가 중요할 수 있음
  • 테스트 수행으로 데이터 추가, 삭제 등이 발생 하면 다른 통합 테스트에 영향을 줄 수 있음
  • 때문에 테스트 수행 시 마지막에 롤백을 해서 테스트 실행 전 상태로 되돌림
  • 적절한 격리 수준만 보장하면 동시에 여러 개의 통합 테스트를 진행할 수 있음
public class UserServiceTest {
    ...
    @Test
    public void transactionSync() {
        DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
        TransactionStatus txStatus = transactionManager.getTransaction(txDefinition);
        try {
            userService.deleteAll();
            userService.add(users.get(0));
            userService.add(users.get(1));
        } finally {
            transactionManager.rollback(txStatus);
        }
    }
}

6.8.6 테스트를 위한 트랜잭션 애노테이션

  1. @Transactional
  2. @Rollback
  3. @TransactionConfiguration
  4. @NotTransactional과 Propagation.NEVER

6.8.7 @Transactional

  • @Transactional을 사용하면 트랜잭션 경계설정이 자동 설정됨
  • 디폴트는 REQUIRED

6.8.8 @Rollback

  • @Transactional을 사용하는 애플리케이션 클래스와 테스트 클래스의 디폴트 속성은 동일함
  • 단, 테스트 클래스는 테스트 수행 후 자동으로 롤백
  • 테스트 수행 후 롤백을 원하지 않을 때 @Rollback 사용
  • @Rollback의 기본값은 true이기 때문에 false로 선언해야 롤백되지 않음
public class UserServiceTest {
    ...
    @Test
    @Transactional
    @Rollback(false)
    public void transactionSync() {
        ...
    }
}

6.8.9 @TransactionConfiguration

  • @Rollback은 메소드 레벨에서만 사용 가능함
  • 클래스 레벨에서 적용하기 위해 @TransactionConfiguration을 사용해야 함
  • @Rollback과 동일하게 false로 선언해야 롤백되지 않음
  • @TransactionConfiguration을 사용한 클래스에서, 특정 메소드는 롤백하고 싶을 경우 해당 메소드에만 @Rollback 사용하면 됨
  • 클래스보다 메소드가 우선순위가 더 높기 때문임
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/applicationContext.xml")
@Transactional
@TransactionConfiguration(defaultRollback = false) //롤백되지 않음
public class UserServiceTest {
    @Test
    @Rollback //롤백됨
    public void add(){}
}

6.8.10 @NotTransactional과 Propagation.NEVER

  • @Transactional이 있는 클래스에서 특정 메소드를 트랜잭션 동작하고 싶지 않을 때 사용
  • @NotTransactional는 deprecated 되었음
  • 대신, Propagation.NEVER사용
@Transactional(propagation = Propagation.NEVER)

6.8.11 효과적인 DB 테스트

  • 테스트 내에서 트랜잭션을 제어할 수 있는 네 가지 애노테이션을 잘 활용하면 통합 테스트를 만들 때 편리함
  • 단위 테스트와 통합 테스트는 클래스로 구분하는게 좋음
  • DB가 사용되는 통합 테스트는 롤백 테스트로 만드는 것이 좋음