Test Code 라이브 세션

SJ.CHO·2024년 10월 28일

테스트코드는 왜 필요할까?

  • SW가 개발자의 의도한대로 정확하게 동작하는지 확인하는것이 제일 중요.

  • 규모가 이미 있는 프로젝트내에서 추가한 기능에 대한 검증만 요구

    • 신규기능 개발시 테스트에 대한 비용을 아낄수 있다.
  • 기존 요구 사항에서 변동을 주는 부분을 빠르게 발견가능하다.

    • 기존기능에 대한 변경의 전파에 대한 안정감을 얻을 수 있다.
  • 주기를 짧게 자주한다면 결함을 조기에 발견이 가능하다.

  • 테스트코드를 작성함으로써 개발속도가 느려지지만 장기적 으로 본다면 굉장히 많은 비용이 아껴진다.

테스트코드의 작성 중요성

시나리오

  • 테스트를 위해선 시나리오(흐름) 이 중점이 되어야한다.
  • 코드를 떠나서 먼저 내가 어떤 상황에 대한 테스트를 하고싶은지 정리가 필요하다.
  • 가능한 효과적이고 꼼꼼한 시나리오를 생각해보자.
  • 테스트코드 또한 관리의 영역에 포함된다. 필요한 시나리오의 경우 자연스럽게 채워지게 됄것.
  • 어차피 SW 는 터진다!

FIRST 원칙

  • F(Fast) : 테스트코드는 빠르게 실행되어야한다. 1시간 씩 걸린다면 굳이 테스트코드로?
    • Ex) @SpringBootTest 사용 등
  • I(Isolated) : 테스트는 독립적이어야 한다.
    • 다른 테스트나 외부시스템을 의존해서는 안된다. (테스트시점에 따라 성공 실패가 갈림)
    • 테스트코드의 중점은 외부시스템의 연결상태가 아닌 내 SW의 정상흐름이다!
    • Repeatable 원칙 또한 같이 위배될 확률이 높다.
  • R(Repeatable) : 테스트코드는 반복해도 항상 동일한 결과여야한다.
    • 변경사항이 없더라도 테스트코드가 실패한다면 (Random, LocalDateTime) 실패한 케이스
  • S(Self-validating) : 테스트 코드는 그 자체로도 검증의 수단이 되어야한다.
    • 개발자가 로그등을 확인해서 검증을 하면 안된다.
    • 테스트 코드를 CI/CD 배포 파이프라인의 구성요소로 추가하기도 하는데, 이런 상황에서 Self-validating 원칙이 깨진다면 충분히 검증이 안되는 상황이 생길 수 있다.
  • T(Timely) : 테스트 코드는 즉시 작성되어야한다.

그 외 사항

  • 테스트코드 방법론이 매우 많지만 일단 테스트코드를 작성해보고 이후 나에게 맞게 찾아가자.

  • DomainEntity -> Service -> Client -> POJO -> Repository -> Controller 순으로 작성을 해보자. Jacoco 같은걸로 테스트 커버리지 확인가능.

  • 재사용성 보단 시나리오 가독성 이 더 중요하다.
    (중복코드가 발생하더라도 가독성을 우선시 하자)

SpringBootTest VS POJO 테스트

  • @SpringBootTest 는 편의성이 좋지만 실행속도가 매우 느리게되기 때문에 비용의 규모와 FAST 원칙을 위배하기 때문에 최대한 지양하는것이 좋다.
    • @WebMvcTest, @DataJpaTest, @Import 등 지원하는것이 많다.
  • E2E 테스트, 인수 테스트는 어플리케이션 전체의 흐름이 필요하기에 @SpringBootTest 가 필요하다.

단위 테스트

  • public 메소드 자체를 독립적으로 테스트하기위한 테스트 코드
  • 객체의 의존성을 배제하고 해당 객체(메소드) 자체만 테스트해야하기 때문에
    Test Double 이 필수적으로 사용된다.
  • 단위테스트 작성 순서는 의존성의 안 쪽 부터 작성하는것이 좋다.
    • Mock 객체를 만들고 Stubbing 한다는 것은 의존성을 가지는 그 객체가 예상한대로 잘 동작한다는 보장이 되어있어야 하기 때문이다.

