이 포스팅은 2장을 요약 정리한 것이며, 상세한 정리는 이 링크에서 제공한다.
스프링과 테스트
- 스프링에서 제공하는 가장 중요한 가치
객체지향
,테스트
- 현대의 앱이 복잡해져가며, 테스트의 중요성은 더욱 높아지고 있다.
좋은 코드는
테스트하기 쉬운 코드
라는 특징을 갖는 경우가 많다.
변화에 대응하는 전략
IoC 그리고 DI
- 스프링의 의존관계 주입형 코드는 로직 코드의 직접적인 수정이 아닌 외부에서 주입하는 객체의 설정정보를 바꾸는 것만으로 손쉬운 변화가 가능하다.
테스트
- 코드에 변화가 생겼을 때, 해당 코드가 특정 로직에서 정상적으로 작동하는지 알 수 있게 해준다.
테스트 전략
나쁜 테스트의 예 - 웹을 통한 테스트
- 테스트하고자 하는 코드 외에 부가적인 코드가 너무 많이 들어간다.
- 모든 레이어의 기능을 다 만들고 나서야 테스트가 가능하다.
- 다른 코드가 너무 많이 개입하는 경우, 실패의 원인이 해당 테스트코드가 아닐 수도 있다.
좋은 테스트의 예 - 단위 테스트
단위 테스트란 외부에 의존하지 않는 하나의 관심사에 집중하여 테스트하는 것이다.
- 작은 단위에서 테스트를 진행하면, 어느 부분의 코드가 동작하지 않는지에 대해 상대적으로 명확하다.
- 외부의 리소스에 의존하지 때문에 통제할 수 있는 범위 내에서 이루어진다.
단위 테스트의 단점
- 개발을 하다보면 각 단위별로는 잘 작동해도 전체 시스템에서 에러가 나는 경우가 있다.
- 개발자 입장에서 만들어놓은 테스트기 때문에 고객의 행동은 단위 테스트처럼 천편일률적이지 않다.
그러나 단위테스트가 잘 작성되어 있다면, 통합테스트 시에 발생하는 에러를 빠르게 잡는데도 분명 도움이 된다.
점진적 개발
- 테스트는 단순히 내가 만든 코드의 기능 확인을 넘어서 지속적 확신을 갖게 해주는 개발 프로세스를 만들어준다.
테스트 검증의 자동화
단순 print문 테스트의 단점
값을 찍어보고 맞는지 눈으로 확인하는 가장 간단한 테스트이다.
- 사람의 눈으로 값을 확인해야 한다.
- 사람은 언제나 실수를 할 수 있다. (잘못 볼 수 있다.)
- 확인해야 할 값이 매우 많을 때는? 사람은 노동력과 시간에 한계가 있다.
결국 컴퓨터가 확인하는 방식으로 전환해야 한다. 이를 위한 프레임워크로
Junit
이 있다.
좋은 테스트의 특징
일관성 있는 테스트
- 테스트 코드에 변경사항이 없다면 외부 영향에 상관없이 동일한 결과를 내는 테스트가 좋은 테스트이다.
- 어떤 데이터에 의존하는 테스트라면, 테스트 시작 전 데이터를 만들어주고 테스트가 끝난 뒤엔 잘 제거해주면 이러한 일관성을 지켜줄 수 있다.
포괄적인 테스트
- 테스트를 작성할 때는 꼼꼼하게 작성하는 편이 좋다.
- 경계값, 다른 타입의 값,
null
값, 찾을 수 없는 값 등 다양한 방식으로 테스트해주는 것이 좋다.
- 경계값, 다른 타입의 값,
위에서 언급한 값들을 테스트하는 것을
네거티브 테스트
라고 한다. 이렇게 부정적인 케이스를 먼저 만드는 습관을 들이면 때때로 큰 실수로 들어가는 비용을 절감할 수 있다.
테스트 주도 개발 (TDD)
아래와 항목에 따라 개발하는 개발방법이다.
- 테스트 할 실제 코드보다 테스트 코드를 먼저 만들며 개발한다.
- 실패한 테스트를 성공시키기 위한 목적이 아닌 코드를 만들지 않는다.
TDD
는 테스트를 먼저 만들고 실제 코드를 작성하기 때문에 테스트를 빼먹을 일이 적다.
TDD 기능 설계 테스트 전략과 예제
- 추가하고 싶은 기능을 구현 전에 먼저 테스트 코드로 표현해본다.
조건
,행위
,결과
3개로 표현하는 것이 좋다.- 일반적으로
when
,if
,then
이라고 표현한다. - 나의 경우에는
if
,action
,then
이 더 직관적으로 와닿는다. 아니면 조건 행위 결과라는 한국어를 그대로 쓰는 것도 괜찮은 것 같다.
- 일반적으로
유저를 가져오는 것에 실패하는 테스트를 작성한다면?
조건
: 유저가 존재하지 않는다.행위
: 존재하지 않는 유저를 조회한다.결과
: 유저가 존재하지 않는다는 예외를 던져준다.
위와 같은 방식으로 기능을 구현하기 전에 테스트 코드로 먼저 표현하며 개발을 진행해볼 수 있다.
JUnit 프레임워크의 동작 방식
public void
메서드 시그니처를 가지고 있는 테스트 메서드를 모두 조회한다.- 테스트 클래스에서 사용될 오브젝트를 하나 만든다. (이 메서드는 메 테스트마다 한번씩 생성되고 다시 버려진다.)
@BeforeEach
가 붙은 메서드가 있는지 찾고 각 테스트 전에 먼저 실행하게 된다.@Test
가 붙은 메서드를 호출하고 테스트 결과를 저장해둔다.@AfterEach
가 붙은 메서드가 있는지 찾고 각 테스트 이후에 실행하게 된다.- 모든 테스트 메서드에 대해 2~5번을 반복한다.
- 모든 테스트의 결과를 종합해서 반환한다.
주의깊게 볼 점은 JUnit이 완전한 독립적 실행을 보장하기 위해 매번 테스트를 위한 오브젝트를 새로 생성했다가 버린다는 것이다. 덕분에 인스턴스 변수와 같은 것들도 마음껏 만들어도 다음 오브젝트 때 초기화될 것을 미리 알 수 있다.
픽스쳐
픽스쳐란 테스트에서 사용될 정보나 오브젝트를 말한다. 보통 @BeforeEach
와 같은 메서드를 이용하여 매 테스트마다 적용할 수 있게 편리하게 사용한다.
스프링 테스트 구성하기
개선 전
public class UserDaoTest {
UserDao userDao;
@BeforeEach
public void setUp() {
ApplicationContext applicationContext = new GenericXmlApplicationContext("spring/applicationContext.xml");
this.userDao = applicationContext.getBean(UserDao.class);
...
}
...
}
- 위 예제 코드의 경우 매번 스프링 컨테이너 오브젝트를 새로 만든다는 단점이 있다.
- 스프링 컨테이너가 작을 때는 충분히 테스트할만 하겠지만, 스프링 컨테이너가 커지면 테스트 시간이 매우 많이 소요될 것이다.
- ex) 시간을 많이 잡아먹는 초기화 빈, 리소스를 많이 할당받는 빈, 스레드를 띄우는 빈
- 시간이 오래걸리는 것보다 더 큰 문제는 리소스를 깔끔하게 정리해주지 않았을 때, 일관적인 결과를 내지 않을 수도 있다는 것이다.
- 스프링 컨테이너가 작을 때는 충분히 테스트할만 하겠지만, 스프링 컨테이너가 커지면 테스트 시간이 매우 많이 소요될 것이다.
일반적으로 스프링을 테스트할 때 애플리케이션 컨텍스트를 공유한다.
개선 후
@ExtendWith(SpringExtension.class) // (JUnit5)
@ContextConfiguration(locations="/spring/applicationContext.xml")
public class UserDaoTest {
@Autowired ApplicationContext applicationContext;
UserDao userDao;
@BeforeEach
public void setUp() {
System.out.println("applicationContext = " + applicationContext);
System.out.println("this = " + this);
this.userDao = this.applicationContext.getBean("userDao", UserDao.class);
...
}
...
}
JUnit5
에서 스프링 테스트하기 쉬운 환경을 제공한다.spring-test
의존성을 받아오면 위와 같이 코드를 구성할 수 있다.xml
설정정보를 가져와서 애플리케이션 컨텍스트를@Autowired
로 주입받을 수 있다.
이제 매번 동일한
ApplicationContext
를 이용하여 테스트를 진행할 수 있다.
심지어 다른 클래스에서도 위의 애노테이션을 붙이면 하나의 애플리케이션 컨텍스트를 공유할 수 있다.
@Autowired
의 활용
- 일명
자동 와이어링
이라는 기능이다. - 스프링 컨테이너에 올라간 빈을 집어올 수 있다.
DI는 인터페이스를 이용하는 것이 좋다
- 추후 변경에 용이하다.
- 그럼에도 별다른 큰 비용 없이 그냥 인터페이스 타입으로 받아주기만 하면 된다.
- 테스트에도 도움된다.
- 많은 구현체에 대한 테스트를 일일이 작성할 필요 없다.
- 구현체의 복잡한 테스트를 할 필요 없이 인터페이스에 공개된 메서드를 작게 테스트하면 된다.
테스트를 위해 ApplicationContext 더럽히기
@BeforeEach
public void setUp() {
DataSource dataSource = new SingleConnectionDataSource(
"jdbc:postgresql://localhost/toby_spring",
"postgres",
"password",
true
);
userDao.setDataSource(dataSource);
}
ApplicationContext
에서 가져온 스프링 빈인userDao
를 테스트하기 위해setter
로 다른DataSource
를 넣어 오염시킬 수 있다.- 단,
@DirtiesContext
라는 애노테이션을 반드시 명시해주어야 한다.
테스트용 ApplicationContext
관리하기
- 설정
.java
파일이나 설정.xml
파일을 따로 관리하여 테스트용Application
을 관리할 수 있다. applicationContext.xml
파일이 실제 사용하는 설정 정보라면,test-applicationContext.xml
와 같은 방식으로 따로 관리하면 된다.
컨테이너 없이 테스트하기
- 스프링 테스트임에도 스프링 컨테이너가 필수가 아닌 경우가 많다. 스프링 컨테이너를 띄우지 않아도 되는 테스트라면 띄우지 않는 것이 가장 좋다.
침투적 기술과 비침투적 기술
침투적 기술
: 애플리케이션 코드가 특정 기술 관련 API, 인터페이스, 클래스에 종속되는 것비침투적 기술
: 기술에 종속되지 않는 순수한 코드를 유지할 수 있게 해주는 것
테스트 시 DI 방법 정리
- 테스트 코드에 의한 DI (
setter
를 이용해 스프링 빈에 직접,@DirtiesContext
) - 테스트를 위한 별도의 DI 설정 (
test-ApplicationContext.xml
와 같은 파일을 따로 관리) - 컨테이너 없는 DI 테스트
컨테이너가 없어도 된다면 항상 컨테이너가 없는 것이 최선이다.
학습 테스트
- 애플리케이션의 로직보다는 API의 기능 확인 등을 위해 작성해보는 테스트
학습 테스트의 장점들
- 자동화된 코드로 API를 테스트해보며 조건에 따라 기능이 어떻게 작동하는지 빠르게 확인할 수 있다.
- 개발 중에 다시 참고하며 개발할 수 있다.
- 다른 개발자와 공유도 가능하다.
- 프레임워크, 제품 업그레이드 후에도 API가 같은 방식으로 작동하는지 알 수 있다.
- 테스트 코드 작성에 좋은 훈련이 된다.
- 레퍼런스만 보는 것보다 훨씬 재밌다.
스프링 프레임워크 프로젝트 내부에도 스프링 프레임워크를 개발하기까지 작성한 테스트들이 매우 많다. 그 테스트들을 레퍼런스로 참고하는 것도 매우 좋다.
버그 테스트
- 코드에 오류가 있을 때, 해당 오류를 가장 잘 나타내주는 테스트를 만드는 것이다. 코드를 수정하여 버그 테스트가 성공하면 버그 수정이 성공한 것이다.
버그 테스트 장점
- 테스트의 완성도를 높인다.
- 기존의 테스트에 테스트가 하나 더 추가되는 것이다.
- 테스트에서 버그의 내용을 명확히 나타내준다.
- 이 버그 내용을 이용하여 다른 버그도 추가적으로 테스트할 수 있다.
- 기술적인 문제를 해결하는데 도움이 된다.
- 가장 단순한 코드와 그에 대한 버그 테스트를 만들어 기술적 문제 해결에 도움이 될 수 있다.
테스트에서 동등 분할과 경계값 분석
동등 분할
- 작업의 결과가 한정적일 때, 값의 범위를 구분하여 구분되는 범위들을 커버하는 테스트를 작성하는 기법을 말한다.
경계값 분석
- 최대, 최소, 문제가 되는 값의 주변 값 등을 테스트해보는 기법을 말한다.
정리
- 테스트는 자동화되고 빠르게 실행할 수 있어야 한다.
- main()을 이용하지 말고, JUnit 프레임워크를 이용하면 테스트 자동화가 가능하다.
- 테스트 결과는 일관성이 있어야한다.
- 환경이나 테스트 순서에 영향을 받으면 안 된다.
- 테스트는 포괄적으로 작성해야 한다. 충분한 검증이 없는 테스트는 없는 것보다 나쁘다.
- 네거티브 테스트 먼저 작성하는 습관을 들이자.
- 코드 작성과 테스트 수행의 간격이 짧을수록 효과적이다.
- 테스트하기 쉬운 코드가 좋은 코드다.
- 테스트를 먼저 만들고 테스트를 성공시키는 코드를 만들어가는 TDD도 유용하다.
- 테스트 코드도 애플리케이션 코드와 마찬가지로 적절한 리팩토링이 필요하다.
- @BeforeEach, @AfterEach를 사용해서 테스트 메소드들의 공통 준비 작업과 정리 작업을 처리할 수 있다.
- 스프링 테스트 컨텍스트 프레임워크를 이용하면 테스트 성능을 향상시킬 수 있다.
- 동일한 설정 파일을 사용하는 테스트는 하나의 애플리케이션 컨텍스트를 공유한다.
- @Autowired를 사용하면 컨텍스트의 빈을 테스트 오브젝트에 DI할 수 있다.
- 학습 테스트를 이용하면 기술의 사용 방법을 익히고 이해를 도울 수 있다.
- 오류가 발견되는 경우 버그 테스트를 만들어두면 유용하다.
'프레임워크 > 토비의 스프링' 카테고리의 다른 글
토비의 스프링 5장 요약 정리 - 서비스 추상화 (0) | 2022.09.06 |
---|---|
토비의 스프링 4장 요약 정리 - 예외 처리 (0) | 2022.06.21 |
토비의 스프링 3장 요약 정리 - 템플릿 (0) | 2022.06.20 |
토비의 스프링 1장 요약 정리 - 오브젝트와 의존관계 (3) | 2021.12.26 |
토비의 스프링 0장 정리 (0) | 2021.12.14 |