토비의 스프링 | 6장 AOP - Transaction (학습)

주싱·2022년 10월 24일
0

토비의 스프링

목록 보기
20/30
post-custom-banner

토비의 스프링 6장 AOP를 읽고 배우고 학습한 것들을 정리합니다. 특별히 Tansaction과 관련도니 부분을 따로 정리합니다. 트랜잭션을 다루는 개념들을 읽다보니 섬세하게 데이터를 다루는 일에 대한 호기심이 생겨납니다. 처음있는 일입니다. 지금까지는 섬세하게 외부 API 서버를 다루는 일을 했고 대게 하드웨어와 연관된 일이 많았는데 이제 순수하게 데이터를 다루는 일도 점차 해보고 싶습니다.

1. 트랜잭선 속성

아래 트랜잭션 경계설정 코드에서 DefaultTransactionDefinition를 사용해 트랜잭선 속성을 설정할 수수 있다. 트랜잭션 설정에는 아래의 4가지 속성이 있다.

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
    // DefaultTransactionDefinition : 디폴트 트랜잭션 속성 사용 (트랜잭션 전파, 격리수준, read-only, 타임아웃)
    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
        Object ret = invocation.proceed();
        transactionManager.commit(status);
        return ret;
    } catch (RuntimeException e) { // 롤백을 수행할 예외를 설정
        transactionManager.rollback(status);
        throw e;
    }
}

트랜잭션 전파(transaction propagation)

트랜잭션 전파란 트랜잭션의 경계에서 이미 진행 중인 트랜잭션이 있을 때 또는 없을 때 어떻게 동작할 것인가를 결정하는 방식을 말한다.

  • PROPAGTION_REQUIRED 진행 중인 트랜잭션이 없으면 새로 생성하고 진행 중인 트랜잭션이 있으면 이에 참여한다.
  • PROPAGTION_REQUIRED_NEW 앞에서 시작된 트랜잭션이 있든 없든 항상 새로운 트랜잭션을 독자적으로 생성한다.
  • PROPAGTION_NOT_SUPPORTED 진행 중인 트랜잭션이 있어도 무시한다. 특정 클래스의 모든 메서드에 트랜잭션을 클래스 단위로 적용하고 일부 메서드만 제외하는 경우 유용하게 사용된다.

격리수준(isolation level)

서버환경에서는 여러 개의 트랙잭션이 동시에 진행될 수 있다. 가능하다면 모든 트랜잭션이 순차적으로 진행되서 다른 트랜잭션의 작업에 독립적인 것이 좋겠지만, 그러자면 성능이 크게 떨어질 수 밖에 없다. 따라서 적절하게 격리수준을 조정해서 가능한 많은 트랜잭션을 동시에 진행시키면서도 문제가 발생하지 않게 하는 제어가 필요하다. DefaultTransactionDefinition에 설정된 격리수준은 ISOLATION_DEFAULT이다. 이는 DataSource에 설정되어 있는 디폴트 격리수준을 그대로 따른다는 뜻이다.

제한시간(timeout)

트랜잭션을 수행하는 제한시간을 설정할 수 있다. DefaultTransactionDefinition의 기본 설정은 제한시간이 없다는 것이다. 제한시간은 트랜잭션을 직접 시작할 수 있는 PROPAGATION_REQUIRED 또는 PROPAGATION_REQUIRED_NEW에서만 의미가 있다. 트랜잭션이 처음 시작되는 경우가아니라면 적용되지 않는다.

읽기전용

읽기전용으로 설정해두면 트랜잭션 내에서 데이터를 조작하는 시도를 막아줄 수 있다. 읽기 전용 트랜잭션에서 데이터 조작 시도가 있으면 예외가 발생하게 된다. 트랜잭션이 처음 시작되는 경우가아니라면 적용되지 않는다.

2. 트랜잭션 인터셉터와 트랜잭션 속성

메서드별로 다른 트랜잭션 정의를 적용하려면 어드바이스의 기능을 확장해야 한다. 메서드 이름 패턴에 따라 다른 트랜잭션 정의가 적용되도록 만드는 것이다.

