90DaysOfDevOps (Day 26)

고태규·6일 전
0

DevOps

목록 보기
25/25
post-thumbnail

해당 스터디는 90DaysOfDevOps
https://github.com/MichaelCade/90DaysOfDevOps
를 기반으로 진행한 내용입니다.

Day 26 - Advanced Code Coverage with Jenkins and API Mocking


1. Jenkins 코드 커버리지


Jenkins는 사용자가 직접 자동화 시스템을 구축하는 '확장 가능한 프레임워크'이다.

단순히 설치만으로 완성된 CI/CD 환경이 만들어지는 것이 아니라, 다양한 Plugin과 외부 도구 (코드 스캐너, 저장소 등) 를 연동하고, 필요에 맞게 구성해야 자동화 시스템이 완성된다.

1-1. 과거 젠킨스 코드 커버리지의 한계

과거 Jenkins Pipeline이 도입된 초기에는 코드 커버리지를 설정하고 유지보수하는 것이 매우 복잡했다.

코드 커버리지란?

테스트 코드가 프로덕션 코드를 얼마나 실행했는지를 백분율로 나타내는 지표
즉, 소프트웨어의 테스트를 논할 때 얼마나 테스트가 충분한가를 나타내는 지표

개발자는 파이프라인 스크립트 내에서 직접 클래스 경로와 같은 복잡한 매개변수를 지정하여 플러그인을 실행해야하는 번거로움이 존재하였다.

해당 방식은

  • 높은 복잡도 : 파이프라인 스크립트가 길어지고 가독성이 떨어져 유지보수가 어려움.
  • 병렬 처리의 어려움 : 여러 Node에서 테스트를 병렬로 실행할 경우, 각기 다른 커버리지 보고서를 하나로 통합하는 과정이 매우 번거롭고 불안정하였음.

이러한 문제점과 한계점을 가졌으며, 이를 해결하기 위해 새로운 접근법이 제시되었다.

1-2. Coverage 플러그인

과거 방식의 불편함을 해결하기 위해 2018년 구글 서머 오브 코드(Google Summer of Code) 프로젝트를 통해 새로운 코드 커버리지 API 개발이 시작되었다.

해당 프로젝트의 목표는 자코코(Jacoco), 코베르투라(Cobertura) 등 기존 도구들이 쉽게 재사용할 수 있는 표준화된 API를 제공하는 것이었다.

해당 프로젝트의 성공적인 결과물은 플러그인으로 출시되었고, 지속적인 사용자 경험 개선을 거쳐 2023년 현재의 새로운 Coverage 플러그인으로 발전하게 되었으며, Coverage 플러그인은 기존 코드베이스를 기반으로 하지만 더욱 유연하고 확장된 모델을 제공한다.

새롭게 출시된 Coverage 플러그인은 Maven이나 Gradle 같은 빌드 도구가 생성한 커버리지 데이터를 받아, 이를 시각화하고 분석하는 역할을 담당한다.

  • 간편한 설정 및 데이터 게시 :

    • 파이프라인 Step을 통해 간단한 명령어로 커버리지 데이터를 추출하고 게시할 수 있음.
  • 다양한 포맷 지원 :

    • 자코코, 코베르투라 등 널리 사용되는 포맷을 직접 지원
    • 다른 형식의 보고서도 기존 스크립트나 변환 도구를 통해 표준 포맷으로 변환하여 통합 가능
  • 상세하고 깊이 있는 리포팅 :

    • line 커버리지 (전체 소스 코드 중에서 테스트 코드에 의해 실행된 코드 라인의 비율) 와 branch 커버리지 (코드 내의 모든 분기문이 얼마나 실행되었는지를 측정) 등 기본적인 통계정보 제공

    • 코드의 복잡도 분석하여 리팩토링이 필요한 부분 식별하는 데 도움을 제공

    • 전체 프로젝트부터 개별 패키지, 클래스, 파일 단위까지 드릴 다운 (Drill-Down)하며 커버리지 정보를 계층적으로 탐색 가능

    • 빌드가 진행됨에 따라 코드 커버리지가 어떻게 변화하는지 시작적인 차트로 확인 가능

  • 높은 확장성과 유연성 :

    • 병렬 테스트에서 생성된 여러 개의 보고서를 단일 보고서로 통합 가능
    • 커버리지 데이터를 외부에서 활용할 수 있도록 REST API 제공

