Post

토비의 스프링 2장

2. 테스트

2.1 UserDaoTest 다시보기

  • 2.1.1 테스트의 유용성
    • 테스트란 예상하고 의도했던 대로 코드가 정확히 동작하는지를 확인해서, 만든 코드를 확신할 수 있게 해주는 작업
  • 2.1.2 UserDaoTest의 특징
    • main()메소드를 이용해 쉽게 테스트를 수행
    • 테스트할 대상인 UserDao를 직접 호출해서 사용
    • 웹을 통한 DAO 테스트 방법의 문제점
      • DAO뿐만 아니라 서비스 클래스, 컨트롤러, JSP 뷰 등 모든 레이어의 기능을 다 만들고 나서야 테스트가 가능하다는 점
      • 테스트를 하는 중에 에러가 나거나 테스트가 실패했다면, 과연 어디에서 문제가 발생했는지를 찾아내야 하는 문제
    • 작은 단위의 테스트
      • 테스트는 가능하면 작은 단위로 쪼개서 집중(관심사의 분리)
      • 단위 테스트(unit test): 작은 단위의 코드에 대해 테스트를 수행한 것
      • 통제할 수 없는 외부의 리소스에 의존하는 테스트는 단위 테스트가 아니라고 보기도 한다.
      • 단위 테스트를 하는 이유는 개발자가 설계하고 만든 코드가 원래 의도한 대로 동작하는지를 개발자 스스로 빨리 확인받기 위해서다.
    • 자동수행 테스트 코드
      • 테스트는 자동으로 수행되도록 코드로 만들어지는 것이 중요
    • 지속적인 개선과 점진적인 개발을 위한 테스트
  • 2.1.3 UserDaoTest의 문제점
    • 수동 확인 작업의 번거로움
      • 콘솔로 확인하기 때문에 성공적으로 되고 있는지를 확인하는 건 사람의 책임이다.
    • 실행 작업의 번거로움
      • 여러 개라면 실행 하는 것 자체가 번거롭다.

2.2 UserDaoTest 개선

  • 2.2.1 테스트 검증의 자동화
    • 비교를 통해 테스트 성공 혹은 실패를 출력하도록 개선하여 자동화
  • 2.2.2 테스트의 효율적인 수행과 결과 관리
    • main() 메소드로는 한계가 있기 때문에 JUnit 사용
    • JUnit 테스트로 전환
      • JUnit은 프레임워크이기 때문에 main()메소드가 필요 없음
    • 테스트 메소드 전환
      • 조건1: public void 파라미터가 없는 메소드
      • 조건2: 메소드에 @Test 어노테이션 붙임
    • 검증 코드 전환
      • assertThat(): 첫번째 파라미터의 값을 뒤에 나오는 매처(matcher)라고 불리는 조건으로 비교해서 일차하면 다음으로 넘어가고, 아니면 테스트가 실패하도록 만들어 줌
    • JUnit 테스트 실행

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

  • 2.3.1 JUnit 테스트 실행 방법
    • IDE
    • 빌드 툴
  • 2.3.2 테스트 결과의 일관성
    • UserDaoTest는 이전 테스트 때문에 DB에 등록된 중복 데이터가 있을 수 있다는 문제가 있다.
    • deleteAll()의 getCount() 추가
    • deleteAll()의 getCount()의 테스트
      • deleteAll()과 getCount()를 기존의 테스트에 추가
      • deleteAll()의 결과를 믿을 수 없기 때문에 getCount()로 0인지 확인한다.
      • getCount()도 믿을 수 없기 때문에 add를 한 뒤 1인지 확인한다.
    • 동일한 결과를 보장하는 테스트
      • 단위 테스트는 항상 일관성 있는 결과가 보장돼야 한다. DB에 남아있는 데이터와 같은 외부 환경에 영향을 받지 말아야 하고, 테스트를 실행하는 순서를 바꿔도 도일한 결과가 보장되도록 만들어야 한다.
  • 2.3.3 포괄적인 테스트
    • getCount() 테스트
      • 좀 더 꼼꼼한 테스트가 필요하기 때문에 하나씩 추가하면서 getCount() 확인
    • addAndGet() 테스트 보완
      • get() 이 파라미터로 주어인 id에 해당하는 사용자를 가져온 것인지, 그냥 아무거나 가져온 것인지 검증하기 못했기 때문에 보완 필요
      • user 2개를 테스트하여 검증
    • get() 예외조건에 대한 테스트
      • get() 메소드에 전달된 id 값에 해당하는 사용자가 없다면?
      • @Test(expected=EmpryResultDataAccessException.class) 처럼 expected를 통해 예외 테스트를 구현
    • 테스트를 성공시키기 위한 코드의 수정
    • 포괄적인 테스트
      • 개발자는 잘 돌아가는 케이스를 상상하면서 코드를 만들고, 테스트를 작성할 때도 문제가 될 만한 상황이나, 입력 값 등은 교묘히 피하는 습성이 있다.
      • 그래서 부정적인 케이스를 먼저 만드는 습관을 들이는 것이 좋다.
  • 2.3.4 테스트가 이끄는 개발
    • 테스트 주도 개발(Test Driven Development)
      • 만들고자 하는 기능의 내용을 담고 있으면서 만들엊딘 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법을 의미
      • TDD는 테스트를 먼저 만들고 그 테스트가 성공하도록 하는 코드만 만드는 식으로 진행하기 때문에 테스트를 빼먹지 않고 꼼꼼하게 만들 수 있다.
      • TDD에서 테스트 작성하고 이를 성공시키는 코드를 만드는 작업의 주기를 가능한 짧게 가져가도록 권장한다.
      • TDD를 하면 자연스럽게 단위 테스트를 만들 수 있다.
  • 2.3.5 테스트 코드 개선
    • @Before
      • @Test가 실행되기 전에 먼저 실행돼야 하는 메소드를 정의한다.
      • setUp() 메소드에서 만드는 오브젝트를 테스트 메소드에서 사용할 수 있도록 로컬 변수가 아닌 인스턴스 변수로 선언한다.
      • JUnit 테스트 수행 방식
        1. @Test가 붙은 public void 파라미터 없는 테스트 메소드를 모두 찾음
        2. 테스트 클래스의 오브젝트 하나 생성
        3. @Before 메소드 실행
        4. @Test 호출 후 결과 저장
        5. @Afte 메소드 실행
        6. 나머지 테스트 메소드에서 2~5 반복
        7. 테스트의 결과를 종합하여 돌려줌
      • JUnit 개발자는 각 테스트가 서로 독립적으로 실행됨을 확실히 보장해주기 위해 매번 새로운 오브젝트를 만들도록 했다.
    • 픽스처
      • 픽스처(fixture): 테스트를 수행하는 데 필요한 정보나 오브젝트
      • 픽스처는 여러 테스트에서 반복적으로 사용하기 때문에 @Before 메소드를 이용해 생성해두면 편리하다.

