본문 바로가기

Spring/토비의 스프링 3.1

2.4 스프링 테스트 적용


애플리케이션 컨텍스트 생성 방식

  • @Before 메소드가 테스드 메소드 개수만큼 반복
  • 반복될 때마다 애플리케이션 컨텍스트가 만들어짐
  • 애플리케이션 컨텍스트 생성 시 모든 싱글톤 빈 오브젝트를 초기화하므로 제법 많은 시간을 필요로 함

테스트는 가능한 독립적으로 매번 새로운 오브젝트를 사용하는 것이 원칙

  • 애플리케이션 컨텍스트처럼 시간과 자원이 많이 소모되는 경우 테스트 전체가 공유하는 오브젝트를 만들기도 함
  • 다만, 이 때도 테스트는 일관성이 있어야하며, 테스트 순서에 영향을 받지 말아야한다.
  • 애플리케이션 컨텍스트의 경우 초기화되고 나면 내부 상태가 변경되는 일이 거의 없으므로 무관함

문제는 JUnit이 매번 테스트 클래스의 오브젝트를 새로 생성함. 여러 테스트가 함께 참조할 애플리케이션 컨텍스트를 오브젝트 레벨에 저장해두면 곤란(Question : 음 ?)

  1. 스태틱 필드에 저장
    • JUnit은 @BeforeClass 스태틱 메소드를 지원
  2. 스프링이 제공하는 애플리케이션 컨텍스트 테스트 지원 기능
    • 이게 더 편리

2.4.1 테스트를 위한 애플리케이션 컨텍스트 관리

스프링 테스트 컨텍스트 프레임워크 적용

적용 방법

  1. @Before 메소드에서 애플리케이션 컨텍스트 생성코드 제거
  2. ApplicationContext 타입의 인스턴스 변수 선언 후 @Autowired 애노테이션 추가
  3. 클래스 레벨에 @RunWith와 @ContextConfiguration 애노테이션 추가

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/applicationContext.xml")
public class UserDaoTest {
    @Autowired
    private ApplicationContext context;
    // ...
}

context를 초기화해주는 코드가 없음

  • 그럼에도 불구하고 NullPointerException은 발생하지 ㅇ낳음
  • context 변수에 애플리케이션 컨텍스트가 들어가 있음
  • RunWith : JUnit 테스트 실행 방법 확장 시 사용하는 애노테이션
  • SpringJUnit4ClassRunner : JUnit용 테스트 컨텍스트 프레임워크 확장 클래스, 테스트에서 사용할 애플리케이션 컨텍스트를 만들고 관리하는 작업을 해 줌
  • ContextConfiguration : 자동으로 만들어 줄 애플리케이션 컨텍스트의 위치를 지정

테스트 메소드의 컨텍스트 공유

setUp()에 context와 자기 자신인 this를 출력하는 코드 추가 (임시 코드)

@Before
public void setUp() {
    System.out.println("context: " + this.context);
    System.out.println("setUp: " + this);
}
context: org.springframework.context.support.GenericApplicationContext@4883b407
setUp: com.david.tobysspring.user.dao.UserDaoTest@659a969b
context: org.springframework.context.support.GenericApplicationContext@4883b407
setUp: com.david.tobysspring.user.dao.UserDaoTest@41e1e210
context: org.springframework.context.support.GenericApplicationContext@4883b407
setUp: com.david.tobysspring.user.dao.UserDaoTest@625732

context는 세 번 모두 동일하지만, UserDaoTest 자신은 세 번 모두 새로 생성됨.

  • 테스트 실행 전 딱 한 번 애플리케이션 컨텍스트를 생성해두고, 테스트 오브젝트가 생성될 때마다 특별한 방법으로 애플리케이션 컨텍스트를 특정 필드에 주입
  • 일종의 DI(애플리케이션 오브젝트 사이의 관계를 관리하기 위한 DI와는 조금 다름)

테스트 클래스의 컨텍스트 공유

테스트 클래스가 여러 개 있을 때

  • 같은 설정 파일을 가진 애플리케이션 컨텍스트를 사용하면, 테스트 클래스 사이에서도 애플리케이션 컨텍스트를 공유하게 해 줌
  • 테스트 클래스의 수가 늘어나도 이 덕분에 테스트 성능이 대폭 향상 됨

@Autowired

스프링 DI에 사용되는 특별한 애노테이션

  • @Autowired가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다.
  • 타입이 일치하는 빈이 있으면 인스턴스 변수에 주입해준다.
  • 일반적으로 생성자 또는 수정자 메소드가 필요하지만, 이 경우에는 없어도 주입이 가능하다.

UserDao 역시 Autowired를 이용하여 DI 받을 수 있다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/applicationContext.xml")
public class UserDaoTest {
    @Autowired
    private ApplicationContext context;

    @Autowired
    private UserDao dao;

    // ...
}

@Autowired는 변수에 할당 가능한 타입을 가진 빈을 자동으로 찾음

  • 다형성 이용 가능
  • 같은 타입의 빈이 두 개 이상 있을 경우 변수의 이름과 같은 이름의 빈을 주입함
  • 그래도 못 찾으면 예외 발생

2.4.2 DI와 테스트

UserDao와 커넥션 생성 클래스 사이 : DaoSource

  • UserDao는 자신이 사용하는 오브젝트의 클래스가 무엇인지 알 필요가 없음
  • DI를 통해 오브젝트를 주입받으므로 오브젝트 생성에 대한 부담이 없음
  • 코드 수정 없이 의존 오브젝트를 바꿔가면 사용할 수 있음

테스트 코드에 의한 DI

