테스트하고자 하는 대상이 명확하다면 그 대상에만 집중해서 테스트하는 것이 바람직하다.
따라서 테스트는 가능하면 작은 단위로 쪼개서 집중해서 할 수 있어야한다.
- 관심사의 분리 원리가 여기에도 적용된다.
- 테스트의 관심이 다르다면 테스트할 대상을 분리하고 집중해서 접근해야 한다.
이렇게 작은 단위의 코드에 대해 테스트를 수행한 것을 **단위 테스트 Unit test
**라고 한다.
- 여기서 말하는 단위가 크기나 범위가 딱 정해진 건 아니다.
- 하나의 관심에 집중해서 효율적으로 테스트할 만한 범위의 단위라고 보면 된다.
통제할 수 없는 외부의 리소스에 의존하는 테스트는 단위 테스트가 아니라고 보기도 한다.
- 테스트 에러 : 테스트가 진행되는 동안에 에러가 발생해서 실패하는 경우
- 테스트 실패 : 테스트 중에 에러가 발생하진 않았지만 그 결과가 기대한 것과 다르게 나오는 경우
JUnit
은 @Test
가 붙어 있고 리턴 값이 void
형이고 파라미터가 없다는 조건을 지키기만 하면 된다.
만든 코드는 어떤 방법으로든 테스트해야 한다.
테스트란 결국 내가 예상하고 의도했던 대로 코드가 정확히 동작하는지를 확인해서, 만든 코드를 확신할 수 있게 해주는 작업이다.
2.3.2 테스트 결과의 일관성 예제
코드에 변경사항이 없다면 테스트는 항상 동일한 결과를 내야한다.
현재 문제는 이전 테스트 때문에 DB에 등록된 중복 데이터가 있을 수 있다는 점이다.
성의 없이 테스트를 만들어 문제가 있는 코드인데도 테스트가 성공하는 테스트코드가 제일 위험하다.
특히 한 가지 결과만 검증하고 마는 것은 상당히 위험하다.
"항상 네거티브 테스트를 먼저 만들라" - 스프링의 창시자 로드 존슨
getCount()
테스트 예제
- 데이터를 모두 지우고 레코드 개수가 0임을 확인
- 3개의 사용자 정보를 하나씩 추가하면서 매번 결과가 하나씩 증가하는지 확인
addAndGet()
테스트 보완 예제
get()
예외조건에 대한 테스트 예제
위와 같은 단순 DAO는 테스트 코드를 작성 안해도 실수 없이 짤 수 있겠다고 생각하겠지만, 종종 단순하고 간단한 테스트가 치명적인 실수를 피할 수 있게 해주기도 한다.
"실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 개발하지 않는다"
는 것이 TDD의 기본 원칙이다.
코드에 대한 피드백을 매우 빠르게 받을 수 있게 된다.
테스트없이 한 번에 너무 많은 코드를 만드는 것은 좋지 않다.
필요하다면 테스트 코드도 언제든지 내부구조와 설계를 개선해서 좀 더 깔끔하고 이해하기 쉬우며 변경이 용이한 코드로 만들 필요가 있다. 테스트 코드 자체가 이미 자신에 대한 테스트 이기 때문에 테스트 결과가 일정하게 유지된다면 얼마든지 리팩토링을 해도 좋다.
- 테스트 클래스에서
@Test
가 붙은 메소드를 모두 찾는다. - 테스트 클래스의 오브젝트를 하나 만든다.
@BeforeEach
가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.@Test
가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.@AfterEach
가 붙은 메소드가 있으면 실행한다.- 나머지 테스트 메소드에 대해 2 ~ 5번을 반복한다.
- 모든 테스트의 결과를 종합해서 돌려준다.
그런데 , 왜 테스트 메소드를 실행할 때 마다 새로운 오브젝트를 만드는 것일까?
JUnit 개발자는 각 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 확실히 보장해주기 위해 매번 새로운 오브젝트를 만들게 했다.
테스트를 수행하는 데 필요한 정보나 오브젝트를 픽스처라고 한다.
2.4 스프링 테스트 적용 예제
@BeforeEach
void setUp() {
context = new AnnotationConfigApplicationContext(DaoFactory.class);
dao = context.getBean("userDao" , UserDao.class);
}
위의 픽스처를 보면 애플리케이션 컨텍스트가 매번 새로 만들어진다.
애플리케이션 컨텍스트가 만들어질 때는 모든 싱글톤 빈 오브젝트를 초기화하기 때문에 빈이 많아지고 복잡해진다면 적지 않은 시간이 걸릴 수 있다.
스프링은 JUnit을 이용하는 테스트 컨텍스트 프레임워크를 제공한다.
@SpringJUnitConfig(DaoFactory.class)
public class UserDaoTest {
@Autowired
private ApplicationContext context;
private UserDao dao;
@BeforeEach
void setUp() {
dao = context.getBean("userDao" , UserDao.class);
}
...
}
여러 개의 테스트 클래스가 있는데 모두 같은 설정파일을 가진 애플리케이션 컨텍스트를 사용한다면,
스프링은 테스트 클래스 사이에서도 애플리케이션 컨텍스트를 공유하게 해준다.
테스트에 DI를 이용하는 방법을 몇 가지 살펴보자.
테스트 코드에서 사용할 DB를 테스트 코드에 의한 DI를 이용해서 테스트 중에 DAO가 사용할 DataSource 오브젝트를 바꿔주는 방법을 이용해보자.
테스트용 DB에 연결해주는 DataSource를 테스트 내에서 직접 만들자.
스프링이 제공하는 가장 빠른 SingleConnectionDataSource
를 사용해보자.
- DB커넥션을 하나만 만들어두고 계속 사용하기 때문에 매우 빠르다.
스프링 테스트 컨텍스트 프레임워크를 적용했다면 애플리케이션 컨텍스트는 테스트 중에 딱 한 개만 만들어지고 모든 테스트에서 공유해서 사용한다.
@DirtiesContext
를 메소드 또는 클래스 레벨에 작성하면 해당 메소드 또는 클래스의 테스트에서 애플리케이션 컨텍스트 공유를 허용하지 않는다.
테스트를 위한 별도의 DI 설정 예제
항상 스프링 컨테이너 없이 테스트 할 수 있는 방법을 가장 우선적으로 고려하자.
개발환경과 테스트환경 , 운영환경 차이가 있기 때문에 각각 다른 설정파일을 만들어 사용하자.
자신이 만들지 않은 프레임워크나 다른 개발팀에서 만들어서 제공한 라이브러리 등에 대해서도 테스트를 작성해야 한다.
이런 테스트를 학습 테스트라고 한다.
- 다양한 조건에 따른 기능을 손쉽게 확인해 볼 수 있다.
- 학습 테스트 코드를 개발 중에 참고할 수 있다.
- 프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와준다.
- 테스트 작성에 대한 좋은 훈련이 된다.
- 새로운 기술을 공부하는 과정이 즐거워진다.
코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트를 말한다.
- 테스트의 완성도를 높여준다.
- 버그의 내용을 명확하게 분석하게 해준다.
- 기술적인 문제를 해결하는 데 도움이 된다.
동등분할
같은 결과를 내는 값의 범위를 구분해서 각 대표 값으로 테스트를 하는 방법을 말한다.
어떤 작업의 결과의 종류가 true
, false
또는 예외발생
세 가지라면 각 결과를 내는 입력 값이나 상황의 조합을 만들어 모든 경우에 대한 테스트를 해보는 것이 좋다.
경계값 분석
에러는 동등분할 범위의 경계에서 주로 많이 발생한다는 특징을 이용해서 경계의 근처에 있는 값을 이용해 테스트 하는 방법이다.
보통 숫자의 입력 값인 경우 0
이나 그 주변 값
또는 정수의 최대값 , 최소값
등으로 테스트 해보면 도움이 될 때가 많다.