1-3. 깃허브를 사용한 코드 커버리지 결과 확인

Jenkins UI는 상세한 분석이 필요할 때 매우 강력한 도구이다.

하지만 오늘날 개발자들은 대부분의 시간을 GitHub와 같은 코드 저장소 플랫폼에서 보낸다.
따라서 최신 개발 워크플로우에서는 코드 커버리지 결과를 깃허브 웹 인터페이스에 직접 표시하는 방식을 선호한다.

개발자는 CI/CD 결과를 확인하기 위해 젠킨스 화면으로 이동할 필요 없이, 풀 리퀘스트(Pull Request) 화면에서 바로 커버리지 변경 사항과 같은 핵심 정보를 확인할 수 있다.

이는 개발자가 무언가 잘못되었을 때만 Jenkins에 접속하여 심층 분석을 진행하고, 평상시에는 깃허브 내에서 모든 작업을 완결할 수 있게 하여 컨텍스트 스위칭을 최소화하고 생산성을 극대화하는 방법이다.


2. 쉬프트 레프트 (Shift Left)


통합 테스트는 여러 기술 스택이나 플랫폼별 코드가 얽힌 복잡한 기능을 검증하고 코드 커버리지를 높이는 데 필수적이다. 하지만 개발자들은 통합 테스트를 기피하는 경향이 있다.

  • 긴 실행 시간과 피드백 루프 :

    • 통합 테스트는 무겁고 실행속도가 느림.

    • 개발자가 코드를 푸시한 후, Jenkins와 같은 CI 서버에서 빌드가 테스트가 끝나기를 기다려야 함.

    • 테스트가 만약 실패하게 되면, 코드를 다시 수정하고 푸시하고 기다려야되는 순환이 반복됨.

  • 개발 생산성 저하 :

    • "코드 Push -> 대기 -> 실패 확인 -> 수정 -> 다시 Push -> 대기" 로 이어지는 긴 피드백 루프는 개발자의 작업 흐름을 끊고, 생산성과 속도를 저해함.
  • 불안정한 환경 :

    • 실제 DB나 외부 API와 같은 여러 외부 요인에 의존하여, 네트워크 문제나 외부 서비스 상태에 따라 테스트가 불안정하게 실패하는 경우가 잦음.

해당 문제의 핵심은 통합 테스트가 개발자의 로컬 환경을 벗어나, 느리고 무거운 원격 CI 환경에서 실행된다는 점에 있다.

이러한 생산성 저하와 불안정한 환경으로 인한 불편함을 해소하기 위해 나온 패러다임이 '쉬프트 레프트 (Shift Left)'이다.

Shift Left란, CI/CD 파이프라인의 후반 단계에 있는 무거운 통합 테스트를 개발 주기의 초기 단계, 즉 개발자의 로컬 환경으로 가져오는 것을 의미한다.

핵심 목표는 유닛 테스트와 같은 개발 경험으로 통합 테스트를 수행하는 것으로, 이를 가능하게 하는 현대적인 자바 툴링으로 테스트컨테이너 (Testcontainers)와이어목 (WireMock)이 존재한다.


3. Shift Left 도구


3-1. 테스트컨테이너 (Testcontainers)

https://testcontainers.com/

테스트컨테이너는 JUnit과 같은 테스트 프레임워크 내에서 프로그래밍 방식으로 Docker 컨테이너를 생성하고 관리할 수 있게 해주는 라이브러리이다.

테스트가 시작될 때마다, DB, 메세지 큐 등 필요한 외부 서비스를 깨끗한 상태의 Container로 실행하고, 테스트가 종료되면 해당 Container를 자동으로 폐기한다.

  • 주요 장점:

    • 환경 격리 및 일관성:
      로컬에서는 잘 동작하지만, Container 환경에서 동작하지 않는 문제를 방지한다.
      (모든 Test는 항상 동일하고 격리된 환경에서 실행)

    • 빠른 실행과 효율적인 자원 관리:
      필요할때만 Container를 즉시 생성 (On-Deman)하고, 캐싱을 통해 속도를 높인다.
      또한, Test 종료 후, 자원을 완벽하게 해제하여 시스템 부담을 최소화한다.

    • 풍부한 생태계와 Modules:
      PostgreSQL, Kafka, Redis 등 대부분의 유명 DB와 솔루션을 위한 사전 구성된 모듈을 제공한다.

    • 다양한 언어 지원:
      Java 이외에도 Go, .NET, Python 등 다양한 언어를 지원하여 특정 기술 스택에 국한되지 않는다.


