Home 6장 단위테스트 지원 라이브러리:Unitils
Post
Cancel

6장 단위테스트 지원 라이브러리:Unitils

6.1 Unitils를 사용하기 위한 환경 준비

Unitils 라이브러리를 사이트에서 내려 받고, 클래스패스에 포함시켜준다. JUnit4와 마찬가지로 static import를 주로 사용한다.

6.2 Unitils의 단위 테스트 지원 기능들

객체 동치성 비교

테스트 케이스를 작성하다 보면 객체끼리 동치성 비교를 해야하는 경우가 흔히 발생한다.

1
2
3
4
5
6
@Test
public void testBook() {
  Book abook = new Book("사람은 무엇으로 사는가?", "톨스토이", "9000");
  Book otherBook = new Book("사람은 무엇으로 사는가?", "톨스토이", "9000");
  assertEquals(aBook, otherBook); //fail
}

객체의 비교는 단순히 참조 비교를 하기 때문에 두 객체는 의도된 객체의 상태 측면에서는 동일하지만 결과는 두 객체가 서로 다르다고 판정한다. 그래서 다음과 같이 각 필드를 비교해야 한다.

1
2
3
assertEquals(aBook.getName(), otherBook.getName())
assertEquals(aBook.getAuthor(), otherBook.getAuthor())
assertEquals(aBook.getPrice(), otherBook.getPrice())

리플렉션 단정문

객체의 필드에 저장되어 있는 값을 알아서 비교해주는 기능이다.

assertReflectionEquals(예상객체, 실제객체);, assertReflectionEquals([메시지], 예상객체, 실제객체);

1
2
3
4
5
6
@Test
public void testBook() {
  Book abook = new Book("사람은 무엇으로 사는가?", "톨스토이", "9000");
  Book otherBook = new Book("사람은 무엇으로 사는가?", "톨스토이", "9000");
  assertReflectionEquals("Book 객체 필드 비교", aBook, otherBook);
}

리플렉션 단정문의 너그러운 비교

리플렉션 단정문에는 몇 가지 옵션을 지정할 수 있는데 대부분 리팩토링이 일어나면서 발생하는 테스트 케이스의 깨짐을 보완하기 위해 사용된다. 이러한 방식을 ‘너그러운 단정문’ 적용 이라고 한다.

assertReflectionEquals(예상객체, 실제객체, ReflectionComparatorMode)

[ReflectionComparatorMode] - 대상을 비교할 때 좀 더 유연한 비교를 할 수 있도록 세 가지 옵션을 제공한다.

  • LENIENT_ORDER : 컬렉션, 배열을 비교할 때 순서 무시
  • IGNORE_DEFAULTS : 예상 객체 필드 중 타입 기본값을 갖는 필드에 대해서는 비교하지 않음 단, 필드 기본값을 비교해서 제외하는 것은 예상 객체를 기준으로 한다.
  • LENIENT_DATES : 시간, 날짜 타입은 비교하지 않음

**Unitils에는 assertLenientEquals라는 것이 있는데 이는 assertReflectionEquals의 간략화 버전으로 세 개의 옵션 중 LENIENT_DATES을 제외한 두 개를 동시에 적용해서 비교하는 버전이다.

프로퍼티 단정문

보통 특정 필드에 값이 제대로 할당됐는지 확인하는 가장 간단한 방법은 getter 메소드를 통해서 값을 직접 확인한다. 하지만 경우에 따라서는 getter 메소드가 제공되지 않는 경우도 있다. 그럴때는 Unitils의 assertPropertyLenientEquals를 이용하면 된다. assertPropertyLenientEquals(속성이름, 예상되는 속성값, 실제 객체)

assertPropertyLenientEquals는 자바빈 규칙을 따르는 식으로 프로퍼티를 비교한다. 추후 getter 메소드가 생기게 되면 assertPropertyLenientEquals는 해당 bean 규약에 맞는 getter 메소드를 이용해 프로퍼티 값을 불러와서 비교를 수행한다.

6.3 Unitils 모듈

Unitils의 모듈은 다양한 서비스를 제공하는데 테스트 케이스 작성시 해당 모듈을 사용하려면 UnitilsJUnit3, UnitilsJUnit3, UnitilsJUnit4, UnitilsTestNG 등의 Test Listener가 필요하다. Test Listener를 사용하려면 Test Listener를 테스트 클래스가 상속하거나 @RunWith 같은 애노테이션으로 Test Runner로 지정하면 된다.

