
2장 테스트
만들어진 코드를 확신할 수 있게 해주고, 변화에 유연하게 대처할 수 있게 자신감을 주는 기술
웹을 통한 DAO 테스트 방법의 문제점
- 서비스 클래스, 컨트롤러, JSP 뷰 등 모든 레이어의 기능을 다 만든 후에야 테스트가 가능
- 에러 메시지와 호출 스택 정보만으로 원인 찾기 힘듦
- DAO를 테스트 하려고 만든 다른 레이어의 코드 때문에 에러가 날 수 있음
- 테스트 하고 싶은 대상에 다른 계층의 코드, 컴포넌트 서버의 설정 상태가 모두 영향을 줌
작은 단위의 테스트
가능한 작은 단위로 쪼개서 집중 -> 관심사의 원리 적용
UserDaoTest 를 수행할 때 웹 인터페이스, 이를 위한 MVC 클래스, 서비스 오브젝트, 서버 배포 필요가 없음
-> 단위 테스트(Unit Test)
- 통제할 수 없는 외부의 리소스에 의존하지 않음
단위 테스트를 하는 이유
- 코드가 의도한 대로 동작하는 지 개발자 스스로 빨리 확인받기 위해
- 확인의 대상과 조건이 간단하고 명확할수록 좋음
- 그래서 작은 단위로 테스트
자동수행 테스트 코드
테스트 수행에 부담이 없도록 자동화가 되어야 함
-> 자주 반복할 수 있기 때문에 언제든 코드 수정 후 테스트가 가능
지속적인 개선과 점진적 개발을 위한 테스트
- 새로운 기능이 의도대로 동작하는 지 확인 + 기존 기능이 새로운 코드에 영향 받지 않고 잘 동작하는 지 확인
- 테스트를 수행하는 동안 확신을 가지고 코드를 변경 -> 코드 개선 속도 빨라짐
UserDaoTest 의 문제점
- 수동 확인 작업의 번거로움
- 테스트 결과를 사람의 눈으로 확인하는 과정이 필요하다.
- 실행 작업의 번거로움
- main() 메소드를 매번 실행하는 것은 번거롭고, DAO 가 수백 개가 된다면 전체 기능을 테스트 해보기 위해 main() 메소드 수백 번 실행해야 함
JUnit 테스트
Junit: 단위 테스트를 위한 프레임워크
조건
- public 메소드 선언
- 메소드에 @Test 어노테이션 붙임
- assertThat() 메소드
- 첫 번째 파라미터의 값을 뒤에 나오는 매처라고 불리는 조건으로 비교해서 일치하면 다음으로, 아니면 테스트가 실패하도록 만듦
if (!user.getName().equals(user2.getName)) {...}
==
assertThat(user2.getName), is(user.getName());
Junit 테스트 수행 7단계
- 테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 모두 찾는다.
- 테스트 클래스의 오브젝트를 하나 만든다.
- @Before가 붙은 메소드가 있으면 실행한다.
- @Test가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.
- @After가 붙은 메소드가 있으면 실행한다.
- 나머지 테스트 메소드에 대해 2~5번을 반복한다.
- 모든 테스트의 결과를 종합해서 돌려준다
- 테스트 메소드 실행마다 새로운 오브젝트 생성
- 각 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 보장해주기 위해
- 테스트 메소드의 일부에서만 공통적으로 사용되는 코드가 있다면? 2가지 방법
- @Before 보다는, 일반적인 메소드 추출 방법을 써서 메소드를 분리하고 테소트 메소드에서 직업 호출해 사용한다.
- 공통적인 특징을 지닌 테스트 메소드를 모아서 별도의 테스트 클래스로 만든다.
픽스처
- 테스트를 수행하는 데 필요한 정보나 오브젝트
- 여러 테스트에서 반복적으로 사용되기 때문에 @Before 메소드를 이용해 생성해두면 편함
동일한 결과를 보장하는 테스트
- 단위 테스트는 항상 일관성 있는 결과가 보장되어야 함
- DB에 남아 있는 데이터와 같은 외부 환경에 영향 받지 X
- 테스트를 실행하는 순서를 바꿔도 동일한 결과 보장
예외상황 테스트
@Test(expected=EmptyResultDataAccessExceptiom.class)
테스트 중에 발생할 것으로 기대하는 예외 클래스를 지정한다.
->정상적으로 테스트 메소드 마치면 테스트 실패
->expected가 지정한 예외가 나오면 테스트 성공
테스트 주도 개발(TDD)
- 테스트 코드를 먼저 작성하고, 테스트를 성공하게 해주는 기능의 내용을 담은 코드를 작성함
- 테스트 코드는 하나의 잘 작성된 기능 정의서
- 테스트가 실패하면, 설계한 대로 코드가 만들어지지 않은 것
- 테스트가 성공하면, 그 순간 코드 구현과 테스트라는 두 가지 작업이 동시에 끝남
장점
- 테스트를 빼먹지 않고 꼼꼼히 만들어낼 수 있다.
- 테스트 작성 시간과 애플리케션 코드 작성 시간의 간격이 짧아진다.
- 코드에 대한 피드백이 빨라진다.
사실 개발자의 머릿속에서는 테스트를 진행 하면서 코딩하는 것.
-> 머릿속의 시뮬레이션을 실제 테스트 코드로 끄집어내는 것이다.
테스트를 위한 애플리케이션 컨텍스트 관리
애플리케이션 컨텍스트가 만들어 질 때 모든 싱글톤 빈 오브젝트를 초기화 함.
단순히 빈 오브젝트 생성이 아닐 때, 발생 할 수 있는 문제점
- 어떤 빈은 오브젝트가 생성될 때 자체적인 초기화 작업 진행해서 많은 시간 필요로 함
- 애플리케이션 컨텍스트가 초기화 될 때 어떤 빈은 독자적으로 많은 리소스를 할당하거나 독립적인 쓰레드를 띄움
테스트는 가능한 독립적으로 새로운 오브젝트를 만들어서 사용하는 것이 원칙
-> 애플리케이션 컨텍스트처럼 생성에 많은 시간과 자원이 소모되는 경우에는 테스트 전체가 공유하는 오브젝트 만듦
-> 애플리케이션 컨텍스트는 초기화 되고 나면 내부의 상태가 바뀌는 일은 거의 없기 때문에 여러 테스트가 공유해서 사용 가능
-> But, Junit은 매번 테스트 클래스의 오브젝트 새로 만듦
-> @BeforeClass 스태틱 메소드를 통해 애플리케이션 컨텍스트를 스태틱 변수에 저장할 수 있음
-> 이보다는 스프링이 직접 제공하는 지원 기능 사용하는 것이 편리함
클래스 레벨에 추가@RunWith : JUnit 프레임워크의 테스트 실행 방법 확장할 때 사용 @ContextConfiguration : 테스트 컨텍스트가 자동으로 만들어줄 애플리케이션 컨텍스트의 설정파일 위치 지정
테스트 클래스의 컨텍스트 공유
- 테스트 클래스가 같은 설정 파일을 가진 애플리케이션 컨텍스트 사용하면, 공유하게 해줌
- 테스트 클래스마다 다른 설정 파일도 ok, 설정파일 종류만큼 컨텍스트 만들 수 있음
@AutoWired
- @Autowired가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다
- 타입이 일치하는 빈이 있으면 인스턴스 변수에 주입해준다.
- 일반적으로 주입을 위해서는 생성자, 수정자 등의 메소드 필요하지만 이 경우에는 메소드 없어도 주입이 가능
- 별도의 DI 설정 없이 필드의 타입정보를 이용해 빈을 자동으로 가져올 수 있음 (타입에 의한 자동 와이어링)
- 애플리케이션 컨텍스트는 초기화할 때 자기자신도 빈으로 등록, 따라서 ApllicationContext 타입의 빈 존재, AutoWired 통해 DI 가능
- @Autowired는 변수에 할당 가능한 타입을 가진 빈을 자동으로 찾는다.
- 같은 타입의 빈이 두 개 이상 있는 경우에는 타입만으로는 어떤 빈을 가져올지 결정 X
- 타입으로 가져올 빈 하나를 선택할 수 없는 경우에 변수의 이름과 같은 이름의 빈이 있는지 확인한다.
인터페이스를 두고 DI 적용해야 하는 이유
- 소프트웨어 개발에서 절대 바뀌지 않는 것은 없다.
- 만약 클래스의 구현 방식은 바뀌지 않는다 해도, 다른 차원의 서비스 기능을 도입을 쉽게 할 수 있다.
- 효율적인 테스트를 손쉽게 만들기 위해서다.
@DirtiesContext
- 스프링의 테스트 컨텍스트 프레임워크에게 해당 클래스의 테스트에서 애플리케이션 컨텍스트의 상태를 변경한다는 것을 알려줌
- 테스트 컨텍스트는 이 애노테이션이 붙은 테스트 클래스에는 애플리케이션 컨텍스트 공유 허용 X
- 빈의 의존관계를 강제로 DI 하는 방법을 사용했을 때의 문제를 피할 수 있음
- 하나의 메소드에서만 컨텍스트 상태 변경한다면, 클래스 레벨이 아니라 메소드 레벨에서 적용하는 게 나음
테스트 시 DI 방법
- 테스트 코드에 의한 DI (@DirtiesContext)
- 테스트를 위한 테스트 전용 DI 설정 파일
- 컨테이너 없는 DI 테스트 (이걸 최우선으로 고려한다.)
정리
- 테스트는 자동화돼야 하고, 빠르게 실행할 수 있어야 한다.
- main() 테스트 대신 Junit 프레임워크를 이용한 테스트 작성이 편리하다.
- 테스트 결과는 일관성이 있어야 한다. 코드의 변경 없이 환경이나 테스트 실행 순서에 따라서 결과가 달라지면 안 된다.
- 테스트는 포괄적으로 작성해야 한다. 충분한 검증을 하지 않는 테스트는 없는 것보다 나쁠 수 있다.
- 코드 작성과 테스트 수행의 간격이 짧을수록 효율적이다.
- 테스트하기 쉬운 코드가 좋은 코드다.
- 테스트를 먼저 만들고 테스트를 성공시키는 코드를 만들어가는 테스트 주도 개발 방법도 유용하다.
- 테스트 코드도 애플리케이션 코드와 마찬가지로 적절한 리팩토링이 필요하다.
- @Before, @After를 사용해서 테스트 메소드들의 공통 준비 작업과 정리 작업을 처리할 수 있다.
- 스프링 테스트 컨텍스트 프레임워크를 이용하면 테스트 성능을 향상시킬 수 있다.
- 동일한 설정 파일을 사용하는 테스트는 하나의 애플리케이션 컨텍스트를 공유한다.
- @Autowired를 사용하면 컨텍스트의 빈을 오브젝트에 DI 할 수 있다.
- 기술의 사용 방법을 익히고 이해를 돕기 위해 학습 테스트를 작성하자.
- 오류가 발견 될 경우 그에 대한 버그 테스트를 만들어두면 유용하다.
'토비의스프링vol.1' 카테고리의 다른 글
| [토비의 스프링vol.1 6장] AOP - 1 (0) | 2023.04.03 |
|---|---|
| [토비의 스프링vol.1 5장] 서비스 추상화 (0) | 2023.04.03 |
| [토비의 스프링vol.1 4장] 예외처리 (0) | 2023.04.03 |
| [토비의 스프링vol.1 3장] 템플릿 (1) | 2022.10.31 |
| [토비의 스프링vol.1 1장] 오브젝트와 의존관계 (0) | 2022.10.03 |