2. 테스트 - 스프링 테스트 적용

이유석·2022년 5월 11일
0

Book - Toby's Spring

목록 보기
12/20
post-thumbnail

스프링 테스트 적용

테스트 코드 정리 후, 아직 수정해야 할 부분이 있다.

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

  • @Before 메소드가 테스트 메소드 개수만큼 반복되기 때문에, 애플리케이션 컨텍스트도 3번 생성된다.
  • 또한 테스트가 종료될 때, 애플리케이션 컨텍스트 내의 빈이 할당한 리소스 등을 깔끔하게 정리해주어야 한다.

테스트는 가능한 독립적으로 매번 새로운 오브젝트를 만들어서 사용하는 것이 원칙이다.
하지만 애플리케이션 컨텍스트처럼 생성에 많은 시간과 자원이 소모되는 경우에는 테스트 전체가 공유하는 오브젝트를 만들기도 한다.

JUnit의 @BeforClass 스태틱 메소드

  • JUnit은 매번 테스트 클래스의 오브젝트를 새로 만든다.
  • 스태틱 필드에 애플리케이션 컨텍스트를 저장해두는 방식으로 해결이 가능하다.

하지만 이보다는 스프링이 직접 제공하는 애플리케이션 컨텍스트 테스트 지원 기능을 사용하는 것이 더 편리하다.

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

스프링은 JUnit을 이용하는 테스트 컨텍스트 프레임워크를 제공한다.

  • 간단한 애노테이션 설정만으로 테스트에서 필요로 하는 애플리케이션 컨텍스트를 만들어 모든 테스트가 공유하게 할 수 있다.

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

  • @Before 메소드에서 애플리케이션 컨텍스트를 생성하는 코드를 제거한다.
  • ApplicationContext 타입의 인스턴스 변수를 선언한다.
  • class 레벨에 @RunWith 와 @ContextConfiguration 애노테이션을 아래와 같이 추가한다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/applicationContext.xml")
public class main {

    @Autowired
    private ApplicationContext context;
	...
}

인스턴스 변수인 context는 어디에서도 초기화해주는 코드가 없어도, 해당 테스트는 모두 성공한다.

  • @RunWith : JUnit 프레임워크의 테스트 실행 방법을 확장할 때 사용하는 애노테이션이다.
    • SpringJUnit4ClassRunner 클래스를 지정해주면, JUnit이 테스트를 진행하는 중에 테스트가 사용할 애플리케이션 컨텍스트를 만들고 관리하는 작업을 진행한다.
  • @ContextConfiguration : 자동으로 만들어줄 애플리케이션 컨텍스트의 설정 파일의 위치를 지정한다.

추가할 라이브러리 : org.springframework.test-3.0.7.RELEASE.jar

이렇게 해서 하나의 테스트 클래스 내의 테스트 메소드는 동일한 애플리케이션 컨텍스트를 공유해서 사용할 수 있음을 확인했다.


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

여러 개의 테스트 클래스가 모두 같은 설정파일을 가진 애플리케이션 컨텍스트를 사용한다면,

  • 스프링은 테스트 클래스 사이에서도 애플리케이션 컨텍스트를 공유하게 해준다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/applicationContext.xml")
public class UserDaoTest { ... }

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/applicationContext.xml")
public class GroupDaoTest { ... }

@Autowired

정의

  • @Autowired는 스프링의 DI에 사용되는 특별한 애노테이션이다.

설명

  • @Autowired가 붙은 인스턴스 변수가 있으면, 컨텍스트 프레임워크는 변수 타입과 일치하는 컨택스트 내의 빈을 찾습니다.
    타입이 일치하는 빈이 있으면, 인스턴스 변수에 주입합니다.

특징

  • 생성자나 수정자 메소드 같은 메소드가 없어도 주입이 가능합니다.
  • 타입에 의한 자동 와이어링 : 별도의 DI 설정 없이 변수의 타입 정보를 이용해 빈을 자동으로 가져옵니다.