[모듈 종류]

  • DatabaseModule : 데이터베이스 관리와 커넥션 풀 관련
  • DbUnitModule : DbUnit을 사용할 때 사용하는 테스트 데이터 관리
  • HibernateModule : 하이버네이트 설정 지원과 DB 매핑 체크
  • MockModule : Unitils에서 제공하는 Mock 프레임워크
  • EasyMockModule : EasyMock 지원 기능
  • InjectModule : 오브젝트를 강제로 할당시켜 버리는 Injection 기능
  • SpringModule : 스프링의 애플리케이션 컨텍스트 지원과 스프링 빈의 주입 기능

6.4 DbUnit과 함께 사용하는 데이터베이스 지원 모듈

환경을 위한 Unitils.properties 파일 설정

1
2
3
4
5
6
7
database.driverClassName=org.apache.derby.jdbc.EmbeddedDriver
database.url=jdbc:derby:shopdb
database.userName=
database.password=
database.schemaNames=App
database.dialect=derby
DatabaseModule.Transacional.value.default=disabled

@DataSet

이 어노테이션을 사용하면 클래스이름.xml 파일을 기본 데이터셋으로 인식하고 DB로 읽어들인다. (FlatXmlDataSet 타입이어야 함)

클래스 레벨에서 @DataSet 애노테이션이 사용되면 클래스이름.xml 파일을 데이터셋으로 인식한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RunWith(UnitilsJUnit4TestClassRunner.class)
@DataSet
public class DatabaseRepositoryTest {

  @Test
  public void testFindById() throws Exception {
    Repository repository = new DatabaseRepository();
    Seller actualSeller = repository.findById("horichoi");

    assertPropertyLenientEquals("id", "horichoi", actualSeller);
    assertPropertyLenientEquals("name", "최승호", actualSeller);
    assertPropertyLenientEquals("email", "megaseller@hotmail.com", actualSeller);
  }
}

클래스 이름과 데이터셋 파일 이름이 일치하지 않는다면 직접 지정도 가능하다. @DataSet("seller.xml") 또 메소드 레벨에서도 데이터셋을 지정할 수 있다.

1
2
3
4
5
6
7
8
@Test
@DataSet("DatabaseRepositoryTest.testAddNewSeller.xml")
public void testAddNewSeller() throws Exception {
  Seller seller = new Seller("hssm", "이동욱", "scala@hssm.kr")
  Repository repository = new DatabaseRepository();

  ...
}

또 FlatXMLDataSet 외의 데이터셋 타입을 이용하고 싶다면 DataSetFactory인터페이스를 직접 구현한 다음 설정파일에 DbUnitModuleDataSet.factory.default 항목으로 지정하면 된다.

데이터셋 로드 전략

@DataSet은 CLEAN_INSERT가 기본 동작이지만 원할 경우 다른 동작을 지정할 수 있다. 1)설정 파일에 지정하는 방식, 2)애노테이션 사용시 지정하는 방식

1
2
3
4
5
//1)
DbUnitModule.DataSet.loadStrategy.default=org.unitils.dbunit.datasetloadstrategy.InsertLoadStrategy

//2)
@DataSet(loadStrategy = InsertLoadStrategy.class)
  • CleanInsertLoadStrategy : 테이블의 내용을 모두 지우고 데이터셋의 내용을 INSERT
  • InsertLoadStrategy : 데이터셋을 INSERT
  • RefreshLoadStrategy : 데이터셋의내용으로 DB 갱신
  • UpdateLoadStrategy : DB에 존재하는 데이터를 UPDATE

TestDataSource, 테스트에 사용하는 데이터소스 접근하기

Unitils는 설정파일에 지정된 DB 관련 값을 이용해 DataSource를 구성해 자동으로 DB 연결을 만들어 테스트를 진행하도록 설계되어 있다. 경우에 따라 DataSource에 직접 접근할 필요가 있는 경우 1)@TestDataSource 애노테이션을 DataSource에 지정해서 Unitils가 자동으로 주입하도록 하는 방법 2)DatabaseUnitils.getDataSource() 메소드를 이용하는 방법이다.

1
2
3
4
5
6
7
8
9
@TestDataSource
private DataSource dataSource;
private ShopDAO dao;