2.4 스프링 테스트 적용

JUnit이 매번 테스트 클래스의 오브젝트를 만드는 문제가 있다.

  • 2.4.1 테스트를 위한 애플리케이션 컨텍스트 관리
    • 스프링 테스트 컨텍스트 프레임워크 적용
      • @RunWith(SpringJunit4ClassRunner.class) : 스프링의 테스트 컨텍스트 프레임워크의 JUnit 확장기능 지정
      • @ContextConfiguration(locations=""): 테스트 컨텍스트가 자동으로 만들어줄 애플리케이션 컨텍스트의 위치 지정
      • @Autowired : 테스트 오브젝트가 만들어지고 나면 스프링 테스트 컨텍스트에 의해 자동으로 값이 주입
    • 테스트 메소드의 컨텍스트 공유
      • 스프링이 애플리케이션 컨텍스트 테스트 개수에 상관없이 한 번만 만들어서 공유하게 해줬기 때문에 테스트 수행 속도가 빨라짐
    • 테스트 클래스의 컨텍스트 공유
    • @Autowired
      • @Autowired가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다.
      • 단, @Autowired는 같은 타입의 빈이 두 개 이상 있는 경우에는 타입만으로 어떤 빈을 가져올지 결정할 수 없다.
  • 2.4.2 DI와 테스트
    • 인터페이스를 두고 DI를 적용해야 하는 이유
      1. 소프트웨어 개발에서 절대로 바뀌지 않는 것은 없기 때문에
      2. 클래스의 구현 방식은 바뀌지 않는다고 하더라도 인터페이스를 두고 DI를 적용하게 해두면 다른 차원의 서비스 기능을 도입할 수 있기 때문에
      3. 테스트 때문에
    • 테스트 코드에 의한 DI
      • @Before 메소드에서 테스트용 DataSource 오브젝트를 생성하고, set을 통해 DI한다.
      • 하지만 위의 방식으로 하면 애플리케이션 컨텍스트에서 가져온 UserDao 빈의 의존관계를 강제로 변경하기 때문에 바람직하지 못하다.
      • @DirtiesContext: 테스트 메소드에서 애플리케이션 컨텍스트의 구성이나 상태를 변경한다는 것을 테스트 컨텍스트 프레임워크에 알려준다. 그래서 애플리케이션 컨텍스트 공유를 허용하지 않는다.
    • 테스트를 위한 별도의 DI 설정
      • 테스트에서 사용될 DataSource 클래스가 빈으로 정의된 테스트 전용 설정파일을 따로 만든다.
      • 그 후 @ContextConfiguration을 변경한다.
    • 컨테이너 없는 DI 테스트
    • DI를 이용한 테스트 방법 선택
      • 항상 스프링 컨테이너 없이 테스트할 수 있는 방법을 가장 우선적으로 고려한다. (이 방법이 속도가 가장 빠르고 간결하기 때문에)
      • 여러 오브젝트와 복잡한 의존관계를 갖고 있는 오브젝트를 테스트해야 할 경우 DI 방식의 테스트 이용
      • 예외적인 의존관계를 강제로 구성해야 하는 경우 컨텍스트 DI 테스트

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

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

  • 2.5.1 학습 테스트의 장점
    • 다양한 조건에 따른 기능을 손쉽게 확인해볼 수 있다.
    • 학습 테스트 코드를 개발 중에 참고할 수 있다.
    • 프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와준다.
    • 테스트 작성에 대한 좋은 훈련이 된다.
    • 새로운 기술을 공부하는 과정이 즐거워진다.
  • 2.5.2 학습 테스트 예제
  • 2.5.3 버그 테스트
    • 버그 테스트: 코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트 버그 테스트의 필요성과 장점
      1. 테스트의 완성도를 높여준다.
      2. 버그의 내용을 명확하게 분석하게 해준다.
      3. 기술적인 문제를 해결하는 데 도움이 된다.
    • 동등분할: 같은 결과를 내는 값의 범위를 구분해서 각 대표 값으로 테스트를 하는 방법
    • 경계값 분석: 에러는 동등분할 범위의 경계에서 주로 많이 발생한다는 특징을 이용해서 경계의 근처에 있는 값을 이용해 테스트하는 방법
This post is licensed under CC BY 4.0 by the author.