통합 테스트에서 @Transactional이 먹지 않는 경우

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration(defaultRollback=true)
@Transactional
public class DomainTest {
    
    @Autowired
    private SessionFactory sessionFactory;

    @Test
    public void tadd() throws Exception {
        assertNotNull(sessionFactory);
        Session session = sessionFactory.openSession();

        BankAccount bankAccount = new BankAccount();
        bankAccount.setOwner(“keesun”);
        bankAccount.setAccount(“123123″);
        session.save(bankAccount);

        session.flush();
        session.close();

        assertNotNull(bankAccount.getId());
    }

    @Test
    public void atransacctional() throws Exception {
        Session session = sessionFactory.openSession();

        List<BankAccount> bankAccounts = session.createQuery(“from BankAccount”).list();
        assertEquals(0, bankAccounts.size());

        session.flush();
        session.close();
    }
}

테스트 이름을 보면 뭔가 이상하다는 것을 느끼실 겁니다. add 앞에 t를 붙여놨고, transactional앞에 a를 붙여놨습니다. 그냥 테스트 하면 transactional 테스트 코드를 먼저 실행하고 그 뒤에 add를 실행하기 때문에 저 테스트 코드가 정말 트랜잭션 처리가 됐는지 안 됐는지 확인 할 수가 없습니다. 더군다나 메모리 모드로 HSQL을 사용하고 있기 때문에 테스트를 한 번 실행하고 나면 데이터는 어차피 날아갑니다. 그래서 add가 먼저 실행되게 이름을 좀 이상하게 바꿔놨습니다.

그랬더니… 트랜잭션 처리가 되지 않는 것이 확인 되었습니다. 테이블에서 데이터 전부 가져오면 갯수가 0이어야 하는데, 1인 것입니다. add에서 하나 집어 넣은게 롤백되지 않았다는 것입니다.

대체 왜…

내가 뭘 잘못했길래…

애노테이션이 이상한가.. 싶어서 AbstractTransactional어쩌구저쩌구 클래스를 가지고도 테스트 해봤습니다. 여전히..  트랜잭션이 먹히지 않습니다.

bean 설정은 다음과 같습니다.

    <bean class=”chapter7.polymorphic.tpccWithImplicitPolymorphism.DomainTest” />

    <bean id=”dataSource”
        class=”org.apache.commons.dbcp.BasicDataSource”
        destroy-method=”close”>
        <property name=”driverClassName” value=”org.hsqldb.jdbcDriver” />
        <property name=”url” value=”jdbc:hsqldb:mem:test” />
        <property name=”username” value=”sa” />
        <property name=”password” value=”” />
    </bean>

    <bean id=”sessionFactory”
        class=”org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean”>
        <property name=”dataSource” ref=”dataSource” />
        <property name=”hibernateProperties”>
            <props>
                <prop key=”hibernate.dialect”>
                    org.hibernate.dialect.HSQLDialect
                </prop>
                <prop key=”hibernate.show_sql”>true</prop>
                <prop key=”hibernate.format_sql”>true</prop>
                <prop key=”hibernate.hbm2ddl.auto”>update</prop>
                <prop key=”hibernate.connection.autocommit”>false</prop>
            </props>
        </property>
        <property name=”annotatedClasses” ref=”annotatedClasses” />
    </bean>

    <util:list id=”annotatedClasses”>
        <value>chapter7.polymorphic.tpccWithImplicitPolymorphism.BankAccount</value>
        <value>chapter7.polymorphic.tpccWithImplicitPolymorphism.BillingDetails</value>
        <value>chapter7.polymorphic.tpccWithImplicitPolymorphism.CreditCard</value>
    </util:list>
   
    <bean id=”transactionManager”
        class=”org.springframework.orm.hibernate3.HibernateTransactionManager”
        p:dataSource-ref=”dataSource” p:sessionFactory-ref=”sessionFactory” />
       
</beans>

정답은 안 갈쳐줍니다. 캬캬캬캬캬

힌트.
자바 소스 코드는 문제가 없으며,
경로 설정이라든가,
오타로 인한 문제도 아닙니다.
http://forum.springframework.org/showthread.php?t=16884

Spring 애노테이션 기반 통합 테스트 작성하기

http://static.springframework.org/spring/docs/2.5.x/reference/testing.html#testcontext-tx

스프링이 제공하는 통합 테스트 용 클래스들은 매우 유용합니다. 특히 DAO를 테스트 할 때 자동 롤백은 정말이지 고마움까지 느껴질 정도로 애착이 갑니다. 그러나 한 가지 단점이 있다면, 이름이 너무 길다는 것이 단점입니다.

AbstractTransactionalDataSourceSpringContextTests

거의 40자 입니다. 이것을 외워서 타이핑 하다가는 오타나기 딱 좋습니다. 다행히 Eclipse의 자동완성 기능을 사용하면 AbstractT 까지만 입력하면 해당 클래스를 입력할 수 있습니다.