기존 applicationContext.xml에 정의된 DataSource빈을 사용할 경우

  • 운영용 DB와 연결이 되어 있다면? deleteAll()을 실행하고 짤리겠지? (짤리기만 하면 다행)
  • DI를 이용하여 테스트 중에 DAO가 사용할 DataSource 오브젝트를 바꿔주는 방법을 이용

테스트용 DB에 연결해주는 DataSource : SingleConnectionDataSource 사용 (스프링이 제공하는 가장 빠른 DataSource)

  • DBConnection을 하나만 만들어두고 계속 사용(빠름)
  • 다중 사용자 이용 환경에서 사용하기는 어려움
  • 순차적으로 진행되는 테스트 환경에서 이용하기 적절

UserDaoTest.java

@DirtiesContext
public class UserDaoTest {
    @Before
    public void setUp() {
        DataSource dataSource = new SingleConnectionDataSource(
                "jdbc:oracle:thin:@localhost:1521:xe", "springbook_test", "test", true);
        dao.setDataSource(dataSource);
        // ...
    }
}

XML을 사용하지 않고도 오브젝트 관계를 재구성할 수 있다는 점에서 편리하지만, 매우 주의해서 사용해야 함.

  • applicationContext.xml 파일의 설정정보를 따라 구성한 오브젝트를 가져와 의존관계를 강제로 변경
  • 애플리케이션 컨텍스트는 한 번만 만들어지고, 모든 테스트에서 공유되므로 변경하지 않는 것이 원칙
  • 따라서 Dirties 애노테이션 추가
  • 새로운 애플리케이션 컨텍스트를 생성하여 다음 테스트에 영향을 주지 않음
  • 그런데 애플리케이션 컨텍스트를 새로 생성하는 것도 찜찜함

테스트를 위한 별도의 DI 설정

테스트 코드에서 빈 오브젝트에 수동으로 DI하는 방법

  • 코드가 많아져 번거로움
  • 애플리케이션 컨텍스트를 매번 새로 만들어야 하는 부담

DataSource 클래스가 빈으로 정의된 테스트 전용 설정파일을 따로 만들어두는 방법

  • 두가지 설정파일을 만들어 하나는 서버에서 운영용으로 사용할 DataSource를 빈으로 등록
  • 다른 하나는 테스트용 DataSource를 빈으로 등록
  • 테스트에서는 테스트 전용 설정파일을 사용

적용 방법

  • applicationContext.xml을 복사하여 test-applicationContext.xml을 만듦
  • 다른 빈설정은 그대로 두고 dataSource 빈만 테스트용으로 변경
  • UserDaoTest의 ContextConfiguration 애노테이션의 location 엘리먼트 값을 변경
  • SetUp 메소드의 수동 DI 코드와 DirtiesContext 애노테이션 제거

test-applicationContext.xml

<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
    <property name="driverClass" value="oracle.jdbc.driver.OracleDriver"></property>
    <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"></property>
    <property name="username" value="springbook_test"></property>
    <property name="password" value="test"></property>
</bean>

UserDaoTest.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
public class UserDaoTest {
    // ...
}

컨테이너 없는 DI 테스트

스프링 컨테이너를 사용하지 않고 테스트를 만드는 방법

  • UserDao나 DataSource 구현 클래스에서 스프링 API를 직접 사용하거나 애플리케이션 컨텍스트를 이용하는 코드가 없으므로 스프링 DI 컨테이너에 의존하지 않아도 된다.
  • 테스트 코드에서 직접 오브젝트를 만들고 DI해서 사용해도 된다.
  • RunWith, Autowired를 사용하지 않고 직접 UserDao의 오브젝트를 생성하고 테스트용 Data 오브젝트를 만들어 직접 DI 해주면 된다.

UserDaoTest.java

public class UserDaoTest {
    UserDao dao;
    // ...

    @Before
    public void setUp() {
        dao = new UserDao();

        DataSource dataSource = new SingleConnectionDataSource("jdbc:oracle:thin:@localhost:1521:xe", "springbook_test", "test", true);
        dao.setDataSource(dataSource);
    }
}

DataSource를 직접 만들어야 하지만, 애플리케이션 컨텍스트를 아예 사용하지 않으므로 코드는 더 단순해지고 이해하기 편해짐

  • 어플리케이션 컨텍스트가 만들어지지 않으므로 테스트 시간을 절약
  • UserDao가 매번 새로 만들어지는 단점도 있음

DI를 이용한 테스트 방법 선택

DI를 테스트에 이용하는 방법

  1. 스프링 컨테이너 없이 테스트할 수 있는 방법
    • 1순위로 고려
    • 테스트 수행 속도가 가장 빠르고, 테스트가 간결하다.
  2. 스프링의 설정을 이용한 DI 방식의 테스트
    • 여러 오브젝트와 복잡한 의존관계를 갖고 있는 오브젝트를 테스트해야 하는 경우
    • 테스트 전용 설정 파일을 따로 만들어 사용
  3. 컨텍스트에서 DI 받은 오브젝트에서 다시 테스트 코드로 수동 DI 해서 테스트
    • 테스트 설정을 따로 만들었지만, 예외적으로 의존관계를 강제적으로 테스트해야 하는 경우
    • DirtiesContext 애노테이션을 반드시 붙여주어야 한다.


' Spring > 토비의 스프링 3.1' 카테고리의 다른 글

2.6 정리  (0) 2019.01.17
2.5 학습 테스트로 배우는 스프링  (0) 2019.01.17
2.3 개발자를 위한 테스팅 프레임워크 JUnit  (0) 2019.01.14
2.2 UserDaoTest 개선  (0) 2019.01.11
2.1 UserDaoTest 다시보기  (0) 2019.01.11