<토비의 스프링 3.1 vol.1>을 읽고 공부한 내용을 개인적으로 정리한 글입니다.
DAO 테스트 과정을 생각해보자.
User
객체가 되어 UserDao
를 호출한다.이처럼 단순 웹 기능과는 달리 DAO 테스트를 위해서는 DAO뿐만 아니라 서비스 클래스, 컨트롤러, 뷰 계층 등
모든 레이어의 기능을 다 만들고 나서야 테스트가 가능하다는 문제가 있다.
이 경우 테스트하고 싶은 기능은 DAO에 관한 기능 뿐인데,
DAO를 테스트하기 위해 다른 계층들을 깔아주는 과정에서 생긴 오류들이 복합적으로 얽혀버리면
문제의 원인을 찾기 힘들어 정작 DAO에 대한 테스트에 집중하기 어려울 가능성이 크다.
→ 테스트하고자 하는 대상이 명확하다면, 그 대상에만 집중해서 테스트하자!
앞에서 정리한 관심사의 분리를 떠올려보면 된다.
테스트를 통해 확인하고 싶은 한 가지 관심사를 정해 접근해보자는 것.
앞에서 만든 UserDao
의 기능을 확인해보고 싶다.
그렇다면 지금부터 우리의 관심사는 UserDao
단 하나다.
UserDao
를 테스트하기 위한 테스트 코드를 UserDaoTest
라 하자.
이때, UserDaoTest
는 UserDao
에 대한 단위 테스트라고 한다.
매 테스트마다 개발자가 매번 웹 화면에 폼을 띄우고 값을 입력할 필요 없이,
테스트가 자동으로 수행되도록 코드를 짜는 것이 중요하다.
또한 테스트 코드를 메인 애플리케이션에 포함시키는 것보다 별도로 테스트 클래스를 만드는 것이 좋다.
테스트 코드의 도입으로 테스트 과정이 한결 편해졌지만,
테스트 결과 확인을 매번 수동으로 해야 하는 번거로움을 해결하기 위해 검증 과정을 자동화할 필요가 있다.
개발 과정에서, 또는 유지보수 과정에서 기존 애플리케이션 코드를 수정해야 할 때,
빠르게 실행 가능하고 스스로 테스트 수행과 기대하는 결과에 대한 확인까지 해주는 테스팅 프레임워크의 도움을 받을 수 있다.
→ xUnit
!
🚨 테스트 역시 자바 코드이므로 main() 함수에서 한 번은 호출되어야 하지만,
JUnit을 사용하면 알아서 내부에서 적절한 main 함수를 생성해 실행시켜준다.
테스트 객체는public
으로 선언되어야 하며@Test
어노테이션을 붙여주어야 한다.
test 패키지 하에 테스트를 수행할 내용에 대해서만 테스트 코드를 작성해주고 실행해보면 된다.
🗣 책에는 없는 내용이지만, 같이 보면 좋을 것 같아 추가했습니다.
JUnit API docs에서 더 자세한 내용을 확인할 수 있습니다.
JUnit에서 가장 많이 사용되는 단정(assert) 메소드에 대해 알아보도록 하겠다.
단정 메소드는 테스트 케이스의 수행 결과를 판별하는 메소드이다.
**assertEquals**(x, y)
**assetArrayEquals**(a, b)
**assertFalse**(x)
, **assertTrue**(x)
**assertTrue**(메세지, 조건문)
**assertNull**(x)
, **assertNotNull**(x)
**assertSame**(ox, oy)
, **assertNotSame**(ox, oy)
**assertfail**()
1이라는 값을 가질 것으로 기대되는 객체 A가 있다고 생각해보자.
만약 A가 1이 아닌 다른 값을 가지는 경우에 대해 어떻게 처리할 것인가?
이에 대해 두 가지 방법을 생각해볼 수 있다.
null
과 같은 특별한 값을 리턴여기서는 예외를 던지는 방식으로 문제를 해결한다고 가정하겠다.
이떄, 예외의 발생 여부는 assertThat()
과 같은 단정 메소드로 확인할 수 없다.
이러한 상황에 대비해 JUnit은 예외조건 테스트 기능을 제공한다.
get()이라는 메소드의 예외상황을 테스트하기 위한 메소드를 추가한다.
존재하지 않는 id로 user를 검색하는 상황이며,
user가 존재하지 않아 CustomException
이 던져져야 테스트가 성공한다.
@Test(expected=CustomException.class) // 테스트 중에 발생할 것으로 기대되는 예외 클래스 지정
public void getUserFailure() throws SQLException {
ApplicationContext context = new GenerivXmlApplicationContext("applicationContext.xml");
UserDao dao = context.getBean("userDao", UserDao.class);
/* ... */
dao.get("존재하지 않는 id"); // 여기서 CustomException 예외가 발생해야 한다.
// 예외가 발생하지 않으면 테스트는 실패한다.
}
이때, get 메소드는 존재하지 않는 id로 user를 검색하는 상황에 대해 CustomException
을 발생시키도록 설계해야 한다.
그렇지 않으면 테스트가 실패한다.
개발자가 테스트를 직접 만들 때 자주 하는 실수 중 하나가 성공하는 테스트만 골라서 만드는 것이다.
예외적인 상황이나 문제가 될 만한 케이스를 무의식적으로 피해가며 테스트를 만들게 되면,
개발자의 PC를 벗어났을 때 문제를 일으킬 수 있다.
따라서 테스트를 작성할 때, 부정적인 케이스를 먼저 만드는 습관을 들이자.
테스트 코드를 먼저 만들고,
테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법.
만들고자 하는 기능의 내용을 담고 있으면서, 만들어진 코드를 검증도 해줄 수 있도록.
TDD의 기본 원칙은 “실패한 테스트를 성공시키기 이한 목적이 아닌 코드는 만들지 않는다” 이다.
TDD의 장점으로는
@Before
JUnit에서 제공하는 어노테이션.
@Test
어노테이션이 붙은 메소드가 실행되기 전에 먼저 실행돼야 하는 메소드를 정의한다.
이 과정을 잘 이해하기 위해선 JUnit이 하나의 테스트 클래스를 수행하는 흐름을 이해해야 한다.
@Test
가 붙었고 / public void
형이며 / 파라미터가 없는 테스트 메소드를 모두 찾는다.@Before
가 붙은 메소드가 있으면 먼저 실행한다.@Test
가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.@After
가 붙은 메소드가 있으면 실행한다.애플리케이션 컨텍스트가 만들어질 때는 모든 싱글톤 빈 오브젝트가 초기화된다.
이 과정에서 초기화 자체가 오랜 시간을 소요하기도 하고,
어떤 빈은 독자적으로 많은 리소스를 할당하거나 독립적인 스레드를 띄우기도 한다.
스프링은 JUnit을 이용하는 테스트 컨텍스트 프레임워크를 제공한다.
테스트 컨텍스트의 지원을 받으면 어노테이션을 붙여주는 것만으로도 테스트에서 필요로 하는 어플리케이션 컨텍스트를 만들어서 모든 테스트가 공유하게 할 수 있다.
@RunWith(SpringJUnit4ClassRunner.class)
스프링의 테스트 컨텍스트 프레임워크의 JUnit 확장기능 지정
테스트를 진행하는 중에 테스트가 사용할 애플리케이션 컨택스트를 자동으로 만들고 관리하는 작업을 진행
@ContextConfiguration(locations=”/applicationContext.xml”)
테스트 컨텍스트가 자동으로 만들어줄 애플리케이션 컨텍스트의 위치를 지정
@Autowired
변수에 할당 가능한 타입을 가진 빈을 자동으로 찾아 주입받음
테스트 과정에서 테스트에 의해 실제 운영용 DataSource가 변경되는 것은 문제를 야기할 수 있다.
따라서 DAO가 테스트에서만 다른 Datasource를 사용하게 하는 방법에 대해 생각해보자.
테스트 코드에서 빈 객체에 수동으로 다른 DataSource를 DI (@DirtiesContext
)
→ 애플리케이션 컨텍스트를 매번 새로 만들어야 한다는 부담감이 있다.
아예 테스트에서 사용될 DataSource 클래스를 빈으로 정의해둔 테스트 전용 설정파일을 따로 만들어두기
→ 번거롭게 수동으로 DI하지 않아도 되고, @DirtiesContext
메소드도 필요없다.
<bean id="dataSource">
class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
...
<property name="url" value="jdbc:mysql//localhost/testdb" />
...
</bean>
// Test Class
@RunWith(SpringJUnit4ClassRunner.class)
@CintextConfiguration(locations="/test-applicationContext.xml") // 설정파일 따로 적용하기
public class UserDaoTest {
...
}
컨테이너 없는 DI 테스트 해보기
→ 아예 스프링 컨테이너를 사용하지 않고 테스트를 만드는 방식이다.
public class UserDaoTest {
UserDao dao; // @Autowired 를 사용하지 않았다.
...
@Before
public void setUp() {
...
dao = new UserDao();
DataSource dataSource = new SingleConnectionDataSource("jdbc:mysql://localhost/testdb", "spring", "book", true);
dao.setDataSource(dataSource);
// 위 세 줄의 코드에서 객체 생성, 관계 설정 등을 모두 직접 해준다.
}
}
애플리케이션 컨텍스트를 아예 사용하지 않으니 코드는 더 단순해진다.
위의 예제를 통해, 컨테이너가 DI를 가능하게 해주는 역할은 아니라는 것을 알 수 있다.
즉, DI 컨테이너는 DI를 편하게 적용하도록 도움을 줄 뿐, 컨테이너가 DI를 가능하게 하지는 않는다.
스프링은 비침투적인 기술의 대표적 예이다.
침투적 기술이란 기술을 적용했을 때 코드에 기술 관련 API가 등장하거나, 특정 인터페이스나 클래스를 사용하도록 강제하는 기술을 말한다. 침투적 기술을 사용하면 애플리케이션 코드가 해당 기술에 종속되는 결과를 가져온다.
반면 비침투적인 기술은 기술이 애플리케이션 코드에 영향을 주지 않는다. 기술과 코드 사이에 종속성을 가지지 않는다. 따라서 위의 3번과 같은 DI 테스트 방식이 가능했던 것이다.
보통 실제 개발 환경에서는 2번의 방법을 많이 사용하고,
때에 따라 예외적인 의존관계를 강제로 구성해서 테스트해야 할 경우 1번의 방식을 사용한다.
개발자가 자신이 만든 코드가 아닌 다른 사람이 만든 코드와 기능에 대한 테스트를 작성하는 과정.
학습 테스트의 목적은 자신이 사용할 API나 프레임워크의 기능을 테스트로 보면서 사용 방법을 익히는 것이다.
학습 테스트를 작성하는 과정은 다음과 같은 장점을 가진다.
다양한 조건에 따른 기능을 손쉽게 확인해볼 수 있다.
학습 테스트 코드를 개발 중에 참고할 수 있다.
프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와준다.
🗣 버전업에 따라 Depreciated 된 어노테이션이나 메소드들을 어떻게 사용해야 할지 늘 답답했는데 학습 테스트 과정을 거치면 훨씬 편하게 사용할 수 있을 것 같습니다.
테스트 작성에 대한 좋은 훈련이 된다.
동작하는 코드를 보며 새로운 기술을 공부하는 과정이 즐거워진다.
코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트.
코드에서 오류가 발견되었을 때, 무턱대고 코드를 수정하기보단 먼저 버그 테스트를 만드는 편이 좋다.
동등분할
같은 결과를 내는 값의 범위를 구분해서 각 대표 값으로 테스트를 하는 방법.
경계값 분석
에러는 동등분할 범위의 경계에서 주로 발생한다는 특징을 이용한 테스트 방법.
경계의 근처값을 이용해 테스트하는 방법.