이 클래스는 내부적으로 DataSource를 필요로 하고, 이 클래스의 상위 클래스인 AbstractTransactionalSpringContextTests 이녀석은 PlatformTransactionManager를 필요로 합니다. 이 두 개를 가지고 테스트 케이스를 트랜잭션 처리해 주는 일을 합니다.
사용자 삽입 이미지스프링에서 트랜잭션 처리를 선언적으로 하는 방법이 두 가지 있는데, 하나는 XML이고, 하나는 애노테이션을 사용하는 방법입니다. XML은 역시 다 잊어버려서 레퍼런스를 찾아봐야합니다. 애노테이션은 그나마 기억하기가 매우 쉽습니다. @Transactional 이라는 것만 붙여주면 되죠.

이야기가 더 새기전에 마무리 해야겠습니다.

결론만 말씀드리면, Spring 2.5부터는 AbstractTransactionalDataSourceSpringContextTests를 쓰는 대신에 @Transactional과 @TransactionConfiguration을 사용해서 같은 기능을 하는 테스트 클래스를 작성할 수 있습니다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration(transactionManager = “transactionManager”, defaultRollback = true)
@Transactional
public class DaoTest {

    @BeforeTransaction
    public void beforeTransation() {
        System.out.println(“before transation”);
    }

    @Before
    public void before() {
        System.out.println(“before”);
    }

    @After
    public void after() {
        System.out.println(“after”);
    }

    @AfterTransaction
    public void afterTransaction() {
        System.out.println(“after transaction”);
    }

    @Test
    public void transactionalTest() throws Exception {
        System.out.println(“This is a transactional test”);
    }

    @Test
    @NotTransactional
    public void testname() throws Exception {
        System.out.println(“This is not a transactional test”);
    }

}

애노테이션의 이름을 보고 어떤 결과가 출력될지 예상해 보세요. 정답은 접어 둡니다.

