인생을 코딩하다.

[토비의 스프링 2장 정리] 테스트 본문

Spring

[토비의 스프링 2장 정리] 테스트

Hyung1 2021. 6. 4. 20:59
728x90
반응형

2장, 테스트

p.145)

  • 변하고 복잡해져가는 애플리케이션에 대응하는 첫 번쨰 전략이 확장과 변화를 고려한 객체지향적 설계와 그것을 효과적으로
    담아낼 수 있는 IoC/DI 같은 기술이라면

두 번째 전략은 만들어진 코드를 확신할 수 있게 해주고, 변화에 유연하게 대처 할 수 있는 자신감을 주는 테스트 기술이다.

p.146)

  • 테스트란 결국 내가 에상하고 의도했던 대로 코드가 정확히 동작하는지를 확인해서, 만든 코드를 확신할 수 있게 해주는 작업이다.
  • 또한 테스트의 결과가 원하는 대로 나오지 않는 경우에는 코드나 설계에 결함이 있음을 알 수 있다.
  • 이를 통해 코드의 결함을 제거해가는 작업, 일명 디버깅을 거치게 되고, 결국 최종적으로 테스트가 성공하면 모든 결함이 제거됐다는 확신을 얻을 수 있다.

p.149)

작은 단위의 테스트

  • 테스트의 관심이 다르다면 테스트 할 대상을 분리하고 집중해서 접근해야 한다. 즉, 한 가지 관심에 집중할 수 있게
    작은 단위로 만들어야 한다.
  • 이렇게 작은 단위의 코드에 대해 테스트를 수행한 것을 단위 테스트라고 한다.
  • 충분히 하나의 관심에 집중해서 효율적으로 테스트할 만한 범위의 단위라고 보면 된다.
  • 사용할 DB의 상태를 테스트가 관장하고 있다면 이는 단위 테스트라고 해도 된다.

다만 DB의 상태가 매번 달라지고, 테스트를 위해 DB를 특정 상태로 만들어줄 수 없다면 그때는 단위 테스트로써의 가치가 없어진다. 즉, 통제할 수 없는 외부의 리소스에 의존하는 테스트는 단위 테스트가 아니라고 보기도 하는 것이다.

p.150)

  • 단위 테스트를 하는 이유는 개발자가 설계하고 만든 코드가 원래 의도한 대로 동작하는지를 개발자 스스로 빨리 확인받기 위해서다.

p.151 ~ 152)

자동 수행 테스트 코드

  • 테스트 자체가 사람의 수작업을 거치는 방법을 사용하기보다는 코드로 만들어져서 자동으로 수행될 수 있어야 한다.
  • 클래스를 분리하고 유연한 설계구조로 발전시키면서 테스트 코드를 넣을 위치를 결정하기가 애매하기 때문에 과거엔 별개의 클래스를 만들고 그 안에 테스트 코드를 넣었었다.

자동으로 수행되는 테스트의 장점은 자주 반복할 수 있다는 것이다.

  • 개발을 일단 완료하고 실전에서 운영 중인 상황에서 코드를 수정하려고 한다면 아무리 간단한 수정이라고 하더라도
    이게 그럭저럭 돌아가는 전체 애플리케이션에 심각한 문제를 일으키지는 않을까 하는 두려움이 앞선다.
    • 그럴 때 만들어둔 기능에 대한 테스트가 있다면 수정 후 빠르게 전체 테스트를 수행해서 수정 때문에 다른 기능에 문제가 발생하지는 않는지 재빨리 확인하고, 성공한다면 마음에 확신을 얻을 수 있다.

p.153)

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

  • 테스트를 이용하면 새로운 기능도 기대한 대로 동작하는지 확인할 수 있을 뿐 아니라, 기존에 만들어뒀던 기능들이
    새로운 기능을 추가하느라 수정한 코드에 영향을 받지 않고 여전히 잘 동작하는지를 확인할 수도 있다.

p.154)

모든 테스트는 성공과 실패 두 가지 결과를 가질 수 있다.

  • 테스트의 실패는 테스트가 진행되는 동안에 에러가 발생해서 실패하는 경우와,
  • 테스트 작업 중에 에러가 발생하진 않았지만 그 결과가 기대한 것과 다르게 나오는 경우로 구분해 볼 수 있다.
    전자를 테스트 에러, 후자를 테스트 실패로 구분해서 부르겠다.

p.157)

  • 프레임워크는 개발자가 만든 클래스에 대한 제어 권한을 넘겨받아서 주도적으로 애플리케이션의 흐름을 제어한다.
  • 새로 만들 테스트는 Junit 프레임워크가 요구하는 조건 두 가지를 따라야한다. 첫째는 메소드가 public으로 선언돼야 하는 것이고, 다른 하나는 메소드에 Test라는 애노테이션을 붙여주는 것이다.

p.168)

단위 테스트는 항상 일관성 있는 결과가 보장돼야 한다는 점을 잊어서는 안된다.

  • DB에 남아 있는 데이터와 같은 외부 환경에 영향을 받지 말아야 하는 것은 물론이고, 테스트를 실행하는 순서를 바꿔도 동일한 결과가 보장되도록 만들어야 한다.

p.173)

@Test(expected=EmptyResultDataAccessExceptiom.class)

@Test에 execpted를 추가해놓으면 보통의 테스트와는 반대로, 정상적으로 테스트 메소드를 마치면 테스트가 실패하고,
expected에서 지정한 예외가 던져지면 테스트가 성공한다.

p.176)

테스트 주도 개발

