[토비의 스프링 3.1] 2주차 스터디 - 테스트

리리·2025년 1월 24일
0

토비의 스프링

목록 보기
4/6

스터디 날짜 24.07.15.
스터디 범위 2장. 테스트


2장 테스트

💬 스프링이 개발자에게 제공하는 가장 중요한 가치가 무엇이냐고 질문한다면 나는 주저하지 않고 객체지향과 테스트라고 대답할 것이다.

2.1 UserDaoTest 다시 보기

작은 단위의 테스트 (Unit Test)

  • 테스트는 가능하면 작은 단위로 쪼개서 집중해서 할 수 있어야 한다. 관심사의 분리가 여기에도 적용된다.

    • 사용할 DB의 상태를 테스트가 관장하고 있다면 → 단위 테스트 O
    • DB의 상태가 매번 달라지고, 테스트를 위해 DB를 특정 상태로 만들어줄 수 없다면 → 단위 테스트 X
  • 단위 테스트가 필요한 이유

    • 개발자가 설계하고 만든 코드가 원래 의도한 대로 동작하는지를 개발자 스스로 빨리 확인받기 위함

2.2 UserDaoTest 개선

💬 켄트 벡, “테스트란 개발자가 마음 편하게 잠자리에 들 수 있게 해주는 것”

테스트 메소드 전환

  • 테스트 메소드는 JUnit 프레임워크가 요구하는 조건 두 가지를 따라야 한다.
    1. 메소드가 public으로 선언되어야 한다.

    2. 메소드에 @Test라는 어노테이션을 붙인다.

      (+리턴값이 void형이고 파라미터가 없어야 한다.)


검증 코드 전환

  • 기존의 테스트 코드
    if (!user.getName().equals(user2.getName())) { ... }
  • JUnit의 스태틱 메소드 assertThat을 이용한 테스트 코드
    assertThat(user2.getName(), is(user.getName()));
    • is()는 matcher의 일종으로, equals()로 비교해주는 기능을 가진다.
      첫 번째 파라미터를 매처와 비교해서 일치하면 다음으로 넘어가고, 아니면 테스트가 실패한다.

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

JUnit은 특정한 테스트 메소드의 실행 순서를 보장해주지 않는다.

모든 테스트는 실행 순서에 상관없이 독립적으로 항상 동일한 결과를 낼 수 있도록 해야 한다.

예외조건에 대한 테스트

  • 예외 던지기
    • 테스트 중에 예외가 던져지면 테스트 메소드의 실행은 중단되고 테스트는 실패한다.
    • 예외 발생 여부는 메소드를 실행해서 리턴 값을 비교하는 방법으로 확인할 수 없다.
      즉, assertThat()메소드로는 검증이 불가능하다.
  • JUnit의 예외 테스트 기능을 이용하면 예외조건을 보다 쉽게 검증할 수 있다.
    // 테스트 중에 발생할 것으로 기대하는 예외 클래스를 지정해준다. 
    @Test(expected=EmptyResultDataAccessException.class)

포괄적인 테스트

💬 로드 존슨, “항상 네거티브 테스트를 먼저 만들라”

  • 테스트를 작성할 때 부정적인 케이스를 먼저 만드는 습관을 들이는 게 좋다.
  • 예외 케이스를 확인할 수 있는 테스트를 먼저 만들려고 한다면 이 예외적인 상황을 빠뜨리지 않는 꼼꼼한 개발을 할 수 있게 된다.

기능설계를 위한 테스트

  • 테스트 코드는 마치 잘 작성된 하나의 기능정의서처럼 보인다.
    • 조건: 어떤 조건을 가지고
    • 행위: 무엇을 할 때
    • 결과: 어떤 결과가 나온다