[#M_ more.. | less.. |before transation
before
This is a transactional test
after
after transaction
before
This is not a transactional test
after
_M#]
이밖에도, @Rollback 애노테이션으로 특정 테스트 케이스를 롤백할지 말지 설정할 수도 있습니다.

스프링 2.5 기반(JUnit 4.4 사용) 테스트 코드

위의 코드에 보시면, Context 파일을 명시하고 있지 않습니다. 스프링 2.5에서 도입한 COC중 하나로 생각할 수 있는데요. 만약 테스트 코드를 담고 있는 클래스가 whiteship.MemberTest 면 whiteship/MemberTest-context.xml 이라는 파일을 기본 설정 파일로 읽어오려고 합니다. 물론 명시적으로 설정할 수도 있습니다.

사용자 삽입 이미지
그리고 세터도 없이 @Autowired 애노테이션을 사용하여 Member 객체를 가져오고 있습니다.

위 소스코드를 작성할 때 주의 할 것은.. JUnit 4 라이브러리를 이클립스의 자동완성(컨트롤 + 스페이스)으로 추가하면 안됩니다. 스프링 소스 코드를 받을 때 같이 받은 lib/junit에 있는 jar 파일을 빌드패스에 추가해주어야 합니다.(둘 다 같은 4.4같은데 RunWith 애노테이션의 인자가 다른 것 같습니다.)

따라서 위의 코드를 돌리는데 필요한 Jar 파일은 다음과 같습니다.

  • dist 폴더의 spring.jar (스프링 컨테이너 필요함.)
  • dist/modules 폴더의 spring-test.jar (SpringJUnit4ClassRunner.class 필요함.)
  • lib/jakarta-commons 폴더의 commons-logging.jar (spring.jar 가 종속함.)
  • lib/junit 폴더의 junit-4.4.jar (@RunWith, @Test, assertNotNull 필요함.)

Spring의 Test API에서 setUp과 tearDown

질문 : setUp() 와 tearDown() 메소드가 final로 되어 있는 테스트 클래스들이 대부분인데요. 그럼 Spring의 Test API를 사용할 때는 JUnit에서 각 테스트를 위해 매번 세팅하거나 자원을 돌려줘야 할 땐 어떻게 하나요??

답변 :

사용자 삽입 이미지이 아래로 더 있을지도 모르겠지만 이 중에서 AbstractSpringContextTests 요거 부터 보겠습니다.

public class SpringTestTest extends AbstractSpringContextTests{

    @Override
    protected ConfigurableApplicationContext loadContext(Object arg0) throws Exception {
        throw new UnsupportedOperationException();
    }

    @Override
    protected void setUp() throws Exception {
        throw new UnsupportedOperationException();
    }

    @Override
    protected void tearDown() throws Exception {
        throw new UnsupportedOperationException();
    }
}

여기서는 JUnit의 setUp과 tearDown을 재정의 하여 사용할 수 있습니다.

그럼 한 단계 아래로 가서 AbstractSingleSpringContextTests를 보겠습니다. 어랏.. 여기서 부터는 위에서 사용한 setUp과 tearDown을 사용할 수 없습니다. final로 정의해 뒀네요.
사용자 삽입 이미지어라.. “그럼 우리는 무얼 사용해야 되죠~?” 위 그림에 보이는 onSetUp()과 onTearDown()을 사용하면 됩니다.

public class SpringTestTest extends AbstractSingleSpringContextTests{

    @Override
    protected void onSetUp() throws Exception {
        throw new UnsupportedOperationException();
    }

    @Override
    protected void onTearDown() throws Exception {
        throw new UnsupportedOperationException();
    }
}

따라서 AbstractSingleSpringContextTests 이 클래스 하위에 있는 테스트 클레스들은 전부 위의 메소드들을 사용해야겠군요~

위 가정에 해당하는 증명은 AbstractSingleSpringContextTests 클레스에 있는 소스코드 일부로 할 수 있습니다.

    protected final void setUp() throws Exception {
        this.applicationContext = getContext(contextKey());
        prepareTestInstance();
        onSetUp();
    }

8.3. Integration testing

org.springframework.test 패키지에서 제공되는 기능들은 아래와 같습니다.

  • Spring IoC container caching between test case execution.
  • The pretty-much-transparent Dependency Injection of test fixture instances (this is nice).
  • Transaction management appropriate to integration testing (this is even nicer).
  • A number of Spring-specific inherited instance variables that are really useful when integration testing.

8.3.1. Context management and caching

여러 설정 파일들을 cashing하는 기능입니다. 이게 필요한 이유는 매번 테스트를 할 때마다 설정 파일들을 새로 읽어들이는 비용을 줄이기 위해서 입니다.

AbstractDependencyInjectionSpringContextTests 클래스를 상속하면 다음의 메소드를 꼭 구현하도록 abstract로 되어 있습니다.

protected abstract String[] getConfigLocations();

이 메소드에서 테스트에 필요한 bean설정 파일들(context)을 String 배열로 리턴하도록 구현해 주면 됩니다.

기본적으로 설정파일의 내용이 바꼈을 때만 다시 읽어 들이게 되고 명시적으로 다음 테스트를 하기 전에 다시 읽어 들이게 하고 싶다면 setDirty() 메소드를 사용합니다.

8.3.2. Dependency Injection of test fixtures

AbstractDependencyInjectionSpringContextTests 클래스를 사용하여 bean 설정 파일을 읽어 들이면 DI를 통해서 테스트할 때 필요한 멤버 변수에 값을 세팅할 수 있습니다.

by-type으로 autowiring 설정이 되어있습니다. 따라서 같은 타입의 bean이 여러개일 경우에는 적용이 되지 않을 것입니다.

테스트 클래스의 생성자에서 setPopulateProtectedVariables(true); 를 추가해 주면 protected 접근 지시자의 멤버 변수가 자동으로 세팅되도록 설정할 수 있습니다.

이 때는 autowiring을 사용한 것이 아니라 상속 받은 applicationContext 변수를 통해 직접 bean을 가져와서 세팅하게 됩니다.

8.3.3. Transaction management

DB에 접근하는 테스트를 할 때 문제는 DB에 테스트의 흔적이 남는다는 것인데요. AbstractTransactionalDataSourceSpringContextTests 클래스를 사용하면 테스트를 자동으로 트랜잭션 처리를 해주며 테스트가 끝날때 마다 DB를 롤백시켜줍니다.

이 클래스는 DataSource를 autowiring하게 되어 있으며 by-type이기 때문에 DataSource의 bean 이름은 상관없이 등록되어 있기만 하면 찾아서 DI합니다.

setComplete() 메소드를 사용하면 트랙잭션을 커밋시킬 수 있습니다.
endTransaction() 메소드를 사용하면 테스트 메소드가 끝나기 전에 트랜잭션을 종료시킬 수 있습니다.

8.3.4. Convenience variables

applicationContext :: AbstractDependencyInjectionSpringContextTests 클래스로 부터 상속받는 변수 입니다. bean을 직접 lookup 하거나 context 자체에 대한 테스트를 할 때 사용할 수 있습니다.

jdbcTemplate :: AbstractTransactionalDataSourceSpringContextTests 클래스로 부터 상속받는 변수 입니다.

8.3.5. Java5+ specific support

Java 5.0 이상에서 사용할 수 있는 AbstractAnnotationAwareTransactionalTests 클래스를 상요하면 다음의 어노테이션들을 사용할 수 있습니다.

@DirtiesContext :: 위에서 잠시 언급했었던 setDirty()와 같은 효과를 낼 수 있는 어노테이션으로 해당 테스트 메소드 위에 붙여주면 다음 테스트 메소드를 실행하기 전에 context들을 다시 읽어 들입니다.

@ExpectedException ::  이 어노테이션이 붙은 테스트 메소드에서 발생할 예외 객체 타입을 적어 줍니다. 만약 해당 테스트 메소드에서 해당 예외가 발생하면 테스트는 통과하고 그렇치 않으면 테스트는 실패합니다.

@NotTransactional :: 트랙잰션 관리 대상에서 제외합니다.

@Repeat :: 해당 테스트 메소드를 몇 번 반복해서 테스트할지 지정해 줄 수 있습니다.

위에서 언급했던 세 개의 클래스의 상속 구조 입니다.
사용자 삽입 이미지