-
2.4 스프링 테스트 적용 ~ 2.6 정리Java & Spring/토비의 스프링 3.1 2021. 6. 3. 14:32반응형
2장 테스트
2.4 스프링 테스트 적용
앞서 작성한 테스트 코드에서
@Before
(Junit5는BeforeEach
)로 인해 Application Context가 테스트 메소드 개수만큼 생성되는 문제점이 존재- Application Context 생성에는 많은 시간과 자원이 소모
- 테스트 전체가 공유하는 오브젝트로 수정
- 빈은 싱글톤으로 만들었기 때문에 무상태성
- UserDao 빈을 가져다 메소드를 사용한다고 해서 상태가 바뀌지 않음
@Before(Junit5 -> @BeforeEach)
를@BeforeClass(Junit5 -> @BeforeAll)
로 수정- 테스트 클래스 전체에 걸쳐 딱 한번만 실행
테스트를 위한 Application Context 관리
- Junit을 이용하는 테스트 컨텍스트 프레임워크를 제공
@Autowired
를 이용해서 빈 주입- Autowired
- 스프링의 DI에 사용되는 애노테이션
- 변수 타입과 일치하는 컨텍스트 내의 빈을 찾아 주입
- 같은 타입의 빈이 두 개 이상 있는 경우에는 타입만으로 결정할 수 없음
- 변수 이름과 같은 이름의 빈을 찾아 주입
- Autowired
- Test Class에는
@Runwith
와@ContextConfiguration(location="path")
를 붙임- Runwith
- 테스트 실행 방법을 확장 ~> JUnit이 테스트 진행 중, 테스트가 사용할 Application Context를 만들고 관리
- ContextConfiguration
- Application Context의 설정 파일 위치를 지정
- 테스트와 운영 DB를 공유하는 경우, 올바른 테스트 및 운영에 어려움이 있음
- Runwith
- 테스트 메소드의 컨텍스트 공유
- Junit 확장기능은 테스트 실행 전, 한 번만 Application Context를 만들어두고, 테스트 오브젝트가 만들어질 때마다 특별한 방법을 이용해서 Application Context 자신을 테스트 오브젝트의 특정 필드에 주입(일종의 DI)
- 테스트 클래스의 컨텍스트 공유
- 스프링은 테스트 클래스 사이에서도 Application Context를 공유하게 해줌
- 같은 설정파일 사용 ~> 단 한 개의 Application Context 생성 & 공유
- 스프링 Application Context는 초기화할 때, 자기 자신도 빈으로 등록
DI와 테스트
인터페이스를 두고 DI를 적용해야하는 이유
소프트웨어 개발에서 절대로 바뀌지 않는 것은 없다
- 수정에 대한 비용 부담을 줄일 수 있음
인터페이스를 두고 DI를 적용하면, 다른 차원의 서비스 기능 도입 가능
- 1장에서 학습했던, DB 커넥션 개수를 카운팅하는 부가기능이 예시
- 새로운 기능을 넣거나 제거하기 위해 기존 코드는 전혀 수정할 필요가 없음
효율적인 테스트를 손쉽게 만들기 위해
테스트 코드에 의한 DI
- DB Connection과 관련된 Configuration을 수정할 때, 테스트 중 DAO가 사용할 DataSource 오브젝트를 바꿔주는 방법
@Before(Junit5 -> @BeforeEach)
메소드에서 준비된 테스트용 DataSource 오브젝트를 생성하고 Application Context에서 가져온 Dao 오브젝트의 setDataSource를 통해 DI를 해줄 수 있음- 이 경우,
@DirtiesContext
를 클래스에 붙여줘야함- 테스트 메소드에서 Application Context의 구성이나 상태를 변경한다는 것을 의미
- 이 경우, Application Context의 공유를 허용하지 않음
- Application Context 구성이나 상태를 테스트 내에서 변경하지 않는 것이 원칙이므로 사용에 주의
- 의존 관계를 강제로 변경 ~> Application Context는 테스트 중 하나만 만들어지고 모든 테스트에서 공유하기 때문
- DB Connection과 관련된 Configuration을 수정할 때, 테스트 중 DAO가 사용할 DataSource 오브젝트를 바꿔주는 방법
테스트를 위한 별도의 DI 설정
- 테스트에서 사용될 DataSource 클래스가 빈으로 정의된 테스트 전용 설정파일을 따로 만들어두는 방법을 이용
- 번거롭게 수동 DI하는 코드나
@DirtiesContext
가 필요 없음
- 번거롭게 수동 DI하는 코드나
- 테스트에서 사용될 DataSource 클래스가 빈으로 정의된 테스트 전용 설정파일을 따로 만들어두는 방법을 이용
컨테이너 없는 DI 테스트
스프링 컨테이너를 이용해서 IoC방식으로 생성되고 DI 되도록 하는 대신, 테스트 코드에서 직접 오브젝트를 만들고 DI해서 사용해도 됨
public class UserDaoTest { UserDao dao; // @Autowired가 없음 @BeforeEach public void setUp() { // ... dao = new UserDao(); DataSource dataSource = new SingleConnectionDataSource(~); dao.setDataSource(dataSource); } }
코드가 단순해지고 테스트시간 절약
- Application Context가 만들어지는 번거로움이 없어짐
- 매번 새로운 테스트 오브젝트를 만들기 때문에, 새로운 UserDao 오브젝트가 만들어지지만, Application Context에 비해 부담이 없음
- 스프링 API에 의존하지 않고 자신의 관심에만 집중해서 깔끔하게 테스트 가능
DI를 이용한 테스트 방법
- 항상 스프링 컨테이너 없이 테스트할 수 있는 방법을 우선적으로 고려
- 수행 속도가 가장 빠르고 간결
- 복잡한 의존관계를 갖고 있는 오브젝트 테스트 ~> 스프링 설정을 이용한 DI 방식
- 예외적인 의존관계를 강제로 구성한 테스트 ~> 수동 DI 테스트
@DirtiesContext
를 꼭 붙일 것을 잊지말자
- 항상 스프링 컨테이너 없이 테스트할 수 있는 방법을 우선적으로 고려
2.5 학습 테스트로 배우는 스프링
학습 테스트
- 라이브러리 등을 테스트하는 경우
- 기능을 제대로 이해하고 있는지, 사용 방법을 알고 있는지 검증
학습 테스트의 장점
- 다양한 조건에 따른 기능을 손쉽게 확인 가능
- 학습 테스트 코드를 개발 중에 참고할 수 있다.
- 다양한 기능과 조건에 대한 테스트 코드를 개별적으로 만들고 남겨두면, 샘플 코드로 참고 가능
- 프레임워크나 제품을 업그레이드할 때 호환성 검증을 도움
- 테스트 작성에 대한 좋은 훈련이 됨
- 새로운 기술을 공부하는 과정이 즐거워짐
- 당장 적용할 일부 기능의 사용법을 익히기 위해서만이 아니라, 새로운 프레임워크나 기술을 전반적으로 공부하는 과정에서도 유용
- 참고할 수 있는 가장 좋은 소스는 스프링 자신에 대한 테스트코드
학습 테스트 예제
@Runwith(SpringJunit4ClassRunner.class) @ContextConfiguration public class JunitTest { @Autowired ApplicationContext context; static Set<JunitTest> testObjects = new HashSet<>(); static ApplicationContext contextObject = null; @Test public void test1() { assertThat(testObjects, not(hasItem(this))); testObjects.add(this); assertTrue(contextObject == null || contextObject == this.context); contextObject = this.context; } @Test public void test2() { assertThat(testObjects, not(hasItem(this))); testObjects.add(this); assertThat(contextObject, either(is(nullValue())).or(is(this.contet))); contextObject = this.context; } }
- is()
- 타입만 일치하면 어떤 값이든 검증 가능
- is()
버그 테스트
- 코드에 오류가 있을 때, 그 오류를 가장 잘 드러내줄 수 있는 테스트
- 필요성 및 장점
- 테스트의 완성도를 높여줌
- 불충분한 테스트를 보완
- 비슷한 문제 발생 시, 쉽게 추적 가능
- 내용을 명확하게 분석해줌
- 어떤 이유 때문에 발생했는지 효과적으로 분석 가능
- 또한, 함께 발생하는 사이드이펙트를 함께 발견할 수 있음
- 기술적인 문제를 해결하는데 도움이 됨
- 기술적으로 다루기 힘든 버그 발견에 도움
- 테스트의 완성도를 높여줌
2.6 정리
- 침투적 기술과 비침투적 기술
- 침투적 기술
- 기술을 적용했을 때, Application 코드에 기술 관련 API가 등장하거나, 특정 인터페이스나 클래스를 사용하도록 강제하는 기술
- 해당 기술에 종속됨
- 비침투적 기술
- Application 로직을 담은 코드에 영향을 주지 않고 적용 가능
- 순수한 코드 유지
- 스프링이 대표적인 비침투적 기술의 예시
- 침투적 기술
- 동등분할
- 같은 결과를 내는 값의 범위를 구분 ~> 대표 값으로 테스트하는 방법
- 경계값 분석
- 에러가 동등분할 범위의 경계에서 주로 많이 발생 ~> 경계 근처에 있는 값을 이용해 테스트
- 테스트 코드 작성
- 자동화돼야 하고, 빠르게 실행할 수 있어야한다.
- 항상 결과가 일관돼야한다.
- 포괄적으로 작성해야한다. ~> 충분한 검증을 하지 않는 테스트는 없는 것 보다 나쁨
- 테스트하기 쉬워야하며, 적절한 리팩토링이 필요하다
- 오류가 발견될 경우에 대한 테스트를 만들어 두면 유용하다.
- 이를 응용해서 TDD를 진행하면 좋음
반응형'Java & Spring > 토비의 스프링 3.1' 카테고리의 다른 글
3장) 3.4 컨텍스트와 DI ~ 3.5 템플릿과 콜백 (0) 2021.06.11 3장) 3.1 다시 보는 초난감 DAO ~ 3.3 JDBC 전략 패턴의 최적화 (0) 2021.06.11 2장) 2.3 개발자를 위한 테스팅 프레임워크 JUnit (0) 2021.06.01 2장) 2.1 USERDAOTEST 다시보기 ~ 2.2 USERDAOTEST 개선 (0) 2021.05.27 1장) 1.8 XML을 이용한 설정 ~ 1.9 정리 (0) 2021.05.27