테스트 주도 개발, Test Driven Development

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

  • “실패한 테스트를 성공시키기 위한 목적이 아닌 코드를 만들지 않는 것”이 기본원칙
  • 머릿속에서 진행되는 테스트는 제약이 심하고, 오류가 많고, 나중에 다시 반복하기가 힘들다.
    차라리 머릿속에서 복잡하게 진행하던 작업을 실제 코드로 끄집어 놓으면 그게 바로 TDD가 된다.

→ (정원용 멘토님) 개발 초반에 TDD를 하게 되면 코드를 변경하거나 리팩터링 해야할때 테스트코드가 망가진다는 생각에 코드 수정을 망설이게 될 수 있다…


JUnit이 테스트를 수행하는 방식

  1. 테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 모두 찾는다.
  2. 테스트 클래스의 오브젝트를 하나 만든다.
    • 왜 매번 새로운 오브젝트를 만들까?

      💁 각 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 확실히 보장해주기 위해서 매번 새로운 오브젝트를 만든다.

  3. @Before가 붙은 메소드가 있으면 실행한다.
  4. @Test가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.
  5. @After가 붙은 메소드가 있으면 실행한다.
  6. 나머지 테스트 메소드에 대해 2~5번을 반복한다.
  7. 모든 테스트의 결과를 종합해서 돌려준다.

픽스처

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


2.4 스프링 테스트 적용

@BeforeClass 스태틱 메소드

테스트 클래스 전체에 걸쳐 딱 한번만 실행된다.

여러 테스트가 함께 참조할 애플리케이션 컨텍스트를 오브젝트 레벨에 저장해두면 곤란하므로, 스태틱 필드에 애플리케이션 컨텍스트를 저장해두는 방식을 고민해볼 수 있다.

  • 애플리케이션 컨텍스트를 오브젝트 레벨이 저장하게 되면 생기는 문제들
    • 빈이 많아지면 애플리케이션 컨텍스트 생성에 적지 않은 시간이 소요된다.
    • 애플리케이션 컨텍스트가 만들어질 때 모든 싱글톤 빈 오브젝트를 초기화하는데 이때도 상당한 시간을 필요로 한다.
    • 어떤 빈은 많은 리소스를 필요로하거나 독립적인 스레드를 띄우기도 하는데, 테스트를 마칠 때 애플리케이션 컨텍스트 내의 빈이 할당한 리소스를 release해주지 않으면 다음 테스트에 문제가 생길 수도 있다.

하지만 스프링이 직접 제공하는 애플리케이션 컨텍스트 테스트 지원 기능을 사용하는게 더 편리하다!


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

  • ApplicationContext 타입의 인스턴스 변수를 선언하고 @AutoWired 어노테이션을 붙인다.
  • 클래스 레벨에 @RunWith 어노테이션을 붙여 JUnit 확장기능을 지정한다.
  • 클래스 레벨에 @ContextConfiguration 어노테이션을 붙여 테스트 컨텍스트가 자동으로 만들어줄 애플리케이션 컨텍스트의 위치를 지정한다. → 같은 설정파일을 가진 애플리케이션 컨텍스트를 사용하면, 테스트 클래스 사이에서도 애플리케이션 컨텍스트를 공유할 수 있다. (성능 대폭 향상)

@AutoWired

  • 타입에 의한 자동 와이어링
    • AutoWired가 붙은 변수 타입과 일치하는 컨텍스트 내의 빈을 찾고, 타입이 일치하는 빈이 있으면 인스턴스 변수에 주입해준다. 이때 생성자나 수정자 메소드가 없어도 주입이 가능하다.
    • 단, 같은 타입의 빈이 두 개 이상 있는 경우에는 타입만으로 어떤 빈을 가져올지 결정할 수 없기 때문에 변수 이름과 같은 이름의 빈이 주입된다.
    • 변수 이름으로도 빈을 찾을 수 없다면 예외가 발생한다.

DI와 테스트

