Eternity's Chit-Chat

aeternum.egloos.com



Domain-Driven Design의 적용-4.ORM과 투명한 영속성 10부 Domain-Driven Design

, 이것 저것 고칠게 너무 많다. 애초에 아키텍처를 잡을 때 영속성을 고려했어야 했는데아키텍처의 중요성을 뼈저리게 느끼게 되는 순간이다. 순간 불안한 느낌이 든다. 뭔가 망가지지 않았을까? 이럴 때 의지할 수 있는 건 단 하나밖에 없다. 회귀 테스트를 믿으라.

 

테스트를 실행하기 전에 한가지 더 알려 둘 것이 있다. 가능하면 단위 테스트는 데이터베이스에 의존하지 않은 채로 실행할 수 있어야 한다. , 데이터베이스가 가동되지 않아도 각 클래스만을 고립시켜 테스트할 수 있도록 테스트 케이스를 작성해야 한다. 정확하게 말하면 데이터베이스를 포함시키는 테스트는 단위 테스트가 아니라 통합 테스트이다.

 

데이터베이스와 연계해야 하는 통합 테스트에서 가장 어려운 문제는 테스트 데이터를 고립시키는 것이다. 우선 개발자들이 사용하는 테스트 데이터들이 충돌하지 않아야 한다. 만약 테스트 데이터가 서로 충돌하여 테스트가 실패한 경우 문제의 원인을 발견하기가 쉽지 않게 된다. 두 번째 문제는 테스트 데이터들의 초기화와 정리 문제이다. 테스트를 실행하기 위해서는 테스트에 필요한 테스트 데이터들이 데이터베이스에 입력되어 있어야 한다. 테스트가 종료된 후에는 사용된 테스트 데이터들을 제거해야 한다. 테스트가 종료된 후에도 테스트 데이터들이 그대로 존재할 경우 다음 테스트 결과에 영향을 미칠 수 있기 때문에 테스트를 고립시키라는 기본 원칙을 위배하게 된다.

 

앞의 문제를 해결할 수 있는 가장 좋은 방법은 개발자 별로 개발용 데이터베이스를 가지는 것이다. , 자신만의 고립된 개발환경에 샌드 박스를 구성하고 개발을 진행하는 것이다. Hibernate와 같은 ORM을 사용하면 데이터베이스 간의 호환성 문제를 해결할 수 있기 때문에 본 아티클에서 사용하는 HSQLDB와 같은 가벼운 데이터베이스를 사용하여 개발자 환경에서 어플리케이션을 개발한 후 Oracle이나 Informix를 사용하는 개발 서버에서도 별다른 수정 없이 어플리케이션을 실행시키는 것이 가능하다. 그러나 이것은 DBA에 의해 엄격하게 데이터베이스가 관리되는 프로젝트의 경우 적용하기가 쉽지 않다.

 

개발자들 간의 충돌을 막으면서도 테스트 데이터의 정리도 자동으로 수행하는 방법은 각 테스트수행이 종료된 경우 트랜잭션을 롤백시키는 것이다. , 테스트 실행이 시작되었을 때 자동으로 트랜잭션을 시작해서 데이터베이스 작업을 수행하고 결과를 검증한 후에 트랜잭션을 롤백시킴으로써 테스트 데이터들이 자동적으로 제거되도록 하는 것이다. 각 개발자들의 테스트 로직은 트랜잭션에 의해 격리되기 때문에 상호 간에 영향을 미치지 않게 된다.

 

