토비의스프링vol.1

[토비의 스프링vol.1 2장] 테스트

ohyujeong 2022. 10. 7. 22:15

 

2장 테스트

만들어진 코드를 확신할 수 있게 해주고, 변화에 유연하게 대처할 수 있게 자신감을 주는 기술

 

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

  • 서비스 클래스, 컨트롤러, JSP 뷰 등 모든 레이어의 기능을 다 만든 후에야 테스트가 가능
  • 에러 메시지와 호출 스택 정보만으로 원인 찾기 힘듦
  • DAO를 테스트 하려고 만든 다른 레이어의 코드 때문에 에러가 날 수 있음
  • 테스트 하고 싶은 대상에 다른 계층의 코드, 컴포넌트 서버의 설정 상태가 모두 영향을 줌

작은 단위의 테스트

가능한 작은 단위로 쪼개서 집중 -> 관심사의 원리 적용

UserDaoTest 를 수행할 때 웹 인터페이스, 이를 위한 MVC 클래스, 서비스 오브젝트, 서버 배포 필요가 없음
-> 단위 테스트(Unit Test)

  • 통제할 수 없는 외부의 리소스에 의존하지 않음

단위 테스트를 하는 이유

  • 코드가 의도한 대로 동작하는 지 개발자 스스로 빨리 확인받기 위해
  • 확인의 대상과 조건이 간단하고 명확할수록 좋음
  • 그래서 작은 단위로 테스트

자동수행 테스트 코드

테스트 수행에 부담이 없도록 자동화가 되어야 함
-> 자주 반복할 수 있기 때문에 언제든 코드 수정 후 테스트가 가능

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

  • 새로운 기능이 의도대로 동작하는 지 확인 + 기존 기능이 새로운 코드에 영향 받지 않고 잘 동작하는 지 확인
  • 테스트를 수행하는 동안 확신을 가지고 코드를 변경 -> 코드 개선 속도 빨라짐

UserDaoTest 의 문제점

  1. 수동 확인 작업의 번거로움
    • 테스트 결과를 사람의 눈으로 확인하는 과정이 필요하다.
  2. 실행 작업의 번거로움
    • main() 메소드를 매번 실행하는 것은 번거롭고, DAO 가 수백 개가 된다면 전체 기능을 테스트 해보기 위해 main() 메소드 수백 번 실행해야 함

JUnit 테스트

Junit: 단위 테스트를 위한 프레임워크

조건

  1. public 메소드 선언
  2. 메소드에 @Test 어노테이션 붙임
  • assertThat() 메소드
    • 첫 번째 파라미터의 값을 뒤에 나오는 매처라고 불리는 조건으로 비교해서 일치하면 다음으로, 아니면 테스트가 실패하도록 만듦
if (!user.getName().equals(user2.getName)) {...}
==
assertThat(user2.getName), is(user.getName());

Junit 테스트 수행 7단계

  1. 테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 모두 찾는다.
  2. 테스트 클래스의 오브젝트를 하나 만든다.
  3. @Before가 붙은 메소드가 있으면 실행한다.
  4. @Test가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.
  5. @After가 붙은 메소드가 있으면 실행한다.
  6. 나머지 테스트 메소드에 대해 2~5번을 반복한다.
  7. 모든 테스트의 결과를 종합해서 돌려준다
  • 테스트 메소드 실행마다 새로운 오브젝트 생성
    • 각 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 보장해주기 위해
  • 테스트 메소드의 일부에서만 공통적으로 사용되는 코드가 있다면? 2가지 방법
    • @Before 보다는, 일반적인 메소드 추출 방법을 써서 메소드를 분리하고 테소트 메소드에서 직업 호출해 사용한다.
    • 공통적인 특징을 지닌 테스트 메소드를 모아서 별도의 테스트 클래스로 만든다.

픽스처

  • 테스트를 수행하는 데 필요한 정보나 오브젝트
  • 여러 테스트에서 반복적으로 사용되기 때문에 @Before 메소드를 이용해 생성해두면 편함

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

  • 단위 테스트는 항상 일관성 있는 결과가 보장되어야 함
  • DB에 남아 있는 데이터와 같은 외부 환경에 영향 받지 X
  • 테스트를 실행하는 순서를 바꿔도 동일한 결과 보장

예외상황 테스트

@Test(expected=EmptyResultDataAccessExceptiom.class)

테스트 중에 발생할 것으로 기대하는 예외 클래스를 지정한다.

->정상적으로 테스트 메소드 마치면 테스트 실패
->expected가 지정한 예외가 나오면 테스트 성공

테스트 주도 개발(TDD)

  • 테스트 코드를 먼저 작성하고, 테스트를 성공하게 해주는 기능의 내용을 담은 코드를 작성함
  • 테스트 코드는 하나의 잘 작성된 기능 정의서
    • 테스트가 실패하면, 설계한 대로 코드가 만들어지지 않은 것
    • 테스트가 성공하면, 그 순간 코드 구현과 테스트라는 두 가지 작업이 동시에 끝남