@Before
public void initalize() {
    this.dao = new ShopDAO;
    dao.setDataSource(dataSource);
}
1
dao.setDataSource(DatabaseUnitils.getDataSource());

@ExpectedDataSet, 예상 데이터셋을 이용한 테스트 메소드 레벨의 결과 비교

1
2
3
4
5
6
7
@Test
@ExpectedDataSet("expected_seller.xml")
public void testAddNewSeller() throws Exception {
    Seller newSeller = new Seller("hssm", "이동욱", "scala@hssm.kr");
    Repository repository = new DatabaseRepository();
    repository.add(newSeller);
}

직접 예상 데이터셋을 지정하지 않는다면 Unitils는 기본적으로 ‘클래스이름.메소드이름-result.xml’ 이라는 데이터셋 파일을 찾는다.

@Transacional, 트랜잭션 처리

DB관련 기능을 테스트할 때 트랜잭션 처리가 필요한 경우가 많다. 기본적으로 Unitils의 DB관련 기능을 이용해 테스트를 수행하게 되면 모든 테스트는 트랜잭션이 발생하는 상태로 동작하고, 테스트 마지막에 커밋을 발생시킨다.

1
2
3
4
5
6
//1) Unitils.properties
DatabaseModule.Transacional.value.default = rollback

//2) @Transacional 애노테이션으로 지정
@Transacional(TransacionModule.ROLLBACK)
public  class ShopDaoTest extends UnitilsJUnit4{}

6.5 DBMaintainer: DB를 자동으로 유지보수해주는 DB 유지보수 관리자

Unitils의 DBMaintainer는 개발자 각자의 DB 스키마를 SQL 스크립트를 이용해 자동으로 유지시켜주는 기능이다.

  1. 프로젝트 내에 폴더를 만들어서 필요한 DB스크립트를 넣는다.
  2. DB 스트립트의 형식은 숫자형식의 버전넘버를 _언더바로 구분지어 이름을 짓는다. 예)001_DROP_ALL_TABLE.sql …
  3. Unitils의 DataSource를 이용하는 테스트 클래스를 실행한다.
  4. DBMaintainer는 지정된 스크립트 폴더를 모니터링해서 변경된 내용이 있으면 반영한다. DB 구조를 SQL 스크립트로 관리하고, 추가 내용을 덧붙여 반영하도록 만들 때 매우 유용하다. 새로운 스크립트가 추가된 경우라면 해당 스크립트만 실행하고, 기존 스크립트가 변경된 경우라면 스키마를 전체를 리셋하고, 다시 처음부터 스크립트를 실행한다.

DBMaintainer 기능 활성화 시키기

DBMaintainer의 기능은 기본값으로는 false 상태이기 때문에 사용하기 위해서는 설정파일에 true로 지정해야한다. updateDatabaseSchema.enabled=true 그리고 스크립트 파일들이 존재하는 폴더를 지정한다. dbMaintainer.script.locations=src/dbscripts dbMaintainer.autoCreateExecuteScriptsTable=true autoCreateExecuteScriptsTable 테이블을 스크립트들을 버전관리하기 위해 사용하는 테이블로 최초에는 자동으로 생성하기 위해 true로 설정한다. 또 다음의 기능을 지원한다.

  • 모든 참조키와 Not Null 제약조건을 비활성화시키기 때문에 INSERT 스크립트 실행을 편리하게 만들어준다.
  • 시퀀스 타입들을 높은 숫자로 업데이트 시켜주어 기본키 값을 고정값으로 지정해도 충돌이 나지 않도록 도와준다.
  • 데이터베이스의 구조를 XSD 파일로 만들어준다.

6.6 기타 지원 모듈들

하이버네이트 지원 모듈

  • HibernateSessionFactory : SessionFactory를 쉽게 얻을 수 있게 도와준다.
  • 실제 데이터베이스 구조와 하이버네이트 매핑 파일내의 구조가 일치하는지 테스트해준다.

스프링 지원 모듈

  • @SpringApplicationContext, 스프링 애플리케이션 컨텍스트 설정 지원
  • 스프링 빈 주입

Mock 지원 모듈

This post is licensed under CC BY 4.0 by the author.

5장 데이터베이스 테스트 DbUnit

7장 개발 영역에 따른 TDD 작성 패턴