테스트의 유용성

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

테스트의 결과가 원하는 대로 나오지 않는 경우에는 코드나 설계에 결함이 있음을 알 수 있다.

웹을 통한 DAO 테스트 방법의 문제점

DAO뿐만 아니라 서비스 클래스, 컨트롤러, JSP 뷰 등 모든 레이어의 기능을 다 만들고 나서야 테스트가 가능하다는 점이 큰 문제다.

  • 하나의 테스트를 수행하는데 참여하는 클래스와 코드가 너무 많다.
    • 테스드 실패 시 어디에서 문제가 발생했는지를 찾아내기 힘들다.
  • 테스트하고 싶은 기능(클래스? 계층? 아무튼 작은 단위)에 다른 계층의 코드와 컴포넌트, 심지어 서버의 설정 상태까지 모두 테스트에 영향을 줄 수 있다.
    • 번거롭고, 오류가 있을 때 빠르고 정확하게 대응하기가 힘들다는 문제가 있다.

작은 단위의 테스트

테스트는 가능하면 작은 단위로 쪼개서 집중해서 할 수 있어야 한다.

  • 관심사의 분리라는 원리가 여기에도 적용. 테스트의 관심이 다르다면 테스트할 대상을 분리하고 집중해서 접근해야함
  • 작은 단위의 코드에 대해 테스트를 수행한 것을 단위 테스트(unit test) 라고 함
    • 단위란 무엇인지, 그 크기와 범위가 어느 정도인지 정해진건 아님
    • 한 기능을 모두 통틀어서 하나의 단위로 보거나, 메소드 하나만 가지고 하나의 단위라고 생각할 수도 있음
    • 충분히 하나의 관심에 집중해서 효율적으로 테스트할 만한 범위의 단위라고 보면 됨
    • 일반적으로 단위는 작을수록 좋다.
    • 통제할 수 없는 외부의 리소스에 의존하는 테스트는 단위 테스트가 아니라고 보기도 함
    • 길고 많은 단위가 참여하는 테스트도 언젠가는 필요하다
  • 단위테스트를 하는 이유
    • 개발자가 설계하고 만든 코드가 원래 의도한 대로 동작하는지를 개발자 스스로 빨리 확인받기 위해서
    • 확인의 대상과 조건이 간단하고 명확할수록 좋다

자동수행 테스트 코드

테스트는 자동으로 수행되도록 코드로 만들어지는 것이 중요하다.

  • 애플리케이션을 구성하는 클래스 안에 테스트 코드를 포함시키는 것보다는 별도로 테스트용 클래스를 만들어서 테스트 코드를 넣는 편이 낫다.
  • 장점
    • 자주 반복할 수 있다. 번거로운 작업이 없고 테스트를 빠르게 실행할 수 있기 때문에 언제든 코드를 수정하고 나서 테스트를 해볼 수 있다.

지속적인 개선과 점진적인 개발을 위한 테스트

  • 단순 무식한 방법으로 정상작동하는 코드를 만들고, 테스트를 만들어뒀기 때문에 매우 작은 단계를 거쳐가면서 계속 코드를 개선해나갈 수 있다.
    • 작은 단계를 거치는 동안 테스트를 수행해서 확신을 가지고 코드를 변경해갔기 때문에 전체적으로 코드를 개선하는 작업에 속도가 붙고 더 쉬어졌을 수도 있다.
  1. 일단은 가장 단순한 기능을 만들고, 이를 테스트로 검증해서 만든 코드에 대한 확신을 갖는다.
  2. 조금씩 기능을 더 추가해가면서 그에 대한 테스트도 추가하는 식으로 점진적인 개발이 가능해진다.

테스트 검증의 자동화

  • 포괄적인 테스트(comprehensive test)
    • 만들어진 코드의 기능을 모두 점검할 수 있는 테스트
    • 과감한 수정을 하고 나서도 테스트를 모두 돌려보고 나면 안심
    • 테스트를 통해 그 변경에 영향을 받는 부분이 정확히 확인된다면 빠르게 조치를 취할 수 있음
  • 자동화된 테스트
    • 기존 애플리케이션 코드에 수정을 할때 마음의 평화를 얻고, 자신이 만지는 코드에 대해 항상 자신감을 가질 수 있으며, 새로 도입한 기술의 적용에 문제가 없는지 학인할 수 있는 가장 좋은 방법
    • 빠르게 실행 가능하고 스스로 테스트 수행과 기대하는 결과에 대한 확인까지 해주는 코드로 된 자동화된 테스트

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