장점

  • 테스트를 빼먹지 않고 꼼꼼히 만들어낼 수 있다.
  • 테스트 작성 시간과 애플리케션 코드 작성 시간의 간격이 짧아진다.
    • 코드에 대한 피드백이 빨라진다.

사실 개발자의 머릿속에서는 테스트를 진행 하면서 코딩하는 것.

-> 머릿속의 시뮬레이션을 실제 테스트 코드로 끄집어내는 것이다.

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

애플리케이션 컨텍스트가 만들어 질 때 모든 싱글톤 빈 오브젝트를 초기화 함.

단순히 빈 오브젝트 생성이 아닐 때, 발생 할 수 있는 문제점

  • 어떤 빈은 오브젝트가 생성될 때 자체적인 초기화 작업 진행해서 많은 시간 필요로 함
  • 애플리케이션 컨텍스트가 초기화 될 때 어떤 빈은 독자적으로 많은 리소스를 할당하거나 독립적인 쓰레드를 띄움

테스트는 가능한 독립적으로 새로운 오브젝트를 만들어서 사용하는 것이 원칙

-> 애플리케이션 컨텍스트처럼 생성에 많은 시간과 자원이 소모되는 경우에는 테스트 전체가 공유하는 오브젝트 만듦
-> 애플리케이션 컨텍스트는 초기화 되고 나면 내부의 상태가 바뀌는 일은 거의 없기 때문에 여러 테스트가 공유해서 사용 가능
-> But, Junit은 매번 테스트 클래스의 오브젝트 새로 만듦
-> @BeforeClass 스태틱 메소드를 통해 애플리케이션 컨텍스트를 스태틱 변수에 저장할 수 있음
-> 이보다는 스프링이 직접 제공하는 지원 기능 사용하는 것이 편리함

클래스 레벨에 추가

@RunWith : JUnit 프레임워크의 테스트 실행 방법 확장할 때 사용

@ContextConfiguration : 테스트 컨텍스트가 자동으로 만들어줄 애플리케이션 컨텍스트의 설정파일 위치 지정

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

  • 테스트 클래스가 같은 설정 파일을 가진 애플리케이션 컨텍스트 사용하면, 공유하게 해줌
  • 테스트 클래스마다 다른 설정 파일도 ok, 설정파일 종류만큼 컨텍스트 만들 수 있음

@AutoWired

  • @Autowired가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다
    • 타입이 일치하는 빈이 있으면 인스턴스 변수에 주입해준다.
    • 일반적으로 주입을 위해서는 생성자, 수정자 등의 메소드 필요하지만 이 경우에는 메소드 없어도 주입이 가능
    • 별도의 DI 설정 없이 필드의 타입정보를 이용해 빈을 자동으로 가져올 수 있음 (타입에 의한 자동 와이어링)
    • 애플리케이션 컨텍스트는 초기화할 때 자기자신도 빈으로 등록, 따라서 ApllicationContext 타입의 빈 존재, AutoWired 통해 DI 가능
  • @Autowired는 변수에 할당 가능한 타입을 가진 빈을 자동으로 찾는다.
    • 같은 타입의 빈이 두 개 이상 있는 경우에는 타입만으로는 어떤 빈을 가져올지 결정 X
    • 타입으로 가져올 빈 하나를 선택할 수 없는 경우에 변수의 이름과 같은 이름의 빈이 있는지 확인한다.

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

  1. 소프트웨어 개발에서 절대 바뀌지 않는 것은 없다.
  2. 만약 클래스의 구현 방식은 바뀌지 않는다 해도, 다른 차원의 서비스 기능을 도입을 쉽게 할 수 있다.
  3. 효율적인 테스트를 손쉽게 만들기 위해서다.

@DirtiesContext

  • 스프링의 테스트 컨텍스트 프레임워크에게 해당 클래스의 테스트에서 애플리케이션 컨텍스트의 상태를 변경한다는 것을 알려줌
  • 테스트 컨텍스트는 이 애노테이션이 붙은 테스트 클래스에는 애플리케이션 컨텍스트 공유 허용 X
  • 빈의 의존관계를 강제로 DI 하는 방법을 사용했을 때의 문제를 피할 수 있음
  • 하나의 메소드에서만 컨텍스트 상태 변경한다면, 클래스 레벨이 아니라 메소드 레벨에서 적용하는 게 나음

테스트 시 DI 방법

  • 테스트 코드에 의한 DI (@DirtiesContext)
  • 테스트를 위한 테스트 전용 DI 설정 파일
  • 컨테이너 없는 DI 테스트 (이걸 최우선으로 고려한다.)

정리

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