TransactionInterceptor

  • 스프링에서 제공한다. TransactionAdvice 처럼 직접 구현할 필요가 없다.
  • PlatformTransactionManager와 Properties 타입의 두 가지 프로퍼티를 갖는다.
  • Properties는 트랜잭션 속성을 정의한 프로퍼티다. 위에서 살펴본 4가지 트랜잭션 설정과 함께 rollbackOn()이라는 설정을 추가로 제공한다.
  • 우리가 직접 구현했던 TransactionAdvice 코드를 다시 살펴보면 변경될 수 있는 코드가 2군데 존재한다. 하나는 트랙잭션 속성을 설정하는 DefaultTransactionDefinition 객체를 다루는 부분이고, 나머지 하나는 어떤 어떤 예외가 발생하면 롤백을 수행할 지 결정하는 부분이다. rollbackOn() 메서드를 사용해 어떤 예외는 롤백을 하거나, 롤백을 하지 않을지 설정할 수 있다.
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
    // DefaultTransactionDefinition : 디폴트 트랜잭션 속성 사용 (트랜잭션 전파, 격리수준, read-only, 타임아웃)
    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
        Object ret = invocation.proceed();
        transactionManager.commit(status);
        return ret;
    } catch (RuntimeException e) { // 롤백을 수행할 예외를 설정
        transactionManager.rollback(status);
        throw e;
    }
}

TransactionInterceptor 기본 롤백 전략

  • 런타임 예외가 발생하면 트랜잭션은 롤백한다.
  • 체크 예외가 발생하면 이것을 예외상황으로 해석하지 않고 일종의 비지니스 로직에 따른, 의미가 있는 리턴 방식의 한 가지로 인식해서 트랜잭션을 커밋한다.
  • 그러나 이런 예외처리 기본 원칙을 따라지 않는 예외적인 케이스에서는 rollbackOn() 설정을 통해 기본 원칙과 다른 예외처리가 가능하다.

메서드 이름 패턴을 이용한 트랜잭션 속성 지정

  • TransactionInterceptor의 Properties 타입 transactionAttributes 프로퍼티는 메서드 패턴과 속성을 키와 값으로 갖는 컬렉션이다. 트랜잭션 속성은 다음과 같은 문자열로 정의할 수 있다.
PROPAGATION_NAME, ISOLATION_NAME, readOnly, timeout_NNNN, -Exception1, +Exception2
  • 트랜잭션 전파 항목만 필수이고 나머지는 생략가능하다. 생략하면 모두 DefaultTransactionDefinition에 설정된 디폴트 속성이 부여된다. 순서는 바꿔도 상관없다.
  • +, -로 시작하는 건 기본 원칙을 따르지 않는 예외를 정의해 주는 것이다.모든 런타임 예외는 롤백되는 것이 기본이지만 +XxxRuntimeException이라고 해주면 런타임 예외라도 커밋하게 만들 수 있다. 반대로 -를 붙여서 넣어주면 체크 예외는 모두 커밋하는 것이 기본이지만 트랜잭션 롤백 대상이 되도록 할 수도 있다.
  • 이렇게 속성을 하나의 문자열로 표현하게 만든 이유는 트랜잭션 속성을 메서드 패턴에 따라 여러 개를 지정해야 하는데 일일이 중첩된 태그와 프로퍼티로 설정하게 만들면 번거롭기 때문이다.
  • 트랜잭션 속성 중 readOnly나 timeout 등은 트랜잭션이 처음 시작될 때가 아니라면 적용되지 않는다.
<bean id="transactionAdvice" class="org.springframework.transaction.interceptor.TransactionInterceptor">
  <property name="transactionManager" ref="transactionManager"/>
  <property name="transactionAttributes">
    <props>
      <prop key="get*">PROPAGATION_REQUIRED, readOnly, timeout_30</prop>
      <prop key="upgrade*">PROPAGATION_REQUIRED_NEW, ISOLATION_SERIALIZABLE</prop>
      <prop key="*">PROPAGATION_REQUIRED</prop>
    </props>
  </property>
</bean>

tx 네임스페이스를 이용한 설정 방법

TransactionInterceptor 타입의 어드바이스 빈과 TransactionAttribute 타입의 속성 정보도 tx 스키마의 전용 태그를 이용해 정의할 수 있다.

<beans ...
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
			 ... 
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"
>

