본문 바로가기

Spring

토비의 스프링 #2 테스트, #3 템플릿

2장 테스트

스프링이 개발자에게 제공하는 가장 중요한 가치가 무엇이냐고 질문한다면 나는 주저하지 않고 객체지향과 테스트라고 대답할 것이다. p145

애플리케이션은 계속 변하고 복잡해져 간다. 그 변화에 대응하는 첫 번째 전략이 확장과 변화를 고려한 객체지향적 설계와 그것을 효과적으로 담아낼 수 있는 IoC/DI 같은 기술이라면, 두 번째 전략은 만들어진 코드를 확신할 수 있게 해주고, 변화에 유연하게 대처할 수 있는 자신감을 주는 테스트 기술이다. p145


2.1 UserDaoTest 다시보기

2.1.1 테스트의 유용성

테스트란 결국 내가 예상하고 의도했던 대로 코드가 정확히 동작하는지를 확인해서 만든 코드를 확신 할 수 있게 해주는 작업이다.

2.1.2 UserDaoTest의 특징

public class UserDaoTest{
	public static void main(String[] args) throws SQLException{
		ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
		
UserDao dao = conext.getBean("userDao", UserDao.class);
User user = new User();
user.setId("user");
user.setName("백기선");
user.setPassword("married");

dao.add(user);
System.out.println(user.getId() + " 등록 성공");

User user2 = dao.get(user.getId());
System.out.println(user2.getName());
System.out.println(user2.getPassword());
System.out.println(user2.getId() + " 조회 성공");
  • 자바에서 가장 손쉽게 실행 가능한 main() 메소드를 이용한다.
  • 테스트할 대상인 UserDao의 오브젝트를 가져와 메소드를 호출한다.
  • 테스트에 사용할 입력 값을 직접 코드에서 만들어 넣어준다.
  • 테스트의 결과를 콘솔에 출력해준다.
  • 각 단계의 작업이 에러 없이 끝나면 콘솔에 성공 메시지로 출력해준다.

좋은 테스트를 짜야하는 이유

  • 작은 단위의 테스트 : 개발자가 설계하고 만든 코드가 원래 의도한 대로 동작하는지를 빠르게 확인
  • 자동수행 테스트 코드 : 반복 또 반복, 자주 반복할 수 있는 테스트가 돼야한다.
  • 지속적인 개선과 점진적인 개발을 위한 테스트

2.1.3 UserDaoTest의 문제점

수동 확인 작업의 번거로움

  • 입력한 값과 가져온 값이 일치하는지를 확인해 줘야함

실행 작업의 번거로움

  • main이 여러개 라면? 다 실행해 줘야 한다.

2.2 UserDaoTest 개선

2.2.1 테스트 검증의 자동화

테스트란 개발자가 마음 편하게 잠자리에 들 수 있게 해주는 것

  • 켄트 백

수정 전 테스트 코드

System.out.println(user2.getName());
System.out.println(user2.getPassword());
System.out.println(user2.getId() + " 조회 성공");

수정 후 테스트 코드

if(!user.getName().equals(user2.getName())){
	System.out.println("테스트 실패 (Name)");
}
else if(!user.getPassword().equals(user2.getPassword())){
	System.out.println("테스트 실패 (Password)");
}
else{
	System.out.println(user2.getId() + " 조회 성공");
}

2.2.2 테스트의 효율적인 수행과 결과관리

main() 메소드로는 한계가 있다.

애플리케이션의 규모가 커지고 테스트 개수가 많아지면 테스트를 수행하는 일이 부담이 될 것이다.

JUnit 테스트로 전환

프레임 워크는 개발자가 만든 클래스에 대한 제어 권한을 넘겨받아서 주도적으로 애플리케이션의 흐름을 제어한다.


2.3 JUnit

JUnit의 문법에 대해서는 생략

포괄적인 테스트

개발자가 테스트를 직접 만들 때 자주 하는 실수가 하나 있다. 바로 성공하는 테스트만 골라서 만드는 것이다. 개발자는 머릿속으로 이 코드가 잘 돌아가는 케이스를 상상하면서 코드를 만드는 경우가 일반저기다. 그래서 테스트를 작성할 때도 문제가 될 만한 상황이나, 입력 값 등은 교묘히도 잘 피해서 코드를 만드는 습성이 있다. p174

항상 네거티브 테스트를 먼저 만들라

  • 로드 존슨(스프링의 창시자)

Junit이 하나의 테스트 클래스를 가져와 테스트를 수행하는 방식

  1. 테스트 클래스에서 @Test 가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 모두 찾는다.
  2. 테스트 클래스의 오브젝트를 하나 만든다.
  3. @Before 가 붙은 메소드가 있으면 실행한다.
  4. @Test 가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.
  5. @After 가 붙은 메소드가 있으면 실행한다.
  6. 2~5번을 반복한다.
  7. 모든 테스트의결과를 종합해서 돌려준다.

2.3.4 TDD

기능 설계를 위한 테스트

테스트 코드는 마치 잘 작성된 하나의 기능정의서처럼 보인다. 그래서 보통 기능 설계, 구현, 테스트라는 일반적인 개발 흐름의 기능설계에 해당하는 부분을 이 테스트 코드가 일부분 담당하고 있다고 볼 수도 있다. 실제로 스프링 RestDocs에서는 테스트 코드를 이용해 API 설계서를 만들어 준다.

테스트 주도 개발

실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다

  • p176

TDD에서는 테스트를 작성하고 이를 성공시키는 코드를 만드는 작업의 주기를 가능한 한 짧게 가져가도록 권장한다.

  • p177

픽스처

테스트를 수행하는 데 필요한 정보나 오브젝트를 픽스처(fixture)라고 한다. 일반적으로 픽스처는 여러 테스트에서 반복적으로 사용되기 때문에 @Before메소드를 이용해 생성해두면 편리하다.


2.4 스프링 테스트 적용

애플리케이션 컨텍스트의 생성 방식

애플리케이션 컨텍스트를 Test의 @Before 메소드에서 생성해준다면 테스트 메소드 개수만큼 반복되기 때문에 애플리케이션 컨텍스트도 테스트의 개수만큼 만들어진다. 빈이 많아지고 복잡해지면 애플리케이션 컨텍스트 생성에 적지 않은 시간이 걸릴 수 있다. 애플리케이션 컨텍스트가 만들어질 때는 모든 싱글톤 빈 오브젝트를 초기화한다.

2.4.1 테스트를 위한 애플리케이션 컨텍스트 관리

스프링 테스트 컨텍스트 프레임워크 적용

  • ApplicationContext 타입의 인스턴스 변수를 선언한고 스프링이 제공하는 @Autowired 애노테이션을 붙여준다.
  • 클래스 레벨에 @RunWith와 @ContextConfiguration 애노테이션을 추가해준다.
@RunWith(SpringJUnit4ClassRunner.class) //스프링 테스트 컨텍스트 프레임워크의 JUnit 확장기능 지정
@ContextConfiguration(locations="/applicationContext.xml") //테스트 컨텍스트가 자동으로 만들어줄 애플리케이션 컨텍스트의 위치 지정
public class UserDaoTest{
	@Autowired
	private ApplicationContext context; //테스트 오브젝트가 만들어지고 나면 스프링 테스트 컨텍스트에 의해 자동으로 값이 주입된다.
  • 인스턴스 변수인 context는 초기화해주는 코드가 없다. 스프링 컨텍스트 프레임워크가 @Autowired 애노테이션을 통해 주입해준 것이다.
  • @Runwith 는 JUnit 프레임워크의 테스트 실행 방법을 확장할 때 사용하는 애노테이션이다. SpringJUnit4ClassRunner라는 JUnit용 테스트 컨텍스트 프레임워크 확장 클래스를 지정해주면 JUnit이 테스트를 진행하는 중에 테스트가 사용할 애플리케이션 컨텍스트를 만들고 관리하는 작업을 진행해준다.
  • @ContextConfiguration 은 자동으로 만들어줄 애플리케이션 컨텍스트의 설정파일 위치를 지정한 것이다.

위와 같이 테스트 컨텍스트 프레임워크를 적용하면 얻을 수 있는 이점

  • 테스트 메소드의 컨텍스트 공유
  • 테스트 클래스의 컨텍스트 공유

→ 성능이 대폭 향상된다.

@Autowired

@Autowired가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다. 타입이 일치하는 빈이 있으면 인스턴스 변수에 주입해준다. 일반적으로는 주입을 위해서는 생성자나 수정자 메소드 같은 메소드가 필요하지만, 이 경우에는 메소드가 없어도 주입이 가능하다. 또 별도의 DI 설정 없이 필드의 타입정보를 이용해 빈을 자동으로 가져올 수 있는데, 이런 방법을 자동와이어링이라고 한다.

2.5 학습 테스트, 버그 테스트

때로는 자신이 만들지 않은 프레임워크나 다른 개발팀에서 만들어서 제공한 라이브러리 등에 대해서도 테스트를 작성해야 한다. 목적은 자신이 사용할 API나 프레임워크의 기능을 테스트로 보면서 익히려는 것이다. (학습테스트) p.197

2.5.1 학습 테스트의 장점

  • 다양한 조건에 따른 기능을 손쉽게 확인해볼 수 있다.
  • 학습 테스트 코드를 개발 중에 참고할 수 있다.
  • 프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와준다.
  • 테스트 작성에 대한 좋은 훈련이 된다.
  • 새로운 기술을 공부하는 괒어이 즐거워진다.

2.5.3 버그테스트

버그테스트란 코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트를 말한다. 버그를 발견했을 때 무턱대고 코드를 뒤져가면서 수정하려고 하기보다는 먼저 버그 테스트를 만들어보는 편이 유용하다. 버그 테스트는 일단 실패하도록 만들어야 한다. 그러고 나서 버그 테스트가 성공할 수 있도록 애플리케이션 코드를 수정한다. 테스트가 성공하면 버그는 해결된 것이다.

버그테스트의 장점

  • 텟트ㅡ의 완성도를 높여준다.
  • 버그의 내용을 명확하게 분석하게 해준다.
  • 기술적인 문제를 해결하는 데 도움이 된다.

2.6 정리

2장에서는 다음과 같이 테스트의 필요성과 작성 방법을 살펴봤다.

  • 테스트는 자동화돼야 하고, 빠르게 실행할 수 있어야 한다.
  • main() 테스트 대신 JUnit 프레임워크를 이용한 테스트 작성이 편리하다.
  • 테스트 결과는 일관성이 있어야 한다. 코드의 변경 없이 환경이나 테스트 실행 순서에 따라서 결과가 달라지면 안 된다.
  • 테스트는 포괄적으로 작성해야 한다. 충분한 검증을 하지 않는 테스트는 없는 것보다 나쁠 수 있다.
  • 코드 작성과 테스트 수행의 간격이 짧을수록 효과적이다.
  • 테스트하기 쉬운 코드가 좋은 코드다.
  • 테스트를 먼저 만들고 테스트를 성공시키는 코드를 만들어가는 테스트 주도 개발 방법도 유용하다.
  • 테스트 코드도 애플리케이션 코드와 마찬가지로 적절한 리팩토링이 필요하다.
  • @Before, @After를 사용해서 테스트 메소드들의 공통 준비 작업과 정리 작업을 처리할 수 있다.
  • 스프링 테스트 컨텍스트 프레임워크를 이용하면 테스트 성능을 향상시킬 수 있다.
  • 동일한 설정파일을 사용하는 테스트는 하나의 애플리케이션 컨텍스트를 공유한다.
  • @Autowired를 사용하면 컨텍스트의 빈을 테스트 오브젝트에 DI할 수 있다.
  • 기술의 사용 방법을 익히고 이해를 돕기 위해 학습 테스트를 작성하자.
  • 오류가 발견될 경우 그에 대한 버그 테스트를 만들어두면 유용하다.

 

3장 정리

  • JDBC와 같은 예외가 발생할 가능성이 있으며 공유 리소스의 반환이 필요한 코드는 반드시 try/catch/finally 블록으로 관리해야 한다.
  • 일정한 작업 흐름이 반복되면서 그중 일부 기능만 바뀌는 코드가 존재한다면 전략 패턴을 적용한다. 바뀌지 않는 부분은 컨텍스트로, 바뀌는 부분은 전략으로 만들고 인터페이스를 통해 유연하게 전략을 변경할 수 있도록 구성한다.
  • 같은 애플리케이션 안에서 여러 가지 종류의 전략을 다이내믹하게 구성하고 사용해야 한다면 컨텍스트를 이용하는 클라이언트 메소드에서 직접 전략을 정의하고 제공하게 만든다.
  • 클라이언트 메소드 안에 익명 내부 클래스를 사용해서 전략 오브젝트를 구현하면 코드도 간결해지고 메소드의 정보를 직접 사용할 수 있어서 편리하다.
  • 컨텍스트가 하나 이상의 클라이언트 오브젝트에서 사용된다면 클래스를 분리해서 공유하도록 마는다.
  • 컨텍스트는 별도의 빈으로 등록해서 DI 받거나 클라이언트 클래스에서 직접 생성해서 사용한다. 클래스 내부에서 컨텍스트를 사용할 때 컨텍스트가 의존하는 외부의 오브젝트가 있다면 코드를 이용해서 직접 DI 해줄 수 있다.
  • 단일 전략 메소드를 갖는 전략 패턴이면서 익명 내부 클래스를 사용해서 매번 전략을 새로 만들어 사용하고, 컨텍스트 호출과 동시에 전략 DI를 수행하는 방식을 템플릿/콜백 패턴이라고 한다.
  • 콜백의 코드에도 일정한 패턴이 반복된다면 콜백을 템플릿에 넣고 재활용하는 것이 편리하다.
  • 템플릿과 콜백의 타입이 다양하게 바뀔 수 있다면 제네릭스를 이용한다.
  • 스프링은 JDBC 코드 작성을 위해 JdbcTemplate을 기반으로 하는 다양한 템플릿과 콜백을 제공한다.
  • 템플릿은 한 번에 하나 이상의 콜백을 사용할 수 있고, 하나의 콜백을 여러 번 호출할 수도 있다.
  • 템플릿/콜백을 설계할 때는 템플릿과 콜백 사이에 주고받는 정보에 관심을 둬야 한다.