Intro
- 스프링의 핵심 = 객체지향 & 테스트
- 테스트 = 의도했던 대로 코드가 동작하는지를 확인해서, 만든 코드를 확신할 수 있게 해주는 작업임.
2.1 UserDaoTest 다시 보기
2.1.2 UserDaoTest의 특징
작은 단위의 테스트
- 테스트하고자 하는 대상이 명확하면 대상에만 집중해서 테스트하는 것이 바람직함.
→ 테스트는 작은 단위로 쪼개서 집중 (관심사의 분리)
→ 단위 테스트(unit test)
자동수행 테스트 코드
- 테스트는 자동으로 수행되도록 코드로 만들어지는 것이 중요함. (자주 반복할 수 있다는 장점이 있음.)
2.1.3 UserDaoTest의 문제점
- 수동 확인 작업의 번거로움
- 테스트의 수행은 코드에의해 자동으로 진행되지만 확인하는 일은 사람의 책임.
- 실행 작업의 번거로움
- main() 메소드 이용하는 것보다 편리하고 체계적인 방법 필요함.
2.2 UserDaoTest 개선
2.2.1 테스트 검증의 자동화
- 테스트 실패
- 테스트 에러 : 에러가 발생해서 실패
- 테스트 실패 : 에러가 발생하진 않지만 기대한 답이 아님
- 빠르게 실행 가능하고 스스로 테스트 수행과 기대하는 결과에 대한 확인까지 해주는 코드로된 자동화된 테스트 만들어두는 것이 좋음.
2.2.2 테스트의 효율적인 수행과 결과 관리
- 실용적인 테스트를 위한 도구 - JUnit 프레임워크
- JUnit 프레임워크 요구 조건
- 메소드가 public으로 선언되어야 함.
- 메소드에 @Test 추가
2.3 개발자를 위한 테스팅 프레임워크 JUnit
- 스프링 학습을 위해서는 최소한의 JUnit 테스트 작성 & 실행 방법 알아야 함.
2.3.1 JUnit 테스트 실행 방법
IDE
- @Test가 들어있는 테스트 클래스 선택 → eclipse run 메뉴의 JUnit Test 선택
2.3.3 포괄적인 테스트
- JUnit은 하나의 클래스 안에 여러 개의 테스트 메소드가 들어가는 것을 허용함.
- @Test 붙어있고 public 접근자가 있으며 리턴 값이 void형이고 파라미터가 없다는 조건 지키면 됨.
- JUnit은 특정한 테스트 메소드의 실행 순서를 보장하지 않음. (테스트 결과가 실행 순서에 영향을 받는다면 테스트를 잘못 만든 것임.)
- 테스트를 작성할 때 부정적인 케이스를 먼저 만드는 것이 좋음.
2.3.4 테스트가 이끄는 개발
기능설계를 위한 테스트
- 기능설계, 구현, 테스트라는 일반적인 개발 흐름의 기능설계에 해당하는 부분을 이 테스트 코드가 일부분 담당하고 있다고 볼 수 있음.
테스트 주도 개발(TDD, Test Driven Development)
- 만들고자 하는 기능의 내용을 담고 있으면서 만들어진 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법
- TDD의 기본 원칙 - 실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다
- TDD에서는 테스즈 작성하고 코드를 만드는 작업의 주기를 짧게하는 것을 권장함
2.3.5 테스트 코드 개선
Junit의 테스트 수행 방식
- 테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 모두 찾음.
- 테스트 클래스의 오브젝트를 하나 만든다.
- @Before 메소드 있으면 실행
- @Test 메소드 하나 호출하고 테스트 결과 저장
- @After 메소드 있으면 실행
- 나머지 테스트 메소드에 대해 2~5번 반복
- 모든 테스트의 결과를 종합해서 돌려줌
각 테스트 메소드 실행시마다 테스트 클래스의 오브젝트를 새로 만든다.
- 왜일까? JUnit 개발자는 각 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 보장해주기 위해서 매번 새로운 오브젝트를 만들게 함.
픽스처(fixture)
- 테스트를 수행하는 데 필요한 정보나 오브젝트
2.4 스프링 테스트 적용
- 애플리케이션 생성 방식
- 빈이 많아지고 복잡해지면 애플리케이션 컨텍스트 생성에 적지 않은 시간이 걸릴 수 있음.
- 애플리케이션 컨텍스트가 초기화될 때 어떤 빈은 독자적으로 많은 리소스를 할당하거나 독립적인 스레드를 띄우기도 함.
- 애플리케이션 컨텍스트 처럼 생성에 많은 자원이 소모되는 경우 테스트 전체가 공유하는 오브젝트 생성함.
- JUnit은 @BeforeClass 스태틱 메소드를 지원함.
- 이 메소드에서 애플리케이션 컨텍스트를 만들어 스태틱 변수에 저장해두고 테스트 메소드에서 사용하게 할 수 있음.
- 스프링이 제공하는 애플리케이션 컨텍스트 테스트 지원 기능이 더 편리함.
2.4.1 테스트를 위한 애플리케이션 컨텍스트 관리
- 스프링은 JUnit을 이용하는 테스트 컨텍스트 프레임워크를 제공함.
스프링 테스트 컨텍스트 프레임워크 적용
- @Autowired
- @RunWith - 스프링의 테스트 컨텍스트 프레임워크의 Junit 확장기능 지정
- @ContextConfiguration - 테스트 컨텍스트가 자동으로 만들어줄 애플리케이션 컨텍스트의 위치 지정
테스트 메소드의 컨텍스트 공유
- JUnit은 테스트 메소드 실행시마다 새로운 테스트 오브젝트 생성
→ context 변수에 어떻게 애플리케이션 컨텍스트가 들어있을까?
→ 스프링의 JUnit 확장기능은 테스트가 실행되기 전에 딱 한 번만 애플리케이션 컨텍스트를 만들어두고, 테스트 오브젝트가 만들어질 때마다 특별한 방법을 이용해 애플리케이션 컨텍스트 자신을 테스트 오브젝트의 특정 필드에 주입해주는 것임.
→ 스프링이 애플리케이션 컨텍스트 개수에 상관없이 한 번만 만들어서 공유해줬기 때문에 테스트 수행 속도 빨라짐.
테스트 클래스의 컨텍스트 공유
- 스프링은 테스트 클래스 사이에서도 애플리케이션 컨텍스트를 공유하게 해준다.
- @Autowired가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾음.
- 타입이 일치하는 빈이 있으면 인스턴스 변수에 주입.
- 별도의 DI 설정 없이 필드의 타입 정보를 이용해 빈을 자동으로 가져올 수 있음. → 타입에 의한 자동와이어링
- 변수에 할당 가능한 타입을 가진 빈을 자동으로 찾음.
- 테스트에서도 가능한 한 인터페이스를 사용해서 애플리케이션 코드와 느슨하게 연결해두는 편이 좋음.
2.4.2 DI와 테스트
- 인터페이스를 두고 DI를 적용해야 하는 이유
- 소프트웨어 개발에서 절대로 바뀌지 않는 것은 없다.
- 클래스의 구현 방식은 바뀌지 않는다고 하더라도 인터페이스를 두고 DI를 적용하게 해두면 다른 차원의 서비스 기능을 도입할 수 있음.
- 효율적인 테스트를 만들기 위해서임.
테스트 코드에 의한 DI
- @DirtiesContext - 스프링의 테스트 컨텍스트 프레임워크에게 해당 클래스의 테스트에서 애플리케이션 컨텍스트의 상태를 변경한다는 것을 알려줌.
DI를 이용한 테스트 방법 선택
- 항상 스프링 컨테이너 없이 테스트할 수 있는 방법을 최우선으로
- 여러 오브젝트와 복잡한 의존관계를 갖고 있는 오브젝트 테스트
→ 스프링의 설정을 이용한 DI 방식의 테스트
2.5 학습 테스트로 배우는 스프링
- 학습 테스트(learning test)
- 학습테스트의 목적은 자신이 사용할 API나 프레임워크의 기능을 테스트로 보면서 사용법 익히는 것.
2.5.1 학습 테스트의 장점
- 다양한 조건에 따른 기능을 손쉽게 확인
- 프레임워크나 제품을 업그레이드할 때 호환성 검증 도와줌.
- 테스트 작성에 대한 좋은 훈련
- 새로운 기술을 공부하는 과정이 즐거워짐
- 스프링 학습 테스트를 만들 때 참고할 수 있는 좋은 소스는 스프링 자신에 대한 테스트 코드임.
2.5.3 버그 테스트(bug test)
- 코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트
- 버그테스트는 실패하도록 만들어야 함.
- 테스트 방법
- 동등분할 - 같은 결과를 내는 값의 범위를 구분해서 각 대표값으로 테스트
- 경계값 분석 - 에러는 동등분할 범위의 경계에서 주로 발생. 경계의 근처에 있는 값을 이용해 테스트하는 방법임.