<aop:config>
  <aop:advisor advice-ref="transactionAdvice" pointcut="bean(*Service)" />
</aop:config>
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
  <tx:attributes>
    <tx:method name="get*" propagation="REQUIRED" read-only="true" />
    <tx:method name="*" propagation="REQUIRED"/>
  </tx:attributes>
</tx:advice>

3. 포인트컷과 트랜잭션 속성의 적용 전략

트랜잭션 부가기능을 적용할 후보 메서드를 선정하는 작업은 포인트컷에 의해 진행된다. 그리고 어드바이스의 트랜잭션 전파 속성에 따라서 메서드별로 트랜잭션의 적용 방식이 결정된다. aop와 tx 스키마의 전용 태그를 사용한다면 애플리케이션의 어드바이저, 어드바이스, 포인트컷 기본 설정 방법은 바뀌지 않을 것이다. 이제 expression 애트리뷰트에 넣는 포인트컷 표현식과 <tx:attributes>로 정의하는 트랜잭션 속성만 결정하면된다. 포인트컷 표현식과 트랜잭션 속성을 정의할 때 따르면 좋은 몇 가지 전략을 생각해 보자.

트랜잭션 포인트컷 표현식은 타입 패턴이나 빈 이름을 이용한다

  • 트랜잭션용 포인트컷 표현식에는 메서드나 파라미터, 예외에 대한 패턴을 정의하지 않는 것이 바람직하다. 트랜잭션의 경계로 삼을 클래스들이 선정됐다면, 그 클래스들이 모여 있는 패키지를 통째로 선택하거나 클래스 이름에서 일정한 패턴을 찾아서 표현식으로 만들면 된다.
  • 가능하면 클래스보다는 인터페이스 타입을 기준으로 타입 패턴을 적용하는 것이 좋다. 인터페이스는 클래스에 비해 변경 빈도가 적고 일정한 패턴을 유지하기 쉽기 때문이다.
  • 스프링의 빈 이름을 이용하는 bean() 표현식을 사용하는 방법도 고려해볼 만하다. 이는 클래스나 인터페이스 이름에 일정한 규칙을 만들기 어려운 경우에 유용하다.

공통된 메서드 이름 규칙을 통해 최소한의 트랜잭션 어드바이스와 속성을 정의한다

프록시 방식 AOP는 같은 타깃 오브젝트 내의 메서드를 호출할 때는 적용되지 않는다

  • 타겟 객체가 자가 자신의 메서드를 호출할 때는 프록시를 통한 부가기능의 적용이 일어나지 않는다.
  • 같은 객체 안에서의 호출은 새로운 트랜잭션 속성을 부여하지 못한다는 사실을 의식하고 개발할 필요가 있다.

4. 트랜잭션 속성 적용

트랜잭션 속성과 그에 따른 트랜잭션 전략을 UserService에 적용해보자. 지금까지 살펴봤던 몇 가지 원칙과 전략에 따라 작업을 진행할 것이다.

트랜잭션 경계설정의 일원화

  • 트랜잭션 경계설정의 부가기능을 여러 계층에 중구난방으로 적용하는 건 좋지 않다. 일반적으로 특정 계층의 경계를 트랜잭션 경계와 일치시키는 것이 바람직하다. 비지니스 로직을 담고 있는 서비스 계층 객체의 메서드가 트랜잭션 경계를 부여하기에 가장 적절한 대상이다.
  • 서비스 계층을 트랜잭션이 시작되고 종료되는 경계로 정했다면, 테스트와 같은 특별한 이유가 아니고는 다른 계층이나 모듈에서 DAO에 직접 접근하는 것은 차단해야 한다.
  • 트랜잭션은 보통 서비스 계층의 메서드 조합을 통해 만들어지기 때문에 DAO가 제공하는 주요 기능은 서비스 계층에 위임 메서드를 만들어둘 필요가 있다. 가능하면 다른 모듈의 DAO에 접근할 때는 서비스 계층을 거치도록 하는 게 바람직하다. 그래야 UserService의 add()처럼 부가 로직을 적용할 수도 있고, 트랜잭션 속성도 제어할 수 있기 때문이다.
  • 아키텍처를 단순하게 가져가면 서비스 계층과 DAO가 통합될 수도 있다. 비즈니스 로직이 거의 없고 단순 DB 입출력과 검색 수준의 조회가 전부라면 서비스 계층을 없애고 DAO를 트랜잭션 경계로 만드는 것이다. 하지만 비지니스 로직을 독자적으로 두고 테스트하려면 서비스 계층을 만들어 사용해야 한다.

