테스트 코드 작성법

jonghyukLee·2022년 8월 18일
1

오늘은 테스트에 관하여 정리를 해보았습니다.
최근 기술적으로 많은 도움을 받았던 유튜브 채널이 있었는데, 그 분께서 책을 내셨다는 소식을 듣고 읽어보게 되었습니다.
평소 테스트 코드의 중요성은 인지하고 있었지만, 솔직히 거의 작성하지 않고 있었기 때문에, 책의 내용중 테스트를 다룬 부분을 중점적으로 정리하게 되었고,
이번 기회를 통해 확실하게 학습하고, 프로젝트에 적용해볼 생각입니다.

테스트 코드 작성법

테스트 코드를 작성하는 이유.

  • 개발 과정에서 문제를 미리 발견할 수 있다.
  • 리팩토링의 리스크가 줄어든다.
    • 수정사항이 발생했을 때, 다른 로직에 영향을 미치지는 않는지 등의 혹시 모를 부작용에 대비할 수 있다.
  • 애플리케이션을 가동해서 직접 테스트하는 것 보다 테스트를 빠르게 진행할 수 있다.
  • 하나의 명세 문서로서의 기능을 수행한다.
    • 개발은 대부분 협업으로 진행되기 때문에, 타인의 코드를 한 번에 이해하는 것은 쉽지 않다. 각자의 환경에서 작업한 코드를 통합했을 때 발생하는 오류들을 간편하게 상대방의 의도를 파악하고 수정할 수 있다.
  • 몇 가지 프레임워크에 맞춰 테스트 코드를 작성하면 좋은 코드를 생산할 수 있다.
  • 코드가 작성된 목적을 명확하게 표현할 수 있으며, 불필요한 내용이 추가되는 것을 방지한다.

단위 테스트 (Unit Test)

: 애플리케이션의 개별 모듈을 독립적으로 테스트하는 방식

  • 특징
    • 테스트 대상의 범위를 기준으로 가장 작은 단위의 테스트.
    • 메서드 단위로 테스트를 수행하며, 호출을 통해 의도한 결괏값이 나오는지 확인하는 수준
    • 비용이 적기 때문에 빠른 피드백이 가능하다.

통합 테스트 (Integration Test)

: 애플리케이션을 구성하는 다양한 모듈을 결합해 전체적인 로직이 의도한 대로 동작하는지 테스트하는 방식

  • 특징
    • 모듈을 통합하는 과정에서의 호환성 등을 포함해 애플리케이션이 정상적으로 동작하는지 확인하기 위해 수행하는 테스트.
    • 여러 모듈을 함께 테스트해서 정상적인 로직 수행이 가능한지를 확인
    • 단위 테스트는 일반적으로 특정 모듈에 대한 테스트를 진행하기 때문에 데이터 베이스나 네트워크 같은 외부 요인을 제외하고 진행하는데, 통합 테스트는 이를 포함하여 테스트를 진행하므로 애플리케이션이 온전히 동작하는지 여부를 확실하게 파악할 수 있다.
    • 다만 테스트를 수행할 때 마다 모든 컴포넌트가 동작해야 하기 때문에 테스트 비용이 커지는 단점이 있다.