추후에 더 자세히 다룰 예정입니다.

@Autowired 를 이용해 컨텍스트를 가져와 getBean()을 사용하는 것이 아닌, UserDao 빈을 직접 DI 받아보자.

...
public class main {
	@Autowired
    UserDao userdao;
}

@Autowired를 이용하면 어떤 빈이든 다 가져올 수 있다.
단, @Autowired는 같은 타입의 빈이 두 개 이상 있는 경우에는 타입만으로 어떤 빈을 가져올지 결정할 수 없다.
이런 경우, 해당 인스턴스 변수의 이름과 같은 이름의 빈이 있는지 확인한다.

2.4.2 DI와 테스트

UserDao와 DB 커넥션 생성 클래스 사이에 DataSource라는 인터페이스를 두었다.

  • UserDao는 자신이 사용하는 오브젝트의 클래스가 무엇인지 알 필요가 없다.
  • DI를 통해 외부에서 사용할 오브젝트를 주입받기 때문에, 오브젝트 생성에 대한 부담이 없다.
  • 코드의 수정 없이도 얼마든지 의존 오브젝트를 바꿔가며 사용할 수 있다.

하지만 DataSource의 구현 클래스를 SimpleDriverDataSource로 고정 후 바꾸지 않는다면, DataSource 인터페이스를 사용하고 DI를 통해 주입해주는 방식을 이용해야 할 까?

그래도 인터페이스를 두고 DI를 적용해야 하는 이유가 있다.

  • 소프트웨어 개발에서 절대로 바뀌지 않는 것은 없기 때문이다.
  • 다른 차원의 서비스 기능을 도입할 수 있기 때문이다.
  • 효율적인 테스트를 손쉽게 만들기 위해서이다.
    • DI는 테스트가 작은 단위의 대상에 대해 독립적으로 만들어지고 실행되게 하는데 중요한 역할을 한다.

테스트 코드에 의한 DI
테스트 코드 내에서 수정자 메소드를 이용해서 직접 DI해도 된다.

  • DI컨테이너가 의존관계 주입에 사용하도록 만들어진 수정자 메소드는 평벙한 자바 메소드이므로 테스트 코드에서도 사용할 수 있기 때문이다.

애플리케이션이 사용할 applicationContext.xml에 정의된 DataSource 빈은 서버의 DB 풀 서비스와 연결해서 운영용 DB 커넥션을 돌려주도록 만들어져 있다고 해보자.

  • 테스트할 때 해당 DataSource를 사용하면 안된다.
  • 즉 테스트 코드에 의한 DI를 이용해서 테스트 중에 DAO가 사용할 DataSource 오브젝트를 바꿔주는 방법을 이용하여 테스트용 DataSource를 사용하도록 한다.
@DirtiesContext // 테스트 메소드에서 애플리케이션 컨텍스트의 구성이나 상태를 변경한다는 것을 테스트 컨텍스트 프레임워크에 알려준다.
public class main {
	...
    
