테스트 (토비의 스프링 vol.1 2장)
- 스프링이 제공하는 가장 중요한 가치는 객체지향과 테스트다
- IoC/DI를 이용해 객치지향 기술을 손쉽게 적용하도록 도와준다
- 테스트
- 코드의 확신과 변화의 유연함
- 스프링 학습에 효과적
- 웹을 이용한 DAO를 테스트
- DAO뿐만 아니라 서비스 클래스, 컨트롤러, JSP 뷰 등 모든 레이어 기능을 다 만들고 나서야 테스트가 가능
- 테스트 중 발생한 에러의 원인을 찾는 수고가 필요
- 과정이 번거롭고 오류에 대한 대처가 쉽지 않다
- UserDaoTest
public class UserDaoTest { public static void main(String[] args) throws SQLException { Applicationcontext context = new GenericXmlApplicationContext("applicationcontext.xml"); UserDao dao = context.getBean("userDao", UserDao.class); User user = new User(); user.setld("user"); user.setName("백기선"); user.setPassword("married"); dao.add(user); System.out .printin(user.getId() + " 등록 성공"); User user2 = dao.get(user.getId()); System.out.printin(user2.getName()); System.out.printin(user2.getPassword()); System.out.printin (user2.getId() + " 조회 성공"); } } -
단위 테스트(Unit Test) : 작은 단위의 코드에 대해 테스트를 수행
- 테스트에 DB가 사용되면 단위 테스트가 아닐까?
- DB의 상태를 테스트가 관장하고 있다면 단위 테스트이다
- 단위테스트가 필요한 이유
- 개발자가 설계하고 만든 코드가 원래 의도한 대로 동작하는지를 개발자 스스로 발리 확인받기 위함
- 단위가 작은 시점에 빠르게 테스트를 하여 오류를 수정하기 위함
- 자동화된 테스트
- 수작업을 거치는 방법이 아닌 코드로 만들어져 자동으로 수행되어야 한다
- 자주 반복할 수 있다
- UserDaoTest의 문제점
- 수동 확인 작업의 번거로움
- 실행 작업의 번거로움
UserDaoTest 개선
두 문제점을 개선해보자
- 검증 자동화된 UserDaoTest
//수정 전 System.out.printin(user2.getName()); System.out.println(user2.getPassword()); System.out.printin (user2.getId() + " 조회 성공"); //수정 후 if (!user.getName().equals(user2.getName())) { System.out .printin("테스트 실패 (name)"); } else if (!user.getPassword().equals(user2.getPassword())) { System.out .printin("테스트 실패 (password)"); } else { System.out.println ("조회 테스트 성공"); } - JUnit : 독립된 단위테스트를 지원해주는 자바 테스팅 프레임워크
- 테스트용 메소드의 조건
- public으로 선언
- @Test 어노테이션 사용
- assertThat()
- JUnit이 제공하는 메소드
- 첫 번째 파라미터(actual)와 뒤에 오는 matcher와 비교
- actual : 테스트 대상의 실제 값
- matcher : 기대하는 값이나 조건
- 테스트용 메소드의 조건
- JUnit을 적용한 UserDaoTest
import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; ... public class UserDaoTest { @Test public static void addAndGet() throws SQLException { Applicationcontext context = new GenericXmlApplicationContext("applicationcontext.xml"); UserDao dao = context.getBean("userDao", UserDao.class); User user = new User(); user.setld("user"); user.setName("백기선"); user.setPassword("married"); dao.add(user); User user2 = dao.get(user.getId()); assertThat(user2.getName(), is(user.getName())); assertThat(user2.getPassword(), is(user.getPassword())); } } - JUnit 테스트를 실행하는 메소드
import org.junit.runner.JUnitCore; ... public static void main(String[] args) { JUnitCore.main("springbook.user.dao.UserDaoTest"); } - 단위 테스트는 일관성있는 결과가 보장돼야 한다
- DB에 남아있는 데이터와 같은 외부 환경에 영향을 받지 말아야 함
- 실행 순서를 바꿔도 동일한 결과를 보장해야 함
테스트가 이끄는 개발
- 테스트 주도 개발(TDD, Test Driven Develop)
- “실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다”
- 테스트가 만들고자 하는 기능의 내용을 담고있음
- 당연히 만들어진 코드를 검증할 수도 있음
- 테스트 개발 -> 테스트를 위한 기능 구현
- 코드를 만들고 테스트를 실행하는 그 사이의 간격이 짧다
- 오히려 개발속도가 빠르다
- JUnit이 제공하는 기능을 통한 테스트 코드 개선
- @Before : @Test 메소드가 실행되기 전에 먼저 실행되어야 하는 메소드를 정의한다
- @After : @Test 메소드가 실행된 후에 실행되어야 하는 메소드를 정의한다
- @Before 활용
import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; ... public class UserDaoTest { @Before public void setUp() { Applicationcontext context = new GenericXmlApplicationContext("applicationcontext.xml"); this.dao = context.getBean("userDao", UserDao.class); } @Test public static void addAndGet() throws SQLException { User user = new User(); user.setld("user"); user.setName("백기선"); user.setPassword("married"); dao.add(user); User user2 = dao.get(user.getId()); assertThat(user2.getName(), is(user.getName())); assertThat(user2.getPassword(), is(user.getPassword())); } } - JUnit은 테스트 메소드를 실행할 때마다 테스트 클래스의 오브젝트를 새로 만든다
-
픽스처(Fixture) : 테스트를 수행하는 데 필요한 정보나 오브젝트
- @Before를 사용한 ApplicationContext 생성방식
- Bean이 많아지고 복잡해지면 시간이 오래 걸릴 수 있다
- ApplicationContext가 만들어질 때 모든 싱글톤 빈 오브젝트를 초기화한다
- 어떤 빈은 독자적으로 많은 리소스를 할당하거나 독립적인 스레드를 띄우기도 한다
- 한 번만 만들고 공유해서 사용하자
- Bean이 많아지고 복잡해지면 시간이 오래 걸릴 수 있다
- Junit을 이용하는 테스트 컨텍스트 프레임워크
- @RunWith : 프레임워크의 테스트 실행 방법을 확장할 때 사용
- @ContextConfiguration : 자동으로 만들어줄 애플리케이션 컨텍스트의 설정파일 위치를 지정
- @Autowired : 스프링 컨텍스트에서 같은 타입의 빈을 주입해준다
- 스프링 테스트 컨텍스트
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="/applicationContext.xml") public class UserDaoTest { @Autowired private ApplicationContext context; ... @Before public void setUp() { this.dao = this.context.getBean("userDao", UserDao.class); } - 다른 클래스와의 공유
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="/applicationContext.xml") public class UserDaoTest { .. } // 서로 공유 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="/applicationContext.xml") public class GroupDaoTest { .. }
DI와 테스트
인터페이스 구현 클래스를 절대로 바꾸지 않는다고 해도 DI를 사용해야할까?
- 소프트웨어 개발에서 절대로 바뀌지 않는 것은 없다
- 클래스의 구현 방식은 바뀌지 않는다고 하더라도 인터페이스를 두고 DI를 적용하게 해두면 다른 차원의 서비스 기능을 도입할 수 있다
- 효율적인 테스트를 손쉽게 만들기 위해
테스트에서의 DI 활용
- 수동 DI를 통한 의존관계 변경
- 수동DI를 통한 테스트용 DataSource 사용
@DirtiesContext public class UserDaoTest { @Autowired UserDao dao; @Before public void setUp() { ... DataSource dataSource = new SingleConnectionDataSource("jdbc:mysql://localhost/testdb", "spring", "book", true); dao.setDataSource(dataSource); }문제점 : 스프링 테스트 컨텍스트 프레임워크가 적용되어 모든 테스트가 하나의 애플리케이션 컨텍스트를 공유한다 해결 : @Dirtiescontext : 해당 클래스의 테스트에서 애플리케이션 컨텍스트의 상태를 변경한다는 것을 스프링의 테스트 컨텍스트 프레임워크에게 알려줌
- 수동DI를 통한 테스트용 DataSource 사용
- 테스트 전용 설정파일 생성
- @ContextConfiguration 수정
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="/test-applicationContext.xml") public class UserDaoTest { ... }
- @ContextConfiguration 수정
- 컨테이너 없는 DI 테스트
- @ContextConfiguration 수정 ```java public class UserDaoTest { UserDao dao; …
@Before public void setUp() { … dao = new UserDao(); DataSource dataSource = new SingleConnectionDataSource(…); dao.setDataSource(dataSource); } ```
- 오브젝트를 직접 생성하는 번거로움은 있지만 애플리케이션 컨텍스트를 아예 사용하지 않으니 더 단순해지고 이해하기 편해짐
- 학습 테스트(Learning Test) : 자신이 만들지 않은 기능에 대한 테스트를 작성하며 API나 프렘이워크의 사용법을 학습
- 다양한 조건에 따른 기능을 손쉽게 확인해 볼 수 있다
- 학습 테스트 코드를 개발 중에 참고할 수 있다
- 프레임워크나 제품을 업그레이드 할 때 호환성 검증을 도와준다
- 테스트 작성에 대한 좋은 훈련이 된다
- 새로운 기술을 공부하는 과정이 즐거워진다
- 버그 테스트(Bug Test) : 코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트
- 버그가 발생하는 코드를 작성하고 그것을 해결하도록 코드를 수정
- 테스트의 완성도를 높여준다
- 버그의 내용을 명확하게 분석하게 해준다
- 기술적인 문제를 해결하는 데 도움이 된다
댓글남기기