좋은 테스트란
보통 웹을 통한 테스트는 화면에서부터 서비스/MVC 계층 모두 거쳐 진행된다. 이러한 방식은 테스트 에러 혹은 실패 시 문제가 발생한 지점을 찾기 어렵다. 좋은 테스트란 관심사를 분리하여, 테스트하는 대상에만 집중한다. 이렇게 외부에 의존하지 않고(독립적으로) 작은 단위의 대상에만 집중하는 테스트를 단위 테스트라고 한다. 또한 테스트는 어플리케이션 흐름 안에 있기보다는 별도의 테스트로 존재하는 방식이 좋다.
TDD (테스트 주도 개발)
테스트 코드를 먼저 만들고, 테스트를 성공하게 하는 코드를 작성하며 개발하는 방법. 테스트 코드로 기능 명세가 될 수 있고, 안정적으로 개발할 수 있다.
JUnit
단순 print 문으로 테스트 결과를 확인하면, 값을 일일이 사람이 확인해야 한다. 이 과정에서 사람이 잘못 확인할 수 있고, 값이 많아지면 비효율적이다. 따라서 컴퓨터가 확인하는 방식인 Junit을 사용한다.
Junit은 프레임워크이다. 따라서 개발자가 main() 으로 제어권을 가져 동작시키지 않고, 프레임워크의 규칙에 따라 개발자가 필요한 코드를 넣는다. 규칙은 다음 두 가지이다.
- 메소드가 public으로 선언
- 메소드에 @Test라는 어노테이션 붙여주기
public class Test {
@Test
public void add() {
ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
...
}
}
Junit 테스트 통과 기준
JUnit에서는 예외 발생 혹은 assertThat() 통과할 경우 해당 테스트는 성공한다.
-
assertThat() : 값 비교 assertThat(user2.getName(), is(user.getName())); 두 파라미터 값이 같으면 통과, 아니면 테스트가 실패한다.
-
expected : 예외 발생 여부 확인
@Test(expected = NullPointerException.class)
expected는 @Test 어노테이션의 엘리먼트이다. expected에 발생할거라 생각하는 예외 클래스를 넣어준다. expected에서 지정한 예외 발생 시, 테스트가 성공한다. 해당 예외가 반드시 발생해야 하는 경우에 사용한다.
Junit 실행 방식
Junit은 다음의 규칙을 따른다.
- @Before와 @After은 @Test 메소드 실행 전/후에 실행된다. 하나의 테스트 클래스의 테스트 메소드들이 공통적으로 준비/마무리 작업이 필요한 경우 사용한다.
- 매 테스트 메소드마다 테스트 클래스의 오브젝트를 새로 만들고 버린다. 테스트가 독립적으로 이뤄짐을 보장한다. 메모리 부담이 없이 변수를 사용할 수 있다.
Junit 동작 과정
하나의 테스트 클래스에서 테스트를 수행하는 방식은 다음과 같다.
- 테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 모두 찾는다.
- 테스트 클래스의 오브젝트를 하나 만든다.
- @Before가 붙은 메소드가 있으면 실행한다.
- @Test가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.
- @After가 붙은 메소드가 있으면 실행한다.
- 나머지 테스트 메소드에 대해 2~5번을 반복한다.
- 모든 테스트의 결과를 종합해서 돌려준다.
Spring에서 테스트하기
스프링 컨테이너의 빈이 많아지면 ApplicationContext 객체를 생성하는 시간이 오래 걸린다. 다음의 코드는 매 테스트마다 ApplicationContext 객체 생성하는 경우이다.
public class UserDaoTest {
UserDao userDao;
@Before
public void setUp() {
ApplicationContext applicationContext = new GenericXmlApplicationContext("spring/applicationContext.xml");
this.userDao = applicationContext.getBean(UserDao.class);
// ...
}
}
이 방식은 테스트를 할 때마다 ApplicationContext 객체를 새로 생성한다. 이 객체가 생성될 때는 모든 빈이 초기화되어 시간/메모리 비효율적이다. 뿐만 아니라, 리소스를 마무리 해제해주지 않으면, 문제가 발생할 수 있다. 따라서 일반적으로 테스트할 떄는 ApplicationContext 객체를 공유한다.
다음은 모든 테스트에서 ApplicationContext 객체를 공유하는 코드이다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml")
public class UserDaoTest {
@Autowired ApplicationContext context;
@Before
public void setUp() {
this.dao = context.getBean("userDao", UserDao.class);
// ...
}
}
다음의 어노테이션들을 활용하여 모든 테스트에서 ApplicationContext 객체를 공유할 수 있도록 설정한다.
- @Autowired 테스트 오브젝트가 생성될 때마다 ApplicationContext 객체를 해당 필드(context)에 주입한다. 쉽게 말해 스프링 컨테이너의 빈을 DI한다.
- @ContextConfiguration 자동 생성할 ApplicationContext 객체의 xml파일 위치를 알려준다.
- @RunWith JUnit 프레임워크의 테스트 실행 방법을 확장한다. 테스트 시 사용할 ApplicationContext 객체를 생성 및 관리한다.
이를 통해 ApplicationContext 객체는 한 번만 생성되고, 이후에 테스트 오브젝트들이 공유해서 사용한다. 테스트 오브젝트들은 독립적으로 매 테스트마다 생성/삭제되고, ApplicationContext 객체는 모든 테스트 오브젝트들이 공유한다.