    @Before
    public void setUp() {

        this.userDao = context.getBean("userDao", UserDao.class);

        DataSource dataSource = new SingleConnectionDataSource(
                "jdbc:mysql://localhost:3306/testdb?useSSL=false", "id", "pw", true
        );

        userDao.setDataSource(dataSource); // 수정자 메소드에 의한 수동 DI

장점 : XML 설정 파일을 수정하지 않고도 테스트 코드를 통해 오브젝트 관계를 재구성할 수 있다.

주의할 점 : 애플리케이션 컨텍스트에서 구성된 오브젝트를 가져와 의존관계를 강제로 변경하였다는 것 이다.

  • 스프링 테스트 컨텍스트 프레임워크를 적용했다면, 애플리케이션 컨텍스트는 딱 하나만 만들어져야 한다. 또한 변경하지 않는 것이 원칙이다.
  • 한 번 변경하면 해당 컨텍스트를 공유하는 모든 테스트도 변경된 애플리케이션 컨텍스트를 사용한다.

이를 해결하기 위해 클래스 레벨에 @DirtiesContext 라는 애노테이션을 추가해줬다.

  • 스프링의 테스트 컨텍스트 프레임워크에게 해당 클래스의 테스트에서 애플리케이션 컨텍스트의 상태를 변경한다는 것을 알려준다.
  • 테스트 컨텍스트는 이 애노테이션이 붙은 테스트 클래스에는 애플리케이션 컨텍스트 공유를 허용하지 않는다.
  • 즉 테스트 메소드를 수행하고 나면 매번 새로운 애플리케이션 컨텍스트를 만들어 다음 테스트가 사용하게 해준다.

테스트를 위한 별도의 DI 설정
앞의 방법(테스트 코드에서 빈 오브젝트에 수동으로 DI하는 방법)은 장점보다 단점이 많다.

해당 방법 외에 DI의 장점을 살려서 DAO가 테스트에서만 다른 DataSource를 사용하게 할 수 있는 방법이 있다.

테스트에서 사용될 DataSource 클래스가 빈으로 정의된 테스트 전용 설정파일을 따로 만들어 이용한다.

  • 기존의 applicationContext.xml 파일을 복사하여 test-applicationContext.xml 파일을 생성한다.
  • dataSource 빈 부분을 아래와 같이 변경해준다.
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/testdb?useSSL=false" />
        <property name="username" value="id" />
        <property name="password" value="pw" />
    </bean>
  • 그 후, main 클래스의 @ContextConfiguration의 locations 속성의 값을 "/test-applicationContext.xml" 으로 변경해준다.

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

  • @RunWith를 사용해서 스프링 테스트 프레임워크를 적용하지 않았다.
  • @Autowired를 사용해서 애플리케이션 컨텍스트에서 UserDao 의 빈을 가져오지 않았다.
  • @Before 메소드에서 직접 UserDao 오브젝트와 테스트용 DataSource 오브젝트를 생성하였다.
  • 그 후 직접 DI 해주었다.
public class main {
    private UserDao userDao;

    private User user1;
    private User user2;
    private User user3;


    @Before
    public void setUp() {
        this.userDao = new UserDao();
        DataSource dataSource = new SingleConnectionDataSource(
                "jdbc:mysql://localhost:3306/testdb?useSSL=false", "root", "2db8ddasf", true
        );

        userDao.setDataSource(dataSource);
     }
 }

DI를 이용한 테스트 방법 선택
DI를 테스트에 이용하는 세 가지 방법을 보았다.

  • 테스트 코드에 의한 수동 DI
  • 테스트를 위한 별도의 DI 설정
  • 컨테이너 없는 DI 테스트

세가지 방법 모두 장단점이 있고 상황에 따라 유용하게 쓸 수 있다.

항상 스프링 컨테이너 없이 테스트할 수 있는 방법을 가장 우선적으로 고려하자.

  • 테스트 수행 속도가 가장 빠르고 간단하다.
  • 테스트를 위해 필요한 오브젝트의 생성과 초기화가 단순하다면 이 방법을 먼저 고려하자.

스프링의 설정을 이용한 DI 방식을 고려하는 경우

  • 여러 오브젝트와 복잡한 의존관계를 갖고 있는 오브젝트를 테스트할 경우
  • 보통 개발환경, 운영환경, 테스트환경에 맞는 전용 설정파일을 생성하여 사용한다.

컨텍스트에서 DI받은 오브젝트에 다시 테스트 코드로 수동 DI해서 테스트하는 방식을 고려하는 경우

  • 예외적인 의존관계를 강제로 구성해서 테스트해야할 경우
  • @DirtiesContext 애노테이션을 붙이는 것을 잊지 말자.

소스 코드 : github

profile
https://github.com/yuseogi0218

0개의 댓글