만들고자 하는 기능의 내용을 담고 있으면서 만들어진 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고,
테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법이 있다. 이를 테스트 주도 개발이라고 한다.

p.181 ~ 182)

Junit이 하나의 테스트 클래스를 가져와 테스트를 수행하는 방식은 다음과 같다

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

또 한가지 기억해야 할 사항은 각 테스트 메소드를 실행할 떄 마다 테스트 클래스의 오브젝트를 새로 만든다는 점이다. 한 번 만들어진 테스트 클ㄹ스의 오브젝트는 하나의 테스트 메소드를 사용하고 나면 버려진다.

 

그런데, 왜 테스트 메소드를 실행할 때마다 새로운 오브젝트를 만드는 것일까?

  • Junit 개발자는 각 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 확실히 보장해주기 위해 매번 새로운 오브젝트를 만들게 했다.

테스트 메소드의 일부에서만 공통적으로 사용되는 코드가 있다면 어떻게 해야할까?

  • 이때는 @Before를 사용하기보다는 일반적으로 메소드 추출 방법을 써서 메소드를 분리하고 테스트 메소드에서 직접 호출해 사용하도록 만드는 편이 낫다.
  • 아니면 아예 공통적인 특징을 지닌 테스트 메소드를 모아서 별도의 테스트 클래스로 만드는 방법도 생각해 볼 수 있다.

픽스처

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

p.183)

스프링 테스트 적용

애플리케이션 컨텍스트가 만들어 질때는 모든 싱글톤 빈 오브젝트를 초기화한다. 단순히 빈 오브젝트를 만드는 정도라면
상관없지만, 어떤 빈은 오브젝트가 생성될 때

  1. 자체적인 초기화 작업을 진행해서 제법 많은 시간을 필요로 하기 때문이다.
  2. 또 한가지 문제는 애플리케이션 컨텍스트가 초기화 될 떄 어떤 빈은 독자적으로 많은 리소스를 할당하거나 독립적인 쓰레드를 띄우기도 한다.

하지만 애플리케이션 컨텍스트처럼 생성에 많은 시간과 자원이 소모되는 경우에는 테스트 전체가 공유하는 오브젝트를 만들기도 한다. 스프링은 JUnit을 이용하는 테스트 컨텍스트 프레임워크를 제공한다. 테스트 컨텍스트의 지원을 받으면 간단한 애노테이션 설정 만으로 테스트에서 필요로 하는 애플리케이션 컨텍스트를 만들어 모든 테스트를 공유하게 할 수 있다.

p.185)

@RunWith는 Junit 프레임워크의 테스트 실행 방법을 확장할 때 사용하는 애노테이션이다. SpringUnit4ClassRunner라는 Junit용 테스트 컨텍스트 프레임워크 확장 클래스를 지정해주면 Junit이 테스트를 진행하는 중에 테스트가 사용할 애플리케이션 컨텍스트를 만들고 관리하는 작업을 진행해준다.

 

@ContextConfiguration은 자동으로 만들어줄 애플리케이션 컨텍스트의 설정파일 위치를 지정한 것이다.

p.186)

Junit 확장기능은 테스트가 실행되기 전에 딱 한번만 애플리켕이션 컨텍스트를 만들어두고, 테스트 오브젝트가
만들어질 때 마다 특별한 방법을 이용해 애플리케이션 컨텍스트 자신을 테스트 오브젝트의 특정 필드에 주입해주는 것이다.

p.187

하나의 테스트 클래스 내의 테스트 메소드 값은 애플리케이션 컨텍스트를 공유해서 사용할 수 있다.

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

  • 여러 개의 테스트 클래스가 있는데 모두 같은 설정파일을 가진 애플리케이션 컨텍스트를 사용한다면, 스프링은 테스트 클래스
    사이에서도 애플리케이션 컨텍트를 공유하게 해준다.
  • 스프링 애플리케이션 컨텍스트는 초기화 할 때 자기 자신도 빈으로 등록한다. 따라서 애플리케이션 컨텍스트에는
    ApplicationContext 타입의 빈이 존재하는 셈이고 DI도 가능 한 것이다.

@Autowired

  • @Autowired가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다.
    타입이 일치하는 빈이 있으면 인스턴스 변수에 주입해준다.
  • @Autowired는 변수에 할당 가능한 타입을 가진 빈을 자동으로 찾는다.
  • @Autowired는 타입으로 가져올 빈 하나를 서낵할 수 없는 경우에 변수의 이름과 같은 이름의 빈이 있는지 확인한다.

메소드 레벨의 @DirtiesContext

  • @DirtiesContext는 클래스에만 적용할 수 있는 건 아니다. 하나의 메소드에서만 컨텍스트 상태를 변경한다면 메소드 레벨에서 @DirtiesContext를 붙여주는 편이 낫다. 해당 메소드의 실행이 끝나고 나면 이후에 진행되는 테스트를 위해 변경된 애플리케이션 컨텍스트는 폐기되고 새로운 애플리케이션 컨텍스트가 만들어진다.
  • @DirtiesContext를 이용하면 일단 테스트에서 빈의 의존관계를 강제로 DI 하는 방법을 사용했을 떄 문제는 피할 수 있다. 하지만 이 때문에 애플리케이션 컨텍스트를 매번 만드는 건 조금 찜찜하다.

정리

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

스프링을 사용하는 개발자라면 자신이 만든 코드를 테스트로 검정하는 방법을 알고 있어야하며, 테스트를 개발에 적극적으로 활용할 수 있어야 한다.

728x90
반응형
Comments