구성

  • 테스트 코드와 메소드는 1:1 의 관계
  • Given - When - Then
    • Given
      • 테스트를 위해 필요한 준비를 하는단계.
      • Object Mother, Test Data Builder, Test Fixtures(Fixture Monkey), Easy Random
      • 테스트에 필요한 객체를 생성하거나, Mock 객체에 대한 Stubbing 단계.
    • When
      • 테스트하고자하는 메소드 호출단계
      • 일반적으론 1줄의 코드를 가짐. 여러줄 일경우 높은확률로 나눠져야하는 테스트
      • 예외 검증시 Then 과 합쳐지기도함.
    • Then
      • When 절에 호출한 메소드의 반한값, Spy 를 이용한 호출횟수검증, 예외검증 등 테스트에 대한 결과 확인.

우선순위 가이드

  • Domain Entity & POJO
    • 테스트코드 작성 리소스에 비해 가치가 매우 높다.
    • 의존객체가 많지않기에 Test Double 이 필요없는 케이스가 많다.
  • Service
    • 비즈니스로직음 담고있기에 단위테스트의 가치가높다
    • 객체에 대한 의존성이 높기에 Test Double 이 겅의 항상 필요하다 (Mockito)
@Service
class OAuth2LoginService(
    private val oAuth2ClientService: OAuth2ClientService,
    private val socialMemberService: SocialMemberService,
    private val jwtHelper: JwtHelper
) {

    fun login(provider: OAuth2Provider, authorizationCode: String): String {
        return oAuth2ClientService.login(provider, authorizationCode)
            .let { socialMemberService.registerIfAbsent(it) }
            .let { jwtHelper.generateAccessToken(it.id!!) }
    }
}
  • 흐름에 대한 처리만 있을경우 굳이..? 통합테스트를 고려해보자!

  • Client

    • 외부서비스를 호출하는 책임을 가지기에 개체로써의 테스트 가치가 높다.
    • 직접 외부시스템을 의존하게 된다면 I,R 을 위배하기에 Mock Server 이용이필요하다.
    • 간단한 Mock Server 의 경우 MockWebServer 조금 복잡한 동작을 해야할 경우 WireMock
    • 응답이 제대로 Map화 되는지, 예외를 제대로 처리해주는지 등에 중점.
  • Repository

    • JPA 를 사용시 테스트의 우선순위가 낮다. 내까 짠게 아니고 많이 사용되기에 신뢰성이 높다.
    • 직접 Query를 작성한 경우에만 사용.
    • 테스트 시 DB가 결국은 필요하기에 단위테스트보단 통합테스트에서 활용된다.
    • H2 Database(Mode=MySQL) 혹은 MySQL Containe 를 사용.
  • Controller

    • 특별한 로직이 들어가지 않기에 우선순위가 많이 낮다.
    • @MockMvcTest 를 활용해 테스트한다.

통합 테스트

  • 2개이상의 객체를 합쳐서 협력하는 맥락을 테스트 혹은 외부환경을 함께 테스트하고싶을때 고려

  • 시나리오 관점 에서 고민을 하여 테스트를 작성해보자.
  • 되도록이면 @DataJpaTest , @Import 등을 활용하여 최소한의 환경을 만들자.

E2E (End to End Test)

  • 하나의 API를 호출 했을 때 동작하는 APP 전체의 흐름
  • APP이 정말로 실행중인지 가정한 테스트. @SpringBootTest 가 필요한순간.

인수 테스트

  • E2E 테스틑 각각의 API 기준으로 테스트를 했다면 인수테스트는 사용자의 시나리오 기준으로 테스트코드를 작성
    • 회원가입 -> 로그인 -> 게시글 작성 등의 흐름.
  • 기본적인 개념은 요구사항이지만, 최근 많이 사용하는 애자일(Agile) 에서는 요구사항의 단위를 사용자 스토리로 사용하기 때문에 이를 기준으로 표현하기도한다.
  • Ex : 사용자는 카카오 소셜 로그인을 할 수 있다.
    • OAuth 2.0 을 이용한 소셜 로그인은 2번의 흐름으로 전체 프로세스가 구성되어 있다.
    • (1) 로그인 페이지로 리다이렉트하기 위한 API 호출
    • (2) 로그인 성공 후 인증 프로세스를 위한 Callback API 호출
profile
70살까지 개발하고싶은 개발자

0개의 댓글