3-2. 와이어목 (WireMock)

와이어목은 외부 API를 흉내 내는 가짜 (Mock) 서버를 손쉽게 만들어주는 도구이다. 실제 서드파티 API에 의존하지 않고도 API 연동 부분을 안정적으로 테스트할 수 있게 해준다.

미리 정의된 규칙에 따라, 특정 HTTP 요청이 들어오면 약속된 Response를 반환하는 경량 웹 서버 역할을 한다.

  • 주요 장점:

    • 외부 서비스 의존성 제거:
      외부 API 서버가 다운되거나 네트워크에 문제가 생겨도 Test는 안정적으로 실행된다. 이를 통해, 빠르고 일관성 있는 Test 결과를 보장한다.

    • 외부 서비스 의존성 제거:
      실제 환경에서 재현하기 어려운 Edge Case 테스트가 가능하다.
      ex. '서버 500 오류 응답', 'Network latency 시뮬레이션' 같은 장애 주입을 통해 코드의 회복탄력성을 검증할 수 있음.

    • 동적 응답 생성:
      JSON 템플릿팅 기능을 사용하여 요청 내용에 따라 동적으로 응답을 생성할 수 있어, 더욱 현실적인 모킹이 가능하다.

    • 광범위한 생태계:
      Java 이외에도 Go, .NET, Python 등 다양한 언어에서 사용할 수 있는 구현체가 존재한다.


3-3. 테스트컨테이너와 와이어목 결합

해당 두 도구는 함께 사용될 때 엄청난 시너지를 발휘한다.

ex. 테스트컨테이너를 사용해 와이어목 서버 자체를 컨테이너로 실행할 수 있음.
이를 통해 완전히 격리되고 일회용인 API 목 (Mock) 서버를 테스트 코드 내에서 완벽하게 제어할 수 있으며, 이는 'WireMock 공식 도커 이미지를 테스트하는 사례에 사용'된다.


4. 고급 테스트 전략 및 결론


4-1. 테스트 커버리지를 극대화하는 방법

테스트컨테이너 (Testcontainers)와 와이어목 (WireMock)은 단순히 외부 의존성을 격리하는 것을 넘어, 실제 환경에서 발생할 수 있는 복잡하고 어려운 시나리오를 테스트하는 데 매우 효과적으로 사용될 수 있다.

  • 장애 주입(Fault Injection) 및 카오스 엔지니어링: 와이어목을 프록시 (Proxy)처럼 사용하여, 실제 API 제공자가 반환하지 않는 특정 오류 (ex. 503 Service Unavailable) 나 네트워크 지연을 의도적으로 주입할 수 있음.
    이를 통해 시스템의 회복탄력성을 검증하는 카오스 엔지니어링 테스트가 가능해짐.

  • 계약 테스트(Contract Testing): OpenAPI와 같은 API 명세가 있다면, 와이어목을 통해 들어오는 요청과 나가는 응답이 '해당 명세를 정확히 준수하는지 검증하는 계약 테스트'를 자동화할 수 있다.

  • 동작 기록 및 검증: 테스트컨테이너를 활용해 테스트 중 애플리케이션의 동작을 기록하고, 이 기록을 기반으로 추가적인 검증을 수행하거나 이후의 테스트 동작을 가이드하는 데 사용할 수 있음.

4-2. 결론

젠킨스는 여전히 강력한 CI 플랫폼이다. 특히 상세하고 유연한 리포팅 기능은 다른 도구에 비교하여 큰 강점을 가지고 있는 기능이다.

또한, 깃허브 Checks API 연동은 필수적이다. 이는, 개발자의 작업 흐름을 끊지 않고 깃허브 내에서 모든 CI/CD 결과를 확인할 수 있도록 하여 개발 경험을 크게 향상시키는 장점이 있다.

마지막으로, 통합 테스트를 적극적으로 도입하고, Shift Left 하여 API 모킹 도구인 와이어목, 테스트컨테이너를 활용하는 것이 좋다. 해당 방식을 사용하여 개발 생산성을 극적으로 높이는 결과를 가져올 수 있다.


0개의 댓글