❓ 구현 클래스를 절대 바꾸지 않을 경우에도 굳이 인터페이스를 사용하고 DI를 통해 주입해주는 방식을 이용해야 하는가? → 🙆‍♀️

  1. 소프트웨어 개발에서 절대로 바뀌지 않는 것은 없다.
  2. 인터페이스를 두고 DI를 적용하게 해두면 다른 차원의 서비스 기능을 도입할 수 있다.
  3. DI는 테스트가 작은 단위의 대상에 대해 독립적으로 만들어지고 실행되게 하는 데 중요한 역할을 한다.

테스트 코드에 의한 DI

  • 테스트에서 운영용 DB를 사용하는 것은 위험할 수 있다.
  • 테스트 코드에 의한 DI를 이용해서 테스트 중에 DAO가 사용할 DataSource 오브젝트를 바꿔주면 된다.
    @DirtiesContext  -> 테스트 메소드에서 애플리케이션 컨텍스트의 구성이나 상태를 변경
    public class UserDatTest {
    	@Autowired
    	UserDao dao;
    	
    	@Before
    	public void setUp() {
    		DataSource dataSource = new SingleConnectionDataSource(
    			"jdbc:mysql://localhost/testdb", "spring", "book", true);
    			dao.setDataSource(dataSource); // -> 코드에 의한 수동 DI
    	}
    • 장점
      • 설정파일을 수정하지 않고도 테스트 코드를 통해 오브젝트 관계를 재구성할 수 있음
      • 예외상황을 만들고자 일부러 엉뚱한 오브젝트를 넣거나, 테스트용오르 준비된 오브젝트를 사용하게끔 할 수 있음
    • @DirtiesContext
      • 이 어노테이션이 붙은 클래스에는 애플리케이션 컨텍스트 공유를 허용하지 않으므로, 테스트 중에 변경한 컨텍스트가 뒤의 테스트에 영향을 주지 않는다.

      • 이 어노테이션이 붙어있는 클래스나 메서드에서 애플리케이션 컨텍스트를 새로 만들고, 실행이 끝나고 나면 해당 컨텍스트는 폐기하고 다시 새로운 애플리케이션 컨텍스트가 만들어진다.

        → 아무래도 찜찜하므로, 그냥 테스트용 설정 파일을 따로 만드는게 낫다! 번거롭게 수동 DI 하거나 위의 어노테이션을 달아줄 필요가 없어짐 👍


컨테이너 없는 DI 테스트

  • 애플리케이션 컨텍스트를 사용하지 않고 직접 오브젝트를 만들고 수동 DI 해준다.
  • 이 방법은 테스트 수행 속도가 가장 빠르고, 테스트가 간결해지므로 테스트 방법을 선택할 때 컨테이너 없이 테스트할 수 있는 방법을 최우선적으로 고려한다.

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

💬 자신이 만들지 않은 프레임워크나 다른 개발팀에서 만들어서 제공한 라이브러리에 대해 작성하는 테스트를 학습테스트라고 한다. 테스트이지만 기능 검증이 목적이 아니며, 기술이나 기능을 자신이 얼마나 제대로 이해하고 있는지를 검증하는 것이 목적이다.

학습 테스트의 장점

  • 다양한 조건에 따른 기능을 손쉽게 확인해볼 수 있다.
  • 학습 테스트 코드를 개발 중에 참고할 수 있다.
  • 프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와준다.

스프링 배포판의 압축을 풀어보면 프레임워크 소스코드와 함께 테스트 코드를 살펴볼 수 있다 → 좋은 공부자료


테스트 검증 방법

  1. assertThat(param, matcher)
  2. assertTrue(param)
  3. assertThat(param, either(is(nullValue())).or(is(this.context))));

버그 테스트

💬 버그 테스트란 코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트를 말한다. 버그 테스트는 일단 실패하도록 만들고, 이후에 버그 테스트가 성공할 수 있도록 애플리케이션 코드를 수정한다.

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

0개의 댓글

관련 채용 정보