일정한 패턴을 가진 테스트를 만들 수 있고,

많은 테스트를 간단히 실행시킬 수 있으며,

테스트 결과를 종합해서 볼 수 있고,

테스트가 실패한 곳을 빠르게 찾을 수 있는 기능을 갖춘 테스트 지원도구와 그에 맞는 테스트 작성 방법이 필요

개발자를 위한 테스팅 프레임워크 JUnit

  • JUnit은 프레임워크다.
  • 사실상 자바의 표준 테스팅 프레임워크라고 불릴 만큼 폭넓게 사용되고 있다.
  • 프레임워크에서 동작하는 코드는 main() 메소드도 필요 없고 오브젝트를 만들어서 실행시키는 코드를 만들 필요도 없다.

테스트 결과의 일관성

코드에 변경사항이 없다면 테스트는 항상 동일한 결과를 내야 한다.

동일한 결과를 보장하는 테스트

단위 테스트는

  • 코드가 바뀌지 않는다면 매번 실행할 때마다 동일한 테스트 결과를 얻을 수 있어야 한다.
  • 항상 일관성 있는 결과가 보장돼야 한다.
  • 외부환경에 영향을 받지 말아야 하는 것은 물론이고
  • 테스트를 실행하는 순서를 바꿔도 동일한 결과가 보장되도록 만들어야 한다.

포괄적인 테스트

문제가 있는 코드인데도 테스트가 성공하게 만드는 건 위험하다. 특히 한 가지 결과만 검증하고 마는 것은 상당히 위험하다.

  • JUnit은 특정한 테스트 메소드의 실행 순서를 보장해주지 않는다.
  • 모든 테스트는 실행 순서에 상관없이 독립적으로 항상 동일한 결과를 낼 수 있도록 해야함
  • 네거티브 테스트를 먼저 만들어라

테스트가 이끄는 개발

기능설계를 위한 테스트

기능설계에 해당하는 부분을 테스트 코드가 담당 마치 코드로 된 설계문서

테스트 주도 개발(TDD, Test Driven Development)

만들고자 하는 기능의 내용을 담고 있으면서 만들어 진 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법 실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다

  • 테스트를 먼저 만들고 그 테스트가 성공하도록 하는 코드만 만드는 식으로 진행
  • 테스트를 작성하는 시간과 애플리케이션 코드를 작성하는 시간의 간격이 짧아진다.
  • 코드에 대한 피드백을 매우 빠르게 받을 수 있다.
  • 테스트 작성하고 이를 성공시키는 코드를 만드는 작업의 주기를 가능한 짧게 가져가도록 권장

테스트 코드 개선

  • 내부구조와 설계를 개선해서 좀 더 깔끔하고 이해하기 쉬우며 변경이 용이한 코드로 만들 필요가 있다.

  • 테스트 결과가 일정하게 유지되어야 함

  • 각 테스트 메소드를 실행할 때마다 테스트 클래스의 오브젝트를 새로 만든다.

    • 각 테스트가 독립적으로 실행됨을 확실히 보장해주기 위해 매번 새로운 오브젝트를 만들게 함 JUnit의 테스트 메소드 실행 방법

픽스처(fixture)

테스트를 수행하는 데 필요한 정보나 오브젝트

  • 일반적으로 픽스처는 여러 테스트에서 반복적으로 사용되기 때문에 @Before 메소드를 이용해 생성해두면 편리하다.

