1장 '오브젝트와 의존관계'에서 확장되고 복잡해져가는 애플리케이션을 고려한 확장을 위해 IoC/DI와 같은 기술을 배우고, 실제로 만든 서비스에 적용해보는 실습을 하였다.
2장에서는 스프링에서 가장 중요한 가치 중 하나인 테스트에 대해 배워볼 것이다.
단위 테스트 (Unit Test)
DB Connection을 진행하는 코드를 마음대로 변경하는 작업을 수행하면서, 커넥션이 잘 수행되는 지 확인하기 위해 테스트 코드를 사용할 수 있다.
아래는 사용자의 정보를 만들고, 조회하는 테스트 예제이다.
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
UserDao dao = context.getBean("userDao", UserDao.class);
User user = new User();
user.setId("user");
user.setName("백기선");
user.setPassword("married");
dao.add(user);
System.out.println(user.getId() + "등록 성공");
// 사용자 정보 수정
User user2 = dao.get(user.getId());
System.out.println(user2.getName());
System.out.println(user2.getPassword());
System.out.println(user2.getId() + "조회 성공");
}
}
테스트에 관심사의 분리 개념을 추가해서 생각해보자. 테스트하는 범위가 너무 넓다면 에러가 났을 때 어디에서 에러가 나는지 판별하기 힘들어지기 때문에, UserDaoTest처럼 한 가지 관심에 집중할 수 있게 작은 단위로 테스트를 나눌 수 있어야 한다.
이렇게 작은 단위의 테스트를 수행하는 것을 단위 테스트라고 한다.
단위 테스트를 하는 이유는 개발자가 설계하고 만든 코드가 원래 의도대로 동작하는지를 개발자 스스로가 빠르게 확인받기 위해서이다.
UserDaoTest 개선하기
위에서 소개한 UserDaoTest는 아래와 같은 두 가지 문제가 있다.
- 수동 확인 작업의 번거로움
데이터를 입력하고 등록과 조회가 잘 되는 지 확인하는 것이 사용자의 몫이 된다. 이것은 사용자가 에러를 발견하지 못하고 넘어가는 실수를 할 수 있게 된다.
- 실행 작업의 번거로움
main() 메소드를 매번 실행하는 것은 번거롭다. 좀 더 편리하고 체계적으로 테스트를 실행하고 결과를 확인하는 방법이 필요하다.
테스트 검증의 자동화
콘솔에 값을 출력하여 오브젝트의 내부 파라미터를 직접 확인한 방법에서, 오브젝트의 일치 여부를 비교해서 테스트 결과를 확인하는 방법으로 수정해보자.
JUnit Test
JUnit은 하나의 프레임워크로, 제어의 역전 원칙에 따라 애플리케이션의 흐름을 제어한다. Junit을 사용한다면, main() 메소드도 필요없고 오브젝트를 만드는 코드도 만들 필요가 없다.
JUnit을 사용하기 위해서는 메소드가 public으로 선언되고, 메소드에 @Test라는 애노테이션을 붙여주어야 한다.
@Test
public void addAndGet() throws SQLException, ClassNotFoundException {
ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
UserDao dao = context.getBean("userDao", UserDao.class);
User user = new User();
user.setId("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 테스트를 통해 DB에 접근하여 값을 넣고 조회하는 예제를 진행하다보면, DB의 데이터 중복이라던가, 연결 오류와 같은 이유로 테스트에 실패하는 경우가 있다. 그 중, 기존 값에 의해 테스트가 실패하는 경우를 방지하기 위해 아래와 같은 전처리와 후처리를 해줄 수 있다.
- 테스트를 수행하기 전에 DB의 모든 데이터를 삭제한다.
- 테스트를 마치기 직전에 데이터를 모두 원래 상태로 만들어준다.
TDD (Test Driven Development)
테스트 주도 개발이란, 테스트 코드를 먼저 작성하고 그에 맞추어 테스트를 성공하게 하는 코드를 작성하는 방식의 개발 방법이다.
테스트 코드 개선
중복되는 코드를 별도의 메소드로 뽑아, 함수가 실행될 때마다 같은 코드를 실행하도록 만들 수 있다.
중복되었던 코드를 @BeforeEach라는 어노테이션을 붙언 setUp 메소드에 추가하고, 테스트를 실행해보자.
이떄 초기화되는 객체인 dao는 테스트 메소드에서 사용될 수 있도록 테스트 클래스의 인스턴스 변수로 선언한다.
하나의 테스트 메소드가 실행할 때마다 테스트 클래스의 오브젝트는 새로 만들어진다. 각 테스트가 서로 영향을 주지 않고 독립적으로 실행되도록 보장하기 위해 이 방식을 사용하는 것이다.
테스트를 수행하는 데 필요한 정보나 오브젝트를 픽스처 (fixture) 라고 한다. 해당 코드에서는 dao가 하나의 픽스처라고 할 수 있다.
애플리케이션 컨텍스트는 초기화되고 나면 내부의 상태가 바뀌는 일은 거의 없다. 따라서 여러 테스트가 공유해서 사용해도 되지만, Junit은 테스트 클래스의 오브젝트를 매번 새로 만들기 때문에 어플리케이션 컨택스트를 오브젝트 레벨에 두면 안된다.따라서 스프링이 제공하는 애플리케이션 컨텍스트 테스트 지원 기능을 사용할 수 있다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/applicationContext.xml")
public class UserDaoTest {
@Autowired
private ApplicationContext context;
public void setUp(){
ApplicationContext context = new GenericXmlApplicationContext(
테스트를 실행하고 로그를 확인하면, context는 모두 동일한 오브젝트로 사용되는 것을 확인할 수 있다.
이를 통해 수많은 테스트 클래스를 만들더라도, 하나의 context를 공유하면서 사용함으로써 테스트 성능이 대폭 향상된다고 할 수 있다.
@Autowired란 ?
@Autowired가 붙은 인스턴스 변수는, 테스트 컨텍스트 프레임워크가 해당 변수 타입과 일치하는 Context 내의 빈을 찾게 한다. 생성자나 수정자 메소드가 없어도 주입이 가능하며, 이렇게 별도의 DI 설정 없이 필드의 타입 정보를 이용해 빈을 자동으로 가져오는 것을 타입에 의한 자동 와이어링이라고 한다.
스프링에서 애플리케이션 컨텍스트는 초기화와 동시에 자기 자신인 ApplicationContext 타입의 빈을 저장한다.
만약, 테스트 메소드에서 애플리케이션 컨텍스트의 구성이나 상태를 변경하고자 한다면 테스트 클래스 위에 @DirtiesContext 어노테이션을 붙인다.
학습 테스트 (Learning test)
다른 사람이 만든 라이브러리나 프레임워크를 테스트하는 것을 학습 테스트라고 한다. 테스트 코드를 짜보면서, 자신이 사용할 API나 라이브러리에 대한 이해도를 높이는 것이 목적이다.
학습 테스트의 장점은 아래와 같다.
- 다양한 조건에 따른 기능을 확인할 수 있다.
- 학습 테스트 코드를 개발 중에 참고할 수 있다.
학습 테스트에 대한 예시로, Junit 프레임워크를 테스트해보자.
public class JunitTest {
static JunitTest testObject;
@Test
public void test1(){
Assertions.assertThat(this).isNotEqualTo(testObject);
testObject = this;
}
@Test
public void test2(){
Assertions.assertThat(this).isNotEqualTo(testObject);
testObject = this;
}
@Test
public void test3(){
Assertions.assertThat(this).isNotEqualTo(testObject);
testObject = this;
}
}
스태틱 변수로 테스트 오브젝트를 저장할 수 있는 컬렉션을 만들어둔다. 컬렉션에 등록이 되어있는지 확인하고, 자신을 추가한다.
'DEV book > 토비의 스프링 3.1' 카테고리의 다른 글
[토비의 스프링 3.1] 3장 템플릿 (2) - JdbcContext를 UserDao에서 사용하는 두 가지 방법 (0) | 2023.09.24 |
---|---|
[토비의 스프링 3.1] 3장 템플릿 (1) - 디자인 패턴과 DI를 이용한 DAO 최적화 (0) | 2023.09.23 |
[토비의 스프링 3.1] 1장 오브젝트와 의존관계 (3) - 싱글톤 레지스트리와 의존관계 주입 (0) | 2023.09.14 |
[토비의 스프링 3.1] 1장 오브젝트와 의존관계 (2) - 제어의 역전 (IoC)과 ApplicationContext (0) | 2023.09.13 |
[토비의 스프링 3.1] 1장 오브젝트와 의존관계 (1) - 전략 패턴을 이용한 DAO 클래스 리팩토링 (0) | 2023.09.13 |