테스트 코드를 작성하는 방법

  • Given-When-Then 패턴
    • Given : 테스트를 수행하기 전에 테스트에 필요한 환경을 설정. 필요한 변수 선언이나 Mock 객체를 통해 특정 상황에 대한 행동을 정의한다.
    • When : 테스트의 목적을 보여주는 단계. 실제 테스트 코드가 포함되며, 테스트를 통한 결괏값을 가져온다.
    • Then : 테스트의 결과를 검증하는 단계. 일반적으로 When 단계에서 나온 결괏값을 검증하는 작업을 수행한다.
    • 이 패턴은 간단한 테스트로 구성되는 단위테스트 보다는 비교적 많은 환경을 포함해서 테스트하는 인수 테스트에서 활용하는 것이 적합하다. 불필요하게 코드가 길어지기 때문. (하지만
  • F.I.R.S.T 전략 : 테스트 코드를 작성하는 데 도움이 될 수 있는 5가지 규칙을 의미함.
    • Fast(빠르게)
      • 테스트는 빠르게 수행되어야 한다. 테스트가 느리면 코드 개선 작업이 느려져, 코드 품질이 떨어질 수 있다.
      • 테스트 속도에 절대적인 기준은 없지만, 목적을 단순하게 설정해서 작성하거나 외부 환경을 사용하지 않는 단위 테스트를 작성하는 것 등을 빠른 테스트라고 정의할 수 있다.
    • Isolated(독립적)
      • 하나의 테스트 코드는 목적으로 여기는 하나의 대상에 대해서만 수행돼야 한다.
      • 다른 테스트코드와 상호작용 하거나 외부 소스를 사용하면 외부 요인으로 인해 테스트가 수행되지 않을 수 있다.
    • Repeatable(반복 가능한)
      • 테스트는 어떤 환경에서도 반복 가능하도록 작성해야 한다.
      • 테스트 개발 환경의 변화나 네트워크 연결 여부와 상관 없이 수행되어야 한다.
    • Self-Validating(자가 검증)
      • 테스트는 그 자체만으로 테스트의 검증이 완료돼야 한다.
      • 테스트가 성공했는지 실패했는지 확인할 수 있는 코드를 함께 작성해야 한다.
    • Timely(적시에)
      • 테스트하려는 애플리케이션 코드를 구현하기 전에 완성돼야 한다.
      • 너무 늦게 작성된 테스트 코드는 정상적인 역할을 수행하기 어려울 수 있다.
      • 또한 테스트코드로 인해 발견된 문제를 해결하기 위해 소모되는 개발 비용도 커지기 쉽다.
      • 다만 이 규칙은 TDD 원칙을 따르는 작성 규칙으로 분류되므로, 제외하기도 한다.

JUnit을 활용한 테스트 코드 작성

JUnit 특징

  • JUnit은 자바 언어에서 사용되는 대표적인 테스트 프레임워크로서 단위 테스트를 위한 도구를 제공한다.
  • 하지만 단위 테스트 뿐만 아니라, 통합 테스트를 할 수 있는 기능도 제공한다.
  • 어노테이션 기반의 테스트 방식을 지원한다.
  • 단정문(assert)을 통해 테스트 케이스의 기댓값이 정상적으로 도출됐는지 검토할 수 있다.

JUnit의 세부 모듈

JUnit 5는 크게 세 모듈로 구성된다.

  • JUnit Platform
    • JVM에서 테스트를 시작하기 위한 뼈대 역할을 한다.
    • 테스트 엔진(TestEngine)의 인터페이스를 가지고 있다.
      • 테스트를 발견하고 수행하며, 그 결과를 보고하는 역할을 수행함.
      • 각종 IDE와의 연동을 보조하는 역할을 수행
      • TestEngine API, Console Launcher, JUnit 4 Based Runner 등이 포함 돼있다.
  • JUnit Jupiter
    • TestEngine API의 구현체를 포함하고 있으며, Jupiter 기반의 테스트를 실행하기 위한 테스트엔진을 가지고 있다.
    • 테스트의 실제 구현체는 별도 모듈의 역할을 수행하는데, 그중 하나가 Jupiter Engine이다.
      • Jupiter API를 활용해서 작성한 테스트 코드를 발견하고 실행하는 역할
  • JUnit Vintage
    • JUnit 3, 4에 대한 TestEngine API를 구현하고 있다.
    • 기존에 작성된 JUnit 3, 4버전의 테스트 코드를 실행할 때 사용된다.
    • Vintage Engine을 포함한다.
  • 이처럼, JUnit은 하나의 Platform 모듈을 기반으로 Jupiter와 Vintage 모듈이 구현체의 역할을 수행한다.

JUnit의 생명주기

  • 테스트 순서에 관여하는 대표적인 어노테이션
    • @Test : 테스트 코드를 포함한 메서드를 정의
    • @BeforeAll : 테스트를 시작하기 전에 호출되는 메서드를 정의
    • @BeforeEach : 각 테스트 메서드가 실행되기 전에 동작하는 메서드를 정의
    • @AfterAll : 테스트를 종료하면서 호출되는 메서드를 정의
    • @AfterEach : 각 테스트 메서드가 종료되면서 호출되는 메서드를 정의

컨트롤러 테스트 작성

일반적으로 Controller는 Service 객체를 DI 받는다.

하지만 테스트 코드를 작성하는 입장에서, Controller를 테스트 하고자 한다면 Service는 외부 요인에 해당한다.

따라서, 독립적인 테스트 코드를 작성하기 위해 Mock 객체를 활용한다.

주로 쓰이는 어노테이션

  • @WebMvcTest(테스트 대상 클래스.class)
    • 웹에서 사용되는 요청과 응답에 대한 테스트를 수행할 수 있다.
    • 대상 클래스만 로드해 테스트를 수행하며, 만약 대상 클래스를 추가하지 않으면 다른 컨트롤러 클래스들이 모두 읽어진다.
  • @MockBean
    • 실제 빈 객체가 아닌 Mock(가짜) 객체를 생성해서 주입하는 역할을 수행한다.
    • 이 어노테이션이 선언된 객체는 실제 객체가 아니기 때문에 실제 행위를 수행하지 않는다.
    • 그렇기 때문에 해당 객체는 개발자가 Mockito의 given() 메서드를 통해 동작을 정의해야 한다.
  • @Test
    • 테스트 코드가 포함돼 있다고 선언하는 어노테이션이며, JUnit Jupiter에서는 이 어노테이션을 감지해서 테스트 계획에 포함시킨다.
  • @Displayname
    • 테스트 메서드의 이름이 복잡해서 가독성이 떨어질 경우 이 어노테이션을 통해 테스트에 대한 표현을 정의할 수 있다.

슬라이스(Slice) 테스트

  • 단위 테스트와 통합 테스트의 중간 개념으로, 레이어드 아키텍쳐를 기준으로 각 레이어별로 나누어 테스트를 진행한다는 의미이다.
  • 단위 테스트를 수행하기 위해서는 모든 외부 요인을 차단하고 테스트를 진행해야 하지만, 컨트롤러와 같은 경우, 개념상 웹과 맞닿은 레이어로서 외부 요인을 차단하고 테스트하면 의미가 없어지기 때문에, 슬라이스 테스트를 진행하는 경우가 많다.
  • 슬라이스 테스트를 위해 사용할 수 있는 대표적인 어노테이션
    • @DataJdbcTest
    • @DataJpaTest
    • @DataMongoTest
    • @DataRedisTest
    • @JdbcTest
    • @JooqTest
    • @JsonTest
    • @RestClientTest
    • @WebFluxTest
    • @WebMvcTest
    • @WebServiceClientTest
profile
머무르지 않기!

0개의 댓글