ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2.4 스프링 테스트 적용 ~ 2.6 정리
    Java & Spring/토비의 스프링 3.1 2021. 6. 3. 14:32
    반응형

    2장 테스트

    2.4 스프링 테스트 적용

    • 앞서 작성한 테스트 코드에서 @Before(Junit5는 BeforeEach)로 인해 Application Context가 테스트 메소드 개수만큼 생성되는 문제점이 존재

      • Application Context 생성에는 많은 시간과 자원이 소모
      • 테스트 전체가 공유하는 오브젝트로 수정
        • 빈은 싱글톤으로 만들었기 때문에 무상태성
        • UserDao 빈을 가져다 메소드를 사용한다고 해서 상태가 바뀌지 않음
      • @Before(Junit5 -> @BeforeEach)@BeforeClass(Junit5 -> @BeforeAll)로 수정
        • 테스트 클래스 전체에 걸쳐 딱 한번만 실행
    • 테스트를 위한 Application Context 관리

      • Junit을 이용하는 테스트 컨텍스트 프레임워크를 제공
      • @Autowired를 이용해서 빈 주입
        • Autowired
          • 스프링의 DI에 사용되는 애노테이션
          • 변수 타입과 일치하는 컨텍스트 내의 빈을 찾아 주입
          • 같은 타입의 빈이 두 개 이상 있는 경우에는 타입만으로 결정할 수 없음
            • 변수 이름과 같은 이름의 빈을 찾아 주입
      • Test Class에는 @Runwith@ContextConfiguration(location="path") 를 붙임
        • Runwith
          • 테스트 실행 방법을 확장 ~> JUnit이 테스트 진행 중, 테스트가 사용할 Application Context를 만들고 관리
        • ContextConfiguration
          • Application Context의 설정 파일 위치를 지정
          • 테스트와 운영 DB를 공유하는 경우, 올바른 테스트 및 운영에 어려움이 있음
      • 테스트 메소드의 컨텍스트 공유
        • Junit 확장기능은 테스트 실행 전, 한 번만 Application Context를 만들어두고, 테스트 오브젝트가 만들어질 때마다 특별한 방법을 이용해서 Application Context 자신을 테스트 오브젝트의 특정 필드에 주입(일종의 DI)
      • 테스트 클래스의 컨텍스트 공유
        • 스프링은 테스트 클래스 사이에서도 Application Context를 공유하게 해줌
        • 같은 설정파일 사용 ~> 단 한 개의 Application Context 생성 & 공유
        • 스프링 Application Context는 초기화할 때, 자기 자신도 빈으로 등록
    • DI와 테스트

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

        1. 소프트웨어 개발에서 절대로 바뀌지 않는 것은 없다

          • 수정에 대한 비용 부담을 줄일 수 있음
        2. 인터페이스를 두고 DI를 적용하면, 다른 차원의 서비스 기능 도입 가능

          • 1장에서 학습했던, DB 커넥션 개수를 카운팅하는 부가기능이 예시
          • 새로운 기능을 넣거나 제거하기 위해 기존 코드는 전혀 수정할 필요가 없음
        3. 효율적인 테스트를 손쉽게 만들기 위해

      • 테스트 코드에 의한 DI

        • DB Connection과 관련된 Configuration을 수정할 때, 테스트 중 DAO가 사용할 DataSource 오브젝트를 바꿔주는 방법
          • @Before(Junit5 -> @BeforeEach) 메소드에서 준비된 테스트용 DataSource 오브젝트를 생성하고 Application Context에서 가져온 Dao 오브젝트의 setDataSource를 통해 DI를 해줄 수 있음
          • 이 경우, @DirtiesContext를 클래스에 붙여줘야함
            • 테스트 메소드에서 Application Context의 구성이나 상태를 변경한다는 것을 의미
            • 이 경우, Application Context의 공유를 허용하지 않음
          • Application Context 구성이나 상태를 테스트 내에서 변경하지 않는 것이 원칙이므로 사용에 주의
            • 의존 관계를 강제로 변경 ~> Application Context는 테스트 중 하나만 만들어지고 모든 테스트에서 공유하기 때문
      • 테스트를 위한 별도의 DI 설정

        • 테스트에서 사용될 DataSource 클래스가 빈으로 정의된 테스트 전용 설정파일을 따로 만들어두는 방법을 이용
          • 번거롭게 수동 DI하는 코드나 @DirtiesContext가 필요 없음
      • 컨테이너 없는 DI 테스트

        • 스프링 컨테이너를 이용해서 IoC방식으로 생성되고 DI 되도록 하는 대신, 테스트 코드에서 직접 오브젝트를 만들고 DI해서 사용해도 됨

        • public class UserDaoTest {
              UserDao dao; // @Autowired가 없음
          
              @BeforeEach
              public void setUp() {
                  // ...
                  dao = new UserDao();
                  DataSource dataSource = new SingleConnectionDataSource(~);
                  dao.setDataSource(dataSource);
              }
          }
        • 코드가 단순해지고 테스트시간 절약

          • Application Context가 만들어지는 번거로움이 없어짐
          • 매번 새로운 테스트 오브젝트를 만들기 때문에, 새로운 UserDao 오브젝트가 만들어지지만, Application Context에 비해 부담이 없음
          • 스프링 API에 의존하지 않고 자신의 관심에만 집중해서 깔끔하게 테스트 가능
      • DI를 이용한 테스트 방법

        • 항상 스프링 컨테이너 없이 테스트할 수 있는 방법을 우선적으로 고려
          • 수행 속도가 가장 빠르고 간결
        • 복잡한 의존관계를 갖고 있는 오브젝트 테스트 ~> 스프링 설정을 이용한 DI 방식
        • 예외적인 의존관계를 강제로 구성한 테스트 ~> 수동 DI 테스트
          • @DirtiesContext를 꼭 붙일 것을 잊지말자

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

    • 학습 테스트

      • 라이브러리 등을 테스트하는 경우
      • 기능을 제대로 이해하고 있는지, 사용 방법을 알고 있는지 검증
    • 학습 테스트의 장점

      • 다양한 조건에 따른 기능을 손쉽게 확인 가능
      • 학습 테스트 코드를 개발 중에 참고할 수 있다.
        • 다양한 기능과 조건에 대한 테스트 코드를 개별적으로 만들고 남겨두면, 샘플 코드로 참고 가능
      • 프레임워크나 제품을 업그레이드할 때 호환성 검증을 도움
      • 테스트 작성에 대한 좋은 훈련이 됨
      • 새로운 기술을 공부하는 과정이 즐거워짐
        • 당장 적용할 일부 기능의 사용법을 익히기 위해서만이 아니라, 새로운 프레임워크나 기술을 전반적으로 공부하는 과정에서도 유용
        • 참고할 수 있는 가장 좋은 소스는 스프링 자신에 대한 테스트코드
    • 학습 테스트 예제

      • @Runwith(SpringJunit4ClassRunner.class)
        @ContextConfiguration
        public class JunitTest {
            @Autowired ApplicationContext context;
        
            static Set<JunitTest> testObjects = new HashSet<>();
            static ApplicationContext contextObject = null;
        
            @Test
            public void test1() {
                assertThat(testObjects, not(hasItem(this)));
                testObjects.add(this);
        
                assertTrue(contextObject == null || contextObject == this.context);
                contextObject = this.context;
            }
            @Test
            public void test2() {
                assertThat(testObjects, not(hasItem(this)));
                testObjects.add(this);
        
                assertThat(contextObject,
                          either(is(nullValue())).or(is(this.contet)));
                contextObject = this.context;
            }
        }
        • is()
          • 타입만 일치하면 어떤 값이든 검증 가능
    • 버그 테스트

      • 코드에 오류가 있을 때, 그 오류를 가장 잘 드러내줄 수 있는 테스트
      • 필요성 및 장점
        • 테스트의 완성도를 높여줌
          • 불충분한 테스트를 보완
          • 비슷한 문제 발생 시, 쉽게 추적 가능
        • 내용을 명확하게 분석해줌
          • 어떤 이유 때문에 발생했는지 효과적으로 분석 가능
          • 또한, 함께 발생하는 사이드이펙트를 함께 발견할 수 있음
        • 기술적인 문제를 해결하는데 도움이 됨
          • 기술적으로 다루기 힘든 버그 발견에 도움

    2.6 정리

    • 침투적 기술과 비침투적 기술
      • 침투적 기술
        • 기술을 적용했을 때, Application 코드에 기술 관련 API가 등장하거나, 특정 인터페이스나 클래스를 사용하도록 강제하는 기술
        • 해당 기술에 종속됨
      • 비침투적 기술
        • Application 로직을 담은 코드에 영향을 주지 않고 적용 가능
        • 순수한 코드 유지
        • 스프링이 대표적인 비침투적 기술의 예시
    • 동등분할
      • 같은 결과를 내는 값의 범위를 구분 ~> 대표 값으로 테스트하는 방법
    • 경계값 분석
      • 에러가 동등분할 범위의 경계에서 주로 많이 발생 ~> 경계 근처에 있는 값을 이용해 테스트
    • 테스트 코드 작성
      • 자동화돼야 하고, 빠르게 실행할 수 있어야한다.
      • 항상 결과가 일관돼야한다.
      • 포괄적으로 작성해야한다. ~> 충분한 검증을 하지 않는 테스트는 없는 것 보다 나쁨
      • 테스트하기 쉬워야하며, 적절한 리팩토링이 필요하다
      • 오류가 발견될 경우에 대한 테스트를 만들어 두면 유용하다.
        • 이를 응용해서 TDD를 진행하면 좋음
    반응형

    댓글

Designed by Tistory.