글쓴이 보관물: 낭창

Spring, DbUnit 을 이용한 rollback test

Spring으로 개발 중에 DbUnit을 이용해서 DAO 코드를 테스트 하고 있다.
(Spring 3.1.0, DbUnit 2.4.8 사용 중이다)

책을 보다보니 rollback 테스트라는게 있어서 적용해 봤는데 잘 안되서 삽질 좀 했다=_=

==

rollback 테스트라는 개념은 테스트를 시작하기 전에 트랜잭션을 시작해 주고,
테스트 종료 시 rollback 시켜 버리는 것이다.
마지막에 rollback을 해버리니 테스트 중에 무슨 짓을 해버려도 상관없다. 심지어 데이터를 다 날려도..
따로 테스트 DB를 사용하지 않아도 되고, 같은 DB로 여러명이 동시에 테스트를 돌려도 서로
영향을 받지 않으니 꽤 유용할 거 같아서 적용을 해보기로 했다.

트랜잭션은 @Transactional 어노테이션을 사용해서 걸어준다.
우선 컨텍스트 설정 파일에 다음을 추가해 준다.

<tx:annotation-driven />

그리고, Test 클래스에 @Transactional 을 붙여 준다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={“/z/y/x/test-context.xml”})
@Transactional
public class AppDaoTest {

그냥 요렇게만 붙여 주면, 마지막에 그냥 rollback이 된다고 한다.

트랙잭션 시작 -> @Before 매소드 실행 -> @Test 매소드 실행 -> @After 매소드 실행 -> rollback

요런 순서로 진행이 되므로, @Before가 붙은 매소드에서 데이터 초기화 등을 해주면 된다.
AOP로 트랜잭션을 쓸때는 괜찮았는데, 어노테이션으로 하니가 CGLIB가 필요해서 maven에 추가해줬다.

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>

다음으로, DbUnit 에서 Spring 에서 트랜잭션을 시작한 것과 동일한 커넥션을 사용하도록 해주는 것이 핵심인데,
여기서 문제가 발생해서 고생을 좀 했다.=_=
원래 IDatabaseTester 를 사용해서 테스트 중인데, 참고한 책이나 구글신께 물어 봐도 이거 말고 다른걸 사용한
예제가 많이 나온다. 난 그걸로 하니까 먼가 문제가 자꾸 생겨서 이걸로 테스트 중이다.
어쨌든, 중요한건 동일한 커넥션을 쓰면 되니까..

 …

@Inject
private DataSource dataSource;

private IDatabaseTester databaseTester;

@Before

public void setUp() {

final Connection conn = DataSourceUtils.getConnection(dataSource);
final DatabaseConnection dbConn = new DatabaseConnection(conn);

databaseTester = new DefaultDatabaseTester(dbConn);

databaseTester.setDataSet(getDataSet());
databaseTester.onSetup();
 }

 …

dataSource 를 inject 받은 다음에 connection을 외부에서 받아서 쓰는 DefaultDatabaseTester 객체를 만들었다.
DefaultDatabaseTester 는 커넥션을 만드는 방법은 모르고, 단지 외부에서 받은 커넥션을 사용할 뿐이다.

자.. 이제 테스트를 돌리면… 전부 실패=_=
이런..도대체 머가 문제지? 메시지를 보면 죄다 커넥션이 이미 close 됐다는 에러다.
헉.. 도저히 알 수 없는 상황;;;;

이런 저런 삽질하다가, 메시지를 자세히 살펴보니… setUp()은 정상적으로 진행되고, 그 다음에 에러가 나는거 같다.
그럼, setUp() 후에 커넥션이 닫혔다는 말인데… 도저히 이해가 안되는 상황…
DefaultDatabaseTester  를 쓰면 안되는가 싶어서 다른 것도 써보고, 이래저래 찾아 봤지만.. 미궁 속이다.

결국 log4jdbc 디버그 메시지를 찍고 DbUnit 소스까지 뒤져서 알아낸 사실은…

DefaultDatabaseTester.onSetup() 호출 마지막에 커넥션을 닫아 버린다는것=_=;;;

DefaultDatabaseTester는 AbstractDatabaseTester를 상속하고, AbstractDatabaseTester에서는
IOperationListener 를 사용해서 커넥션을 가져온 후, onSetUp() 호출 후, onTearDown() 호출 후 작업을 처리한다.
여기서 디폴트로 쓰는 리스너가 DefaultOperationListener 인데, 얘는, onSetUp(), onTearDown() 후 무조건
커넥션을 종료하도록 되어 있는 것이다;;

외부에서 커넥션을 받아쓰는 DefaultDatabaseTester 에서 이 동작을 오버라이딩 안 했다는건
좀 이해하기 어려운데.. 버그인지 먼지 모르겠지만.. 어쨌든 안되면 바꿔야지=_=
다행히(?) IOperationListener는 변경이 가능하다.

그래서, 아무것도 안하는 DummyOperationListener를 정의해 주고,

private class DummyOperationListener implements IOperationListener {
@Override
public void connectionRetrieved(IDatabaseConnection connection) {
// do nothing
}

@Override
public void operationSetUpFinished(IDatabaseConnection connection) {
// do nothing
}

@Override
public void operationTearDownFinished(IDatabaseConnection connection) {
// do nothing
}
}

IDatabaseTester 객체 생성 후, operation listener로 설정해 줬다.

 …

@Inject
private DataSource dataSource;
private IDatabaseTester databaseTester;


@Before
public void setUp() {

final Connection conn = DataSourceUtils.getConnection(dataSource);
final DatabaseConnection dbConn = new DatabaseConnection(conn);

databaseTester = new DefaultDatabaseTester(dbConn); databaseTester.setOperationListener(new DummyOperationListener());

databaseTester.setDataSet(getDataSet());
databaseTester.onSetup();
 }

 …

이제 테스트를 돌려보면… 짜잔~ 성공.