스프링 테스트 적용

  • 애플리케이션 컨텍스트 생성 방식이 문제?
  • @Before 메소드가 테스트 메소드 개수만큼 반복되기 때문에 애플리케이션 컨텍스트도 여러개 만들어진다.
    • 빈이 많아지고 복잡해지면 애플리케이션 컨텍스트 생성이 오래 걸릴수 있음
    • 애플리케이션 컨텍스트가 만들어질 때는 모든 싱글톤 빈 오브젝트를 초기화
      • 애플리케이션 컨텍스트가 초기화될 때 어떤 빈은 독자적으로 많은 리소스를 할당, 독립적인 스레드를 띄우기도 함
        • 테스트를 마칠 때마다 애플리케이션 컨텍스트내의 빈이 할당한 리소스 등을 깔끔하게 정리해주지 않으면 다음 테스트에서 새로운 애플리케이션 컨텍스트가 만들어지면서 문제를 일으킬 수도 있다.
  • 독립적으로 매번 새로운 오브젝트를 만들어서 사용하는 것이 원칙
    • 생성에 많은 시간과 자원이 소모되는 경우, 테스트 전체가 공유하는 오브젝트를 만들기도 함
    • 이때도 테스트는 일관성 있는 실행 결과를 보장
    • 순서가 결과에 영향을 미치지 않아야 함
  • JUnit은 테스트 클래스 전체에 걸쳐 딱 한번만 실행되는 @BeforeClass 스태틱 메소드를 지원
    • 스프링이 직접 제공하는 애플리케이션 컨텍스트 테스트 지원 기능이 더 편리?

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

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

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml")
public class UserDaoTest {
	@Autowired
	priivate ApplicationContext context;
	...

	@Before
	public void setUp() {
		this.dao = this.context.getBean("userDao", UserDao.class);
		...
	}

테스트 메소드의 컨텍스트 공유

  • 테스트가 실행되기 전, 딱 한 번만 애플리케이션 컨텍스트를 만든다.
  • 테스트 오브젝트가 만들어질 때마다 특별한 방법을 이용해 애플리케이션 컨텍스트 자신을 테스트 오브젝트의 특정 필드에 주입(일종의 DI라고 볼수 있지만 오브젝트 사이의 관계를 관리하기 위한 DI와는 조금 성격이 다름)

테스트 클래스의 컨텍스트 공유

@ContextConfiguration(locations="/applicationContext.xml") 같은 설정파일을 가진 애플리케이션 컨텍스트를 사용한다면 테스트 수행 중에 단 한개의 애플리케이션 컨텍스트만 만들어진다.

@Autowired

스프링의 DI에 사용되는 특별한 애노테이션

  1. @Autowired가 붙은 인스턴스 변수가 있을때
  2. 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다
  3. 타입이 일치하는 빈이 있으면 인스턴스 변수에 주입 별도의 DI 설정 없이 필드의 타입정보를 이용해 빈을 자동으로 가져올 수 있다.(타입에 의한 자동와이어링)

DI와 테스트

인터페이스를 두고 DI를 적용해야 하는 이유

  1. 소프트웨어 개발에서 절대로 바뀌지 않는 것은 없다.
  2. 클래스의 구현 방식은 바뀌지 않는다고 하더라도 인터페이스를 두고 DI를 적용하게 해두면 다른 차원의 서비스 기능을 도입할 수 있다.
  3. 테스트 때문이다.

테스트 코드에 의한 DI

@DirtiesContext  // 테스트 메소드에서 애플리케이션 컨텍스트의 구성이나 상태를 변경한다는 것을 테스트 컨텍스트 프레임워크에 알려준다.
public class UserDaoTest {
	@Autowired
	UserDao dao;

	@Before
	public void setUp() {
		DataSource dataSource = new SingleConnectionDataSource(...);
		dao.setDataSource(dataSource);  // 코드에 의한 수동 DI
	}

테스트를 위한 별도의 DI 설정

테스트를 위한 설정파일을 만들어서 테스트 클래스의 ©ContextConfiguration의 locations의 값을 테스트 전용 설정파일로 변경

컨테이너 없는 DI 테스트

public class UserDaoTest {
	UserDao dao;

	@Before
	public void setUp() {
		...
		dao = new UserDao();
		DataSource dataSource = new SingleConnectionDataSource(...);
		dao.setDataSource(dataSource);
		// 오브젝트 생성, 관계설정 등을 모두 직접 해준다.
	}

DI 컨테이너나 프레임워크는 DI를 편하게 적용하도록 도움을 줄 뿐, 컨테이너가 DI를 가능하게 해주는 것은 아니다.

학습 테스트로 배우는 스프링

자신이 만들지 않은 프레임워크나 다른 개발팀에서 만들어서 제공한 라이브러리 등에 대한 테스트

자신이 사용할 API나 프레임워크의 기능을 테스트로 보면서 사용 방법을 익히려는 것

학습 테스트의 장점

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

버그 테스트

장점

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

Ref.

이일민, “토비의 스프링 3.1”, 에이콘 출판사(2012)