소스코드

UserService에 추가된 메서드

public interface UserService {
		// 신규 추가 메서드들 
    void upgradeLevels(); 
    User get(String id);
    List<User> getAll();
    void deleteAll(); 
		
		// 기존 인터페이스 
    void add(User user);
    void update(User user);
}

추가 메서드 구현

@Override
public User get(String id) { return userDao.get(id); }
@Override
public List<User> getAll() { return userDao.getAll(); }

@Override
public void deleteAll() { userDao.deleteAll(); }

@Override
public void update(User user) { userDao.update(user); }

서비스 빈에 적용되는 포인트 컷 표현식

<aop:config>
  <aop:advisor advice-ref="transactionAdvice" pointcut="bean(*Service)" />
</aop:config>

트랜잭션 속성을 가진 트랜잭션 어드바이스 등록

<bean id="transactionAdvice" class="org.springframework.transaction.interceptor.TransactionInterceptor">
  <property name="transactionManager" ref="transactionManager"/>
  <property name="transactionAttributes">
    <props>
      <prop key="get*">PROPAGATION_REQUIRED, readOnly</prop>
      <prop key="*">PROPAGATION_REQUIRED</prop>
    </props>
  </property>
</bean>

tx 스키마의 태그를 이용한 트랜잭션 어드바이스 등록

<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
  <tx:attributes>
    <tx:method name="get*" propagation="REQUIRED" read-only="true" />
    <tx:method name="*" propagation="REQUIRED"/>
  </tx:attributes>
</tx:advice>

읽기 전용 메서드에 쓰기 작업을 추가한 테스트용 클래스

static class TestUserServiceImpl extends UserServiceImpl {
    @Override
    public List<User> getAll() {
        for (User user : super.getAll()) {
            update(user);
        }
        return List.of();
    }
}

읽기전용 속성 테스트

@Test(expected = UncategorizedSQLException.class)
public void readOnlyTransactionAttribute() {
    users.forEach(user -> userDao.add(user));
    testUserService.getAll();
}

5. 트랜잭션 어노테이션

설정파일에서 패턴으로 분류 가능한 그룹을 만들어서 일괄적으로 속성을 부여하는 대신에 직접 타깃에 트랜잭션 속성정보를 가진 애노테이션을 지정하는 방법이다.

트랜잭션 어노테이션

@Transactional

  • 메서드와 클래스, 인터페이스에 적용될 수 있다.
  • 스프링은 @Transactional이 부여된 모든 객체를 자동으로 타겟 오브젝트로 인식한다.
  • 이때 포인트컷은 TransactionAttributeSourcePointcut이 사용되고 어노테이션이 부여된 모든 객체를 찾아서 포인트컷의 선정 결과로 돌려준다.
  • 트랜잭션 속성은 모든 항목을 엘리먼트로 지정할 수 있고 모두 디폴트 값을 가짐으로 생략할 수 있다.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
	@AliasFor("transactionManager")
	String value() default "";
	@AliasFor("value")
	String transactionManager() default "";
	String[] label() default {};
	Propagation propagation() default Propagation.REQUIRED;
	Isolation isolation() default Isolation.DEFAULT;
	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
	String timeoutString() default "";
	boolean readOnly() default false;
	Class<? extends Throwable>[] rollbackFor() default {};
	String[] rollbackForClassName() default {};
	Class<? extends Throwable>[] noRollbackFor() default {};
	String[] noRollbackForClassName() default {};

}

트랜잭션 속성을 이용하는 포인트컷

  • @Transactional 어노테이션을 사용하면 포인트컷과 트랜잭션 속성을 애노테이션 하나로 지정할 수 있다.
  • @Transactional은 메서드마다 다르게 설정할 수도 있음으로 매우 유연한 트랜잭션 속성 설정이 가능하다.
  • 그러나 메서드 마다 모두 속성을 지정하면 유연한 제어는 가능하겠지만 코드는 지저분해지고 동일한 속성 정보를 가진 애노테이션을 반복적으로 메서드마다 부여해주는 바람직하지 못한 결과를 가져올 수 있다.