테스트가 시작될 때 트랜잭션을 자동으로 시작하고 종료될 때 자동으로 롤백시키기 위해서는 어떻게 해야 할까? 간단하다. 테스트 클래스가 Springorg.springframework.test.AbstractTransactionalSpringContextTests 클래스를 상속받기만 하면 된다. 이 클래스는 이 전 아티클에서 살펴본 AbstractDependencyInjectionSpringContextTests 클래스의 기능에 트랜잭션 자동 롤백 기능이 추가된 것이다. AbstractTransactionalSpringContextTests 클래스는 Spring 빈 컨텍스트가 한 번만 로딩되도록 하여 테스트가 빠르게 실행되도록 하며, 빈 컨텍스트에 선언된 빈들이 자동으로 테스트 클래스에 의존 주입되도록 하고, 매 테스트 메소드 실행 시 자동으로 트랜잭션을 시작하고 종료 시 롤백 시킴으로써 데이터베이스를 항상 동일한 상태로 유지될 수 있도록 한다.

 

OrderRepository 테스트 클래스의 코드를 살펴보자.


package org.eternity.customer;

 

import org.springframework.test.AbstractTransactionalSpringContextTests;

 

 

public class OrderRepositoryTest

extends AbstractTransactionalSpringContextTests {

private Customer customer;     

      

private OrderRepository orderRepository;

private ProductRepository productRepository;

private CustomerRepository customerRepository;

      

public void setOrderRepository(OrderRepository orderRepository) {

this.orderRepository = orderRepository;

}

 

public void setProductRepository(ProductRepository productRepository) {

this.productRepository = productRepository;

}

      

public void setCustomerRepository(CustomerRepository customerRepository) {

this.customerRepository = customerRepository;

}

      

@Override

protected String[] getConfigLocations() {

return new String[] {

    "org/eternity/persistence-beanContext.xml",

       "org/eternity/order-beanContext.xml"

};

}

 

public void onSetUpInTransaction() throws Exception {

productRepository.save(new Product("상품1", 1000));

productRepository.save(new Product("상품2", 5000));

            

customer = new Customer("CUST-01", "홍길동", "경기도 안양시", 200000);

customerRepository.save(customer);

}

      

public void testOrdreCount() throws Exception {

orderRepository.save(customer.newOrder("CUST-01-ORDER-01")

.with("상품1", 5)

.with("상품2", 20)

.with("상품1", 5));

orderRepository.save(customer.newOrder("CUST-01-ORDER-02")

.with("상품1", 20)

.with("상품2", 5));

            

assertEquals(2, orderRepository.findByCustomer(customer).size());

}

      

public void testDeleteOrder() throws Exception {

orderRepository.save(customer.newOrder("CUST-01-ORDER-01")

.with("상품1", 5)

.with("상품2", 20));

Order order = orderRepository.find("CUST-01-ORDER-01");

      

orderRepository.delete(order);

                          

assertNull(orderRepository.find("CUST-01-ORDER-01"));

assertNotNull(order);        

}   

}


이전의 테스트 클래스에서 딱 3가지만 수정함으로써 자동 트랜잰션 롤백이 가능한 테스트 클래스로 변경했다. 수정된 부분을 찾을 수 있겠는가? 첫 번째로 테스트 클래스가 AbstractTransactionalSpringContextTests를 상속 받도록 수정했다. 두 번째로 getConfigLocations() 메소드가 새로 추가된 빈 컨텍스트 파일인 persistence-beanContext.xml 파일도 함께 반환하도록 수정됐다. 마지막으로 onSetUp() 메소드의 명칭을 onSetUpInTransaction()으로 수정했다. onSetUpInTransaction() 메소드는 트랜잭션이 시작되고 각 테스트 메소드가 실행되기 전에 실행되는 메소드로 트랜잭션 컨텍스트 내에서 테스트 픽스처를 셋업하기 위해 사용된다. 트랜잭션이 실행되기 전에 수행될 셋업 작업이 필요한 경우 onSetUpBeforeTransaction() 메소드를 오버라이딩하면 된다.


핑백

  • Domain-Driven Design | Jongmin Kim's Blog 2014-09-02 01:18:08 #

    ...  Domain-Driven Design의 적용-4.ORM과 투명한 영속성 11부 [完] [10] 2009/10/13 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 10부 2009/07/29 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 9부 2009/0 ... more