토비의 스프링 2장
2. 테스트
2.1 UserDaoTest 다시보기
- 2.1.1 테스트의 유용성
- 테스트란 예상하고 의도했던 대로 코드가 정확히 동작하는지를 확인해서, 만든 코드를 확신할 수 있게 해주는 작업
- 2.1.2 UserDaoTest의 특징
- main()메소드를 이용해 쉽게 테스트를 수행
- 테스트할 대상인 UserDao를 직접 호출해서 사용
- 웹을 통한 DAO 테스트 방법의 문제점
- DAO뿐만 아니라 서비스 클래스, 컨트롤러, JSP 뷰 등 모든 레이어의 기능을 다 만들고 나서야 테스트가 가능하다는 점
- 테스트를 하는 중에 에러가 나거나 테스트가 실패했다면, 과연 어디에서 문제가 발생했는지를 찾아내야 하는 문제
- 작은 단위의 테스트
- 테스트는 가능하면 작은 단위로 쪼개서 집중(관심사의 분리)
- 단위 테스트(unit test): 작은 단위의 코드에 대해 테스트를 수행한 것
- 통제할 수 없는 외부의 리소스에 의존하는 테스트는 단위 테스트가 아니라고 보기도 한다.
- 단위 테스트를 하는 이유는 개발자가 설계하고 만든 코드가 원래 의도한 대로 동작하는지를 개발자 스스로 빨리 확인받기 위해서다.
- 자동수행 테스트 코드
- 테스트는 자동으로 수행되도록 코드로 만들어지는 것이 중요
- 지속적인 개선과 점진적인 개발을 위한 테스트
- 2.1.3 UserDaoTest의 문제점
- 수동 확인 작업의 번거로움
- 콘솔로 확인하기 때문에 성공적으로 되고 있는지를 확인하는 건 사람의 책임이다.
- 실행 작업의 번거로움
- 여러 개라면 실행 하는 것 자체가 번거롭다.
- 수동 확인 작업의 번거로움
2.2 UserDaoTest 개선
- 2.2.1 테스트 검증의 자동화
- 비교를 통해 테스트 성공 혹은 실패를 출력하도록 개선하여 자동화
- 2.2.2 테스트의 효율적인 수행과 결과 관리
- main() 메소드로는 한계가 있기 때문에 JUnit 사용
- JUnit 테스트로 전환
- JUnit은 프레임워크이기 때문에 main()메소드가 필요 없음
- 테스트 메소드 전환
- 조건1: public void 파라미터가 없는 메소드
- 조건2: 메소드에
@Test
어노테이션 붙임
- 검증 코드 전환
- assertThat(): 첫번째 파라미터의 값을 뒤에 나오는 매처(matcher)라고 불리는 조건으로 비교해서 일차하면 다음으로 넘어가고, 아니면 테스트가 실패하도록 만들어 줌
- JUnit 테스트 실행
2.3 개발자를 위한 테스팅 프레임워크 JUnit
- 2.3.1 JUnit 테스트 실행 방법
- IDE
- 빌드 툴
- 2.3.2 테스트 결과의 일관성
- UserDaoTest는 이전 테스트 때문에 DB에 등록된 중복 데이터가 있을 수 있다는 문제가 있다.
- deleteAll()의 getCount() 추가
- deleteAll()의 getCount()의 테스트
- deleteAll()과 getCount()를 기존의 테스트에 추가
- deleteAll()의 결과를 믿을 수 없기 때문에 getCount()로 0인지 확인한다.
- getCount()도 믿을 수 없기 때문에 add를 한 뒤 1인지 확인한다.
- 동일한 결과를 보장하는 테스트
- 단위 테스트는 항상 일관성 있는 결과가 보장돼야 한다. DB에 남아있는 데이터와 같은 외부 환경에 영향을 받지 말아야 하고, 테스트를 실행하는 순서를 바꿔도 도일한 결과가 보장되도록 만들어야 한다.
- 2.3.3 포괄적인 테스트
- getCount() 테스트
- 좀 더 꼼꼼한 테스트가 필요하기 때문에 하나씩 추가하면서 getCount() 확인
- addAndGet() 테스트 보완
- get() 이 파라미터로 주어인 id에 해당하는 사용자를 가져온 것인지, 그냥 아무거나 가져온 것인지 검증하기 못했기 때문에 보완 필요
- user 2개를 테스트하여 검증
- get() 예외조건에 대한 테스트
- get() 메소드에 전달된 id 값에 해당하는 사용자가 없다면?
@Test(expected=EmpryResultDataAccessException.class)
처럼 expected를 통해 예외 테스트를 구현
- 테스트를 성공시키기 위한 코드의 수정
- 포괄적인 테스트
- 개발자는 잘 돌아가는 케이스를 상상하면서 코드를 만들고, 테스트를 작성할 때도 문제가 될 만한 상황이나, 입력 값 등은 교묘히 피하는 습성이 있다.
- 그래서 부정적인 케이스를 먼저 만드는 습관을 들이는 것이 좋다.
- getCount() 테스트
- 2.3.4 테스트가 이끄는 개발
- 테스트 주도 개발(Test Driven Development)
- 만들고자 하는 기능의 내용을 담고 있으면서 만들엊딘 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법을 의미
- TDD는 테스트를 먼저 만들고 그 테스트가 성공하도록 하는 코드만 만드는 식으로 진행하기 때문에 테스트를 빼먹지 않고 꼼꼼하게 만들 수 있다.
- TDD에서 테스트 작성하고 이를 성공시키는 코드를 만드는 작업의 주기를 가능한 짧게 가져가도록 권장한다.
- TDD를 하면 자연스럽게 단위 테스트를 만들 수 있다.
- 테스트 주도 개발(Test Driven Development)
- 2.3.5 테스트 코드 개선
@Before
@Test
가 실행되기 전에 먼저 실행돼야 하는 메소드를 정의한다.- setUp() 메소드에서 만드는 오브젝트를 테스트 메소드에서 사용할 수 있도록 로컬 변수가 아닌 인스턴스 변수로 선언한다.
- JUnit 테스트 수행 방식
@Test
가 붙은 public void 파라미터 없는 테스트 메소드를 모두 찾음- 테스트 클래스의 오브젝트 하나 생성
@Before
메소드 실행@Test
호출 후 결과 저장@Afte
메소드 실행- 나머지 테스트 메소드에서 2~5 반복
- 테스트의 결과를 종합하여 돌려줌
- JUnit 개발자는 각 테스트가 서로 독립적으로 실행됨을 확실히 보장해주기 위해 매번 새로운 오브젝트를 만들도록 했다.
- 픽스처
- 픽스처(fixture): 테스트를 수행하는 데 필요한 정보나 오브젝트
- 픽스처는 여러 테스트에서 반복적으로 사용하기 때문에
@Before
메소드를 이용해 생성해두면 편리하다.
2.4 스프링 테스트 적용
JUnit이 매번 테스트 클래스의 오브젝트를 만드는 문제가 있다.
- 2.4.1 테스트를 위한 애플리케이션 컨텍스트 관리
- 스프링 테스트 컨텍스트 프레임워크 적용
@RunWith(SpringJunit4ClassRunner.class)
: 스프링의 테스트 컨텍스트 프레임워크의 JUnit 확장기능 지정@ContextConfiguration(locations="")
: 테스트 컨텍스트가 자동으로 만들어줄 애플리케이션 컨텍스트의 위치 지정@Autowired
: 테스트 오브젝트가 만들어지고 나면 스프링 테스트 컨텍스트에 의해 자동으로 값이 주입
- 테스트 메소드의 컨텍스트 공유
- 스프링이 애플리케이션 컨텍스트 테스트 개수에 상관없이 한 번만 만들어서 공유하게 해줬기 때문에 테스트 수행 속도가 빨라짐
- 테스트 클래스의 컨텍스트 공유
@Autowired
@Autowired
가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다.- 단,
@Autowired
는 같은 타입의 빈이 두 개 이상 있는 경우에는 타입만으로 어떤 빈을 가져올지 결정할 수 없다.
- 스프링 테스트 컨텍스트 프레임워크 적용
- 2.4.2 DI와 테스트
- 인터페이스를 두고 DI를 적용해야 하는 이유
- 소프트웨어 개발에서 절대로 바뀌지 않는 것은 없기 때문에
- 클래스의 구현 방식은 바뀌지 않는다고 하더라도 인터페이스를 두고 DI를 적용하게 해두면 다른 차원의 서비스 기능을 도입할 수 있기 때문에
- 테스트 때문에
- 테스트 코드에 의한 DI
@Before
메소드에서 테스트용 DataSource 오브젝트를 생성하고, set을 통해 DI한다.- 하지만 위의 방식으로 하면 애플리케이션 컨텍스트에서 가져온 UserDao 빈의 의존관계를 강제로 변경하기 때문에 바람직하지 못하다.
@DirtiesContext
: 테스트 메소드에서 애플리케이션 컨텍스트의 구성이나 상태를 변경한다는 것을 테스트 컨텍스트 프레임워크에 알려준다. 그래서 애플리케이션 컨텍스트 공유를 허용하지 않는다.
- 테스트를 위한 별도의 DI 설정
- 테스트에서 사용될 DataSource 클래스가 빈으로 정의된 테스트 전용 설정파일을 따로 만든다.
- 그 후
@ContextConfiguration
을 변경한다.
- 컨테이너 없는 DI 테스트
- DI를 이용한 테스트 방법 선택
- 항상 스프링 컨테이너 없이 테스트할 수 있는 방법을 가장 우선적으로 고려한다. (이 방법이 속도가 가장 빠르고 간결하기 때문에)
- 여러 오브젝트와 복잡한 의존관계를 갖고 있는 오브젝트를 테스트해야 할 경우 DI 방식의 테스트 이용
- 예외적인 의존관계를 강제로 구성해야 하는 경우 컨텍스트 DI 테스트
- 인터페이스를 두고 DI를 적용해야 하는 이유
2.5 학습 테스트로 배우는 스프링
학습 테스트: 자신이 만들지 않은 프레임워크나 다른 개발팀에서 만들어서 제공한 라이브러리에 대한 테스트
- 2.5.1 학습 테스트의 장점
- 다양한 조건에 따른 기능을 손쉽게 확인해볼 수 있다.
- 학습 테스트 코드를 개발 중에 참고할 수 있다.
- 프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와준다.
- 테스트 작성에 대한 좋은 훈련이 된다.
- 새로운 기술을 공부하는 과정이 즐거워진다.
- 2.5.2 학습 테스트 예제
- 2.5.3 버그 테스트
- 버그 테스트: 코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트 버그 테스트의 필요성과 장점
- 테스트의 완성도를 높여준다.
- 버그의 내용을 명확하게 분석하게 해준다.
- 기술적인 문제를 해결하는 데 도움이 된다.
- 동등분할: 같은 결과를 내는 값의 범위를 구분해서 각 대표 값으로 테스트를 하는 방법
- 경계값 분석: 에러는 동등분할 범위의 경계에서 주로 많이 발생한다는 특징을 이용해서 경계의 근처에 있는 값을 이용해 테스트하는 방법
- 버그 테스트: 코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트 버그 테스트의 필요성과 장점
This post is licensed under CC BY 4.0 by the author.