대체 정책

  • 스프링은 트랜잭션 기능이 부여될 위치인 타겟 객체의 메서드부터 시작해서 클래스, 인터페이스 순으로 @Transactional 어노테이션이 존재하는지 확인하고 우선 적용한다.
  • 메서드가 여러개라면 클래스 레벨에 공통 속성을 부여하고 공통 속성을 따르지 않는 메서드가 있다면 추가로 @Transactional을 부여해 줄 수 있다.
  • 기본적으로 @Transactional 적용 대상은 클라이언트가 사용하는 인터페이스가 정의한 메서드이므로 @Transactional도 타겟 클래스보다는 인터페이스에 두는 것이 바람직하다.
  • 다만 인터페이스를 사용하는 타겟에만 트랜잭션을 적용하겠다는 확신이 있다면 타깃 클래스에 직접 어노테이션을 부여할 수도 있다.
  • 그러나 인터페이스에 @Transactional을 두면 구현 클래스가 바뀌더라도 트랜잭션 속성을 유지할 수 있다는 장점이 있다.

트랜잭션 어노테이션 사용을 위한 설정

<tx:annotation-driven/>

6. 트랜잭션 어노테이션 적용

장점

  • 클래스, 빈, 메서드 이름에 일관된 패턴을 만들어 적용하고 이를 활용해 포인트컷과 트랜잭션 속성을 지정하는 것보다는 단순하게 트랜잭션이 필요한 타입 또는 메서드에 직접 어노테이션을 부여하는 것이 훨씬 편리하고 코드를 이해하기도 좋다.

단점과 주의

  • 트랜잭션 적용 대상을 손쉽게 파악할 수 없고, 사용 정책을 잘 만들어두지 않으면 무분별하게 사용되거나 자칫 빼먹을 위험도 있다. 트랜잭션이 적용되지 않았다는 사실은 파악하기가 쉽지 않다. 일반적으로는 트랜잭션이 적용되지 않았다고 기능이 동작하지 않는 것도 아니므로 예외적인 상황이 발생해서 롤백이 필요한 시점이 되서야 비로소 이상하다는 걸 느끼고 트랜잭션 적용 여부를 확인해보게 된다. 그래서 주의가 요구되고 특정 프로젝트 시점에는 트랜잭션이 정상적으로 적용되었는지 확인하는 과정이 반드시 요구된다.
  • 일부 데이터 액세스 기술은 트랜잭션이 시작되지 않으면 아예 DAO에서 예외가 발생하기도 한다.
@Transactional
public interface UserService {
    void upgradeLevels();
    void add(User user);
    @Transactional(readOnly = true)
    User get(String id);
    @Transactional(readOnly = true)
    List<User> getAll();
    void deleteAll();
    void update(User user);
}

7. 트랜잭션 지원 테스트

두 가지 방법

AOP를 이용해 코드 외부에서 트랜잭션의 기능을 부여해주고 속성을 지정할 수 있는 방법을 선언적 트랜잭션(declarative transaction)이라고 한다. 반대로 코드 안에서 트랜잭션 API를 사용해 직접 트랜잭션 기능을 부여하는 방법을 프로그램에 의한 트랙잭션(programmatic transaction)이라고 한다.

하나. Programmatic Transaction 테스트

트랜잭션 동기화 검증

  • 트랜잭션 속성 중에서 읽기전용과 제한시간 등은 처음 트랜잭션이 시작될 때만 적용되고 그 이후에 참여하는 메서드의 속성은 무시된다. 즉 deleteAll()의 트랜잭션 속성은 쓰기 가능으로 되어 있지만 앞에서 시작된 트랜잭션이 읽기전용이라면 deleteAll()의 모든 작업도 읽기전용 트랜잭션이 적용된다. 이 점을 이용해 트랜잭션 설정 적용 여부를 아래와 같이 테스트 해본다.
  • JdbcTemplate의 메서드들도 트랜잭션 전파 속성이 REQUIRED인 것처럼 동작한다고 볼 수 있다.
@Test(expected = UncategorizedSQLException.class)
public void transactionSync() {
    DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
    transactionDefinition.setReadOnly(true);
    TransactionStatus txStatus = transactionManager.getTransaction(transactionDefinition);

    userService.deleteAll();

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

    transactionManager.commit(txStatus);
}

롤백 검증

  • 롤백 테스트는 테스트 내의 모든 DB작업을 하나의 트랜잭션 안에서 동작하게 하고 테스트가 끝나면 무조건 롤백해버리는 테스트를 말한다.
  • 전체 테스트를 수행하기 전에 여러 테스트에서 공통으로 필요한 데이터를 DB에 넣어두고, 각 테스트의 변경 사항은 모두 롤백해 버리면 테스트를 효율적으로 수행할 수 있다.
  • 그러나 커밋을 통해 직접 SQL이 DB테이블에 적용될 때 문제가 발생하는 케이스를 검증해야 하는 경우 적절하지 않을 수 있다.
@Test
public void transactionRollback() {
    userDao.deleteAll();
    Assertions.assertEquals(0, userDao.getCount());

    DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
    TransactionStatus txStatus = transactionManager.getTransaction(transactionDefinition);

    userService.add(users.get(0));
    userService.add(users.get(1));
    Assertions.assertEquals(2, userDao.getCount());

    transactionManager.rollback(txStatus);
    Assertions.assertEquals(0, userDao.getCount());
}

둘. Declarative Transaction 테스트

@Transactional 테스트

@Transactional 어노테이션을 테스트 클래스에도 적용할 수 있다.

@Transactional
@Test
public void transactionSync() {
    userService.deleteAll();

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

중요한 차이점

  • 일반적인 서비스에 적용되는 @Transactional 어노테이션과 스프링 테스트 컨텍스트에 적용되는 @Transactional 어노테이션의 중요한 차이점 하나는 테스트에 적용되는 @Transactional 어노테이션은 테스트 메서드 수행 종료 시 기본적으로 자동 롤백되도록 설정되어 있다는 것이다.
  • 그래서 추가적으로 @Rollback(false) 설정을 통해 커밋이 되도록 설정을 바꿀 수 있다.
@Test
@Transactional
@Rollback(false)
public void transactionRollback() {
    userDao.deleteAll();

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

NotTransactional과 Propagation.NEVER

  • 테스트 클래스 대부분의 메서드에 트랜잭션 설정이 필요하고, 일부 메서드는 제외하는 경우라면 클래스 레벨에서 트랜잭션 설정을 하고, 일부 메서즈에는 @NotTransactional 또는 @Transactional(propagation=Propagation.NEVER) 속성을 지정해 줄 수있다.
  • 그러나 스프링 개발자들은 트랜잭션 테스트와 비 트랜잭션 테스트를 아예 클래스를 구분해서 만들도록 권장한다.

효과적인 DB 테스트

  • 일반적으로 의존, 협력 객체를 사용하지 않고 고립된 상태에서 테스트를 진행하는 단위 테스트와 DB 같은 외부 리소스나 여러 계층의 클래스가 참여하는 통합 테스트는 아예 클래스를 구분해서 따로 만드는게 좋다.
  • DB가 사용되는 통합 테스트를 별도의 클래스로 만들어둔다면 기본적으로 클래스 레벨에 @Transactional을 부여해준다. DB가 사용되는 통합 테스트는 가능한 롤백 테스트로 만드는게 좋다. 애플리케이션의 모든 테스트를 한꺼번에 실행하는 빌드 스크립트 등에서 테스트에서 공통적으로 이용할 수 있는 테스트 DB를 셋업해주고, 각 테스트는 자신이 필요한 테스트 데이터를 보충해서 테스트를 진행하게 만든다. 테스트가 기본적으로 롤백 테스트로 되어 있다면 테스트 사이에 서로 영향을 주지 않으므로 독립적이고 자동화된 테스트로 만들기가 편하다.
  • 테스트는 어떤 경우에도 서로 의존하면 안된다. 코드가 바뀌지 않는 한 어떤 순서로 진행되더라도 테스트는 일정한 결과를 내야 한다.
profile
소프트웨어 엔지니어, 일상
post-custom-banner

0개의 댓글