서버 사이드 테스트 자동화 여정 – 1. 테스트 자동화를 시작한 계기와 그 첫 발걸음

안녕하세요. LINE 미디어 플랫폼 개발과 운영 업무를 담당하고 있는 하태호입니다. 미디어 플랫폼은 LINE 메시징 서비스 및 LINE의 다양한 패밀리 서비스에서 생성되고 유통되는 미디어 콘텐츠(이미지, 비디오, 오디오, 라이브 스트림 등)를 각 서비스 요구 사항에 맞추어서 가공하고 저장한 뒤 사용자에게 전달하는 역할을 합니다. 미디어 플랫폼은 LINE 내 수많은 서비스가 사용하는 플랫폼이기 때문에 장애가 발생하면 전체 LINE 서비스 생태계의 품질에 영향을 미칩니다. 따라서 미디어 플랫폼을 구성하는 서버 모듈은 언제나 높은 수준의 서비스 안정성을 보장해야 하는 미션을 가지고 있습니다. 이 미션을 달성하기 위해서 미디어 플랫폼 조직은 코드 리뷰, 테스트 자동화, 기술 스터디, 테크 토크, 장애 회고 등의 다양한 방법을 사용하고 있는데요. 이번 글에서는 미디어 플랫폼 조직이 서비스 안정성을 높이기 위해 어떻게 테스트를 자동화하고 개선해 왔는지 살펴보도록 하겠습니다.

 

테스트 자동화를 시작하게 된 계기

소프트웨어 개발 과정에 있어 테스트는 빼놓을 수 없는 매우 중요한 단계 중 하나로, 시스템이 요구 사항에 맞게 개발되었는지 평가하고 기대한 대로 동작하는지 확인하는 가장 기본적인 활동입니다. 테스트는 경우에 따라 단순히 기능의 동작을 확인하는 수준을 넘어 지속적으로 서비스의 품질을 측정하고 개선하기 위한 데이터로 활용되기도 합니다. 소프트웨어에 대한 의존성이 날로 증가하는 오늘날엔, 수많은 사람들의 일상 속으로 파고든 서비스의 오동작과 장애가 우리의 일상에 미치는 영향이 과거에 비해 상상도 할 수 없을 만큼 커졌습니다. 또한, 새롭고 편리한 기능을 사용자에게 보다 빠르게 전달하기 위해 신규 버전의 소프트웨어를 실제 서비스에 적용하는 배포 주기가 점점 빨라지고 있는데요. 이에 따라 그 어느 때보다 테스트에 대한 관심과 중요성에 대한 인식이 높아지고 있고, 이와 더불어 테스트에 투입되는 비용과 시간도 증가하고 있습니다.

미디어 플랫폼 조직은 이러한 테스트의 중요성을 인식하여 개발 단계에서 단위 테스트를 작성하고, 개발자 테스트를 수행하며, QA(Quality Assurance) 조직과 협업, 지속적으로 정확하고 안전하게 동작하는 서비스를 제공하기 위해 노력해 왔습니다. 하지만 오랜 기간에 걸쳐 정립한, 일관된 기준에 따른 시스템 평가와 정량화된 테스트 지표 수집, 그리고 자동화 측면에서 많이 부족하다는 문제가 있었습니다. 또한 미디어 플랫폼은 LINE 서비스 생태계 내에서 유통되는 다양한 형태의 미디어 파일을 여러 통신 프로토콜을 이용하여 처리하면서 전 세계 사용자를 대상으로 서비스할 수 있도록 시스템을 구성했는데요. 그러다 보니 일반적인 단위 테스트만으로는 시스템이 정상적으로 동작하는지 확인하기 어려운 경우가 많았고, 테스트를 수행할 때 개발자가 많은 시간을 투입해야 하는 문제점이 있었습니다. 또한 미디어 플랫폼의 기술적 특성으로 인해 Spring Framework에서 제공하는 Mock MVC와 같이 잘 만들어진 테스트 도구를 사용하기 어렵다는 점도 이런 문제의 원인 중 하나였습니다. 팀 내 많은 개발자가 이런 문제점을 인지하고 있었지만 다양한 이유(기능 출시 일정, 업무 우선순위, 연계 서비스 긴급 지원, 운영 업무 등)로 개선하지 못하고 있었습니다.

저 역시 언젠간 해결해야 하는 문제라고 생각하던 차에, 어느 날 테스트하는 데에 개발하는 것보다 더 많은 시간을 들이고 있다는 사실을 불현듯 인지하면서 더 이상 미룰 수 없다고 생각했습니다. 그래서 테스트를 자동화하기로 결심하고, 장기적으로 테스트에 투입되는 비용을 줄여서 사용자에게 필요한 기능을 더 빠르고 안전하면서 효율적으로 개발하겠다는 목표를 세웠습니다.

 

지금 당장 시작할 수 있는 테스트 자동화 활동 찾기

 

단위 테스트 실행 자동화 및 결과 확인 방법 개선하기

테스트 자동화를 통해 장기적인 개선을 이루겠다는 큰 목표를 세웠지만, 관련 경험도 없고 원래 하던 업무와 병행으로 진행해야 했기 때문에 무엇부터 해야 할지 감이 오지 않았습니다. 하지만 ‘천 리 길도 한 걸음부터’라는 속담이 주는 교훈을 생각하며 지금 당장 시작해 볼 수 있는 테스트 자동화 활동에는 어떤 것이 있는지 살펴보았습니다.

먼저 미디어 플랫폼을 구성하는 모듈의 테스트 활동 현황을 살펴보았는데요. 다행히(?)도 중요한 역할을 담당하고 있는 서버 모듈에 수백여 개의 테스트 케이스가 작성되어 있다는 것을 알게 되었습니다. 실패하거나 주석 처리된 채 잘 관리되고 있지 않은 테스트가 일부 포함되어 있기는 했지만, 이를 첫 테스트 자동화 작업에 활용해 보면 좋겠다는 생각이 들었습니다. 그래서 첫 테스트 자동화 작업으로 새로운 버전의 소프트웨어를 배포하기 전, 빌드 단계에서 단위 테스트를 수행한 뒤 1개 이상 실패할 경우 배포가 더 이상 진행되지 않도록 강제하여 안정성을 높이고자 했습니다.

과연 첫 테스트 자동화 결과는 어땠을까요?

실패한 테스트 케이스가 존재하면 배포할 수 없도록 강제하는 정책은 빌드 파일의 기능적 안정성은 어느 정도 보장할 수 있었습니다. 하지만 개발이나 테스트 환경에 배포하는 중에 실패한 테스트 케이스가 나타나면 배포가 중단되면서 업무 진행 속도를 심각하게 저하시키는 문제가 발생했습니다. 다만 그렇더라도 잘못된 코드가 실제 서비스에 적용되는 것보다는 낫다는 생각에 며칠 더 정책을 유지했는데요. 배포가 중단된 사례 중에 테스트 케이스 자체가 잘못 작성된 게 원인으로 밝혀진 경우도 있었습니다. 이는 실제 서비스에 적용되었을 때는 문제가 발생할 상황이 아니었는데도 배포가 중단되어 조직 내 개발자들이 시간을 낭비하게 되었다는 의미였기 때문에 더는 정책을 유지하기가 어려웠는데요. 여전히 한편으론 잘못 개발된 코드가 실제 서비스에 적용되는 사례가 발생하는 것을 원치 않았기에 ‘실패한 테스트 케이스가 존재하면 배포를 중단해야 한다’라는 정책을 이대로 포기하고 싶지 않았습니다.

이 문제를 해결하기 위해서 개발자들이 가장 많은 관심을 갖고 업무 시간을 투입하는 ‘코드 리뷰 단계’에서 배포 가능 여부가 확인될 수 있다면 좋겠다고 생각했는데요. Jenkins의 ‘GitHub pull request builder’라는 플러그인을 통해 이 생각을 실현할 수 있었습니다. 이 플러그인을 이용하여 아래 그림과 같이 테스트 실행 결과를 코드 리뷰 단계에서 확인, 배포 가능 여부를 미리 알 수 있도록 했습니다. 

테스트가 성공( 🙂 )한 경우
테스트가 실패한 경우

개선 결과 배포 작업 중에 배포가 중단되는 문제가 발생하지 않게 하면서도 ‘실패하는 테스트 케이스가 존재하면 배포를 중단해야 한다’라는 정책을 포기하지 않을 수 있었고, 추가적으로 아래와 같은 이점도 얻었습니다.

  1. 작업자가 코드 변경 사항에 대한 피드백을 이전보다 빠르게 받을 수 있게 되었다.
  2. 코드 리뷰에 참여하는 동료 개발자들이 코드 리뷰를 시작하기 전에 해당 변경 사항의 동작 가능 여부를 확인할 수 있게 되었다.
  3. 테스트 실행 결과가 PR(Pull Request) 페이지에 나타나면서 개발자들이 자연스럽게 테스트에 더 많은 관심을 갖게 되었다.

 

설정 테스트 추가 및 자동화하기

잘 관리되지 않았던 단위 테스트를 복구하고 PR이 생성될 때마다 단위 테스트가 실행되도록 구성하니 좀 더 안정적인 프로세스로 개발을 진행할 수 있었습니다. 하지만 테스트가 모두 성공했는데도 배포를 수행했을 때 서버 모듈이 제대로 기동되지 않는 문제가 발생했습니다. 왜 실패하는지 분석해 보니 모듈을 구성하는 설정을 정상적으로 로딩하지 못하는 게 원인인 경우가 대부분이었는데요. 저는 이게 심각한 문제라고 판단했습니다. 만약 설정을 활용한 개발을 진행해 본 개발자라면 ‘에이, 설정 오류로 서버 기동에 실패할 수도 있지, 그게 매우 심각하다고 느끼는 건 너무 민감하게 반응하는 것 아니야?’라고 생각할지도 모르겠습니다. 하지만 미디어 플랫폼 조직에선 설정 오류가 매우 심각한 문제에 해당합니다. 미디어 플랫폼 조직의 서버 모듈 대부분은 LINE에서 서비스하는 수많은 서비스(80개 이상)의 다양한 요구 사항을 지원할 수 있도록 개발하고 운영하는데요. 추후 확장을 고려해 대부분의 기능을 설정으로 제어할 수 있도록 개발하기 때문입니다.

설정 파일은 JSON, YAML, Groovy 등 각각 필요에 맞는 형태로 사용하며, 각 방식에 따라 다르기는 하지만 수십만 줄 규모가 되기도 합니다. 또한 규모뿐 아니라 플랫폼 사용자가 웹 콘솔을 통해 직접 설정을 변경하고 실시간으로 서비스에 반영할 수 있도록 구성했기 때문에 생각보다 구조도 복잡합니다. 최종 동작을 결정짓는 하나의 설정 객체를 조립하기 위해서는, 웹 콘솔 서비스를 제공하는 설정 관리(Configuration Management) 서버에서 주입한 설정값과 빌드 결과물에 함께 패키징되어 배포될 파일을 조합해야 합니다. 이런 기술적 복잡성이 서비스 안정성에 영향을 주지 않기 위해 설정 구성에 조금이라도 잘못이 있다고 판단되면 서버 모듈의 기동 자체가 실패하도록 개발해 두었는데요. 그 때문에 서버를 기동하는 중에 문제가 탐지되어 서버 기동에 실패하는 경우가 많이 발생했습니다. 비록 이 문제가 서비스 안정성엔 영향을 끼치지 않는다고 해도, 설정 오류가 포함된 변경 사항이 수많은 개발자들이 내려받는 메인 개발 브랜치에 머지된다면, 개발자의 개발 환경(PC)에서 서버 모듈을 기동해 볼 수 없는 상황이 발생하여 팀 전체 생산성을 저하시키는 문제가 발생할 수 있었습니다. 

다행히 이 문제는 설정 객체를 조립하는 단위 테스트를 작성하고 이를 배포 환경별로 빌드하여 테스트를 실행하도록 구성함으로써 쉽게 해결할 수 있었습니다. 다만 실제로 이 구성을 기반으로 업무를 진행해 보니 전체 테스트 코드가 배포 환경별로 매번 실행되어 Jenkins 서버 자원이 부족해지는 문제가 발생했습니다. 동일한 형상의 코드에 대한 동일한 단위 테스트를 배포 환경별로 실행시키는 것은 컴퓨팅 파워만 낭비할 뿐 실질적인 이득이 없었기에 특정 단위 테스트만 수행시킬 방법을 찾아야 했습니다. 다행히 당시 사용하고 있던 빌드 도구인 Maven의 플러그인에서 특정 단위 테스트만 실행시키는 기능을 제공하고 있어서 이를 이용하여 Jenkins 서버 자원을 많이 사용하지 않고도 배포 환경별로 설정 객체가 정상적으로 로딩될 수 있는지 확인할 수 있었습니다.

아래는 특정 단위테스트만 실행시키는 예시 코드입니다.

mvn test -Dtest=ConfigurationLoadableTest#testConfigurationLoadable

저희는 설정 테스트 수행 결과도 중요하다고 판단, 이 역시 코드 리뷰 단계에서 확인할 수 있으면 좋겠다고 생각했습니다. 그래서 첫 테스트 자동화 활동에서 활용한 Jenkins 플러그인 기능을 활용하여 ‘설정 객체 로딩 가능 여부’를 아래 그림과 같이 PR 페이지 하단에 표시했습니다(전 세계 사용자를 대상으로 한 서비스를 구성하다 보니 배포 환경이 좀 많은 편입니다).

 

연계 컴포넌트 상태 확인 테스트 자동화 및 확인 방법 개선하기

PR이 생성될 때마다 단위 테스트가 실행되도록 구성하고 보니 ‘이제 단위 테스트 커버리지만 높여주면 큰 문제가 발생하지 않겠다’라는 생각이 들었습니다. 하지만 막상 플랫폼을 운영하다 보니 작업자의 착각이나 실수 때문에 문제가 발생하는 경우가 많다는 것을 알게 되었습니다. 신규 서비스를 미디어 플랫폼에서 제공하는 기능과 연동할 때, 각 서비스의 요구 사항에 맞게 스토리지 용량, 스토리지 성능, 스토리지 내 파일 저장 규칙, 미디어 콘텐츠 가공 방식 등 다양한 항목에 대한 설정을 구성하게 되는데요. 설정에 포함된 연계 컴포넌트들의 준비 상황을 확인하지 못한 채 배포하여 문제가 발생하는 사례가 있었습니다. 저는 이 문제도 테스트 자동화를 통해 해결할 수 있을 것이라고 생각하며 ‘우리 모듈과 연동된 컴포넌트들이 우리가 기대하는 상태로 구성되어 있는지 점검할 수 있어야 한다’라는 문제로 일반화했습니다.

인프라 구성, 연계 서비스 서버 및 API 구성 준비 현황 등을 일일이 체크하는 것은 상당히 많은 시간이 걸리는 비효율적인 작업입니다. 특히 인프라 구성의 경우 보안을 위해 네트워크 망을 논리적 또는 물리적으로 분리해야 하고, 각국의 법령에 따라 접근 제한을 설정하거나, 접근을 특정 방식으로 강제해야 하는 등의 제약이 있기 때문에 개발자의 개발 업무용 PC 환경에서 이런 구성 현황을 일일이 체크하는 것은 쉬운 일이 아닙니다. 이를 해결하기 위해서 각 제약 사항을 만족하는 환경에서 해당 사항을 확인할 수 있는 테스트 코드와 스크립트를 실행할 수 있도록 구성했습니다. 이 문제는 대형 장애를 초래할 수 있기 때문에 이 결과 또한 코드 리뷰 단계에서 확인할 수 있도록 만들었습니다.

아래 그림은 연동 서비스 서버 접근 가능 여부와 스토리지 구성, 인프라 구성 상황을 자동으로 확인한 모습입니다.

연동 서비스 서버 또는 외부 서버에 접근할 수 없는 경우, 아래 그림과 같이 호스트 정보와 함께 댓글이 달립니다.

스토리지 및 인프라 구성이 서버의 설정값과 일치하지 않아 문제가 발생할 것으로 예상되면, 아래 그림과 같이 접속 정보와 함께 댓글이 달립니다.

 

성능 테스트 자동화하기

다양한 테스트 자동화 작업을 통해서 이전보다 안정적인 개발이 가능해졌다고 생각했지만, 모든 테스트가 통과했는데도 개발과 테스트 환경에 새로운 버전의 서버 모듈을 투입했을 때 모든 요청 처리가 실패하거나 성능이 기대치보다 떨어지는 문제들이 발생했습니다. 많은 부분의 테스트를 자동화했다고 생각했는데 간혹 이런 문제들이 발생하니 고민이 되었습니다. 이 문제는 미디어 플랫폼이 제공하는 기능 중 캠코더나 PC, 스마트폰 카메라에서 입력되는 영상 신호를 실시간으로 수신, 가공하여 다양한 기기에서 재생할 수 있도록 만들어주는 LINE LIVE Streaming 플랫폼 개발 부분에서 발생했습니다.

LINE LIVE 서비스에선 실시간성을 보장하는 것이 매우 중요한데요. 이를 위해선 시스템을 구성하는 서버 모듈이 항상 최대의 성능을 낼 수 있다는 것을 보장해야 하기 때문에 성능 테스트를 자동화하는 것이 시급했습니다. 하지만 일반적인 API 트래픽이 아니라 미디어 스트림(stream) 트래픽을 가상으로 대량 발생시켜야 하기 때문에 개발 업무용 PC 환경에서 테스트를 실행하는 게 어려웠습니다. 또한 개발자가 이를 매번 검증하는 데 들어가는 비용도 높았습니다. 이 문제를 기존의 테스트 자동화 방법을 변형하여 해결할 수 있지 않을까 고민한 결과, 테스트 전용 서버에 테스트 대상이 되는 서버 모듈을 배포하고 테스트용 트래픽만 생성할 수 있다면 어렵지 않게 해결할 수 있을 것 같았습니다.

이런 아이디어를 바탕으로 성능 테스트를 수행하기 위해 간략하게 구조를 설계, 아래 그림과 같이 구현했습니다. PR이 생성되면 성능 테스트 대상이 되는 코드를 빌드하여 배포하고, 성능 테스트를 수행합니다.

코드의 변경이 성능에 어떤 변화를 주는지는 항상 주의 깊게 살펴보아야 하는 부분입니다. 따라서 성능 테스트 실행 결과로 생성되는 테스트 대상 서버의 시스템 지표 역시 코드 리뷰 단계에서 확인할 수 있도록 성능 테스트 결과가 PR의 댓글로 달리도록 구성했습니다.

개선 결과 코드 리뷰를 시작하기도 전에 문제가 있는지 없는지 확인할 수 있어서 많은 개발자들이 시간을 절약하게 되었습니다.

 

피드백을 좀 더 빨리 전달하기

테스트 자동화 활동을 진행하면서 수동 확인과 반복 업무에서 발생하는 실수가 줄어들고 테스트 업무가 정형화되었습니다. 하지만 PR이 생성되거나 업데이트될 때마다 여러 가지 테스트 작업이 수행되면서 그에 대한 피드백을 제공하기까지 긴 시간이 소요되는 문제가 발생했습니다. 테스트 수행 결과가 얼른 제공되지 않아 PR을 머지하지 못하고 그에 따라 개발이나 테스트 환경에 빨리 배포해 보지 못하는 상황이 발생한 것입니다. 문제는 Jenkins 서버였는데요. 각 PR마다 수행하는 테스트 항목이 많아 Jenkins 서버가 병목이 되고 있었습니다. 이를 해결하기 위해선 Jenkins의 컴퓨팅 파워를 선형적으로 증가시킬 수 있는 방법을 찾아야 했습니다. 

Jenkins는 이런 상황에서 활용할 수 있는 노드 추가 기능을 제공합니다. 아래 그림과 같이 Jenkins에 슬레이브(slave) 노드를 추가하면 컴퓨팅 파워를 선형적으로 늘릴 수 있습니다.

 

첫 테스트 자동화 결과

시작할 땐 어디부터 어떻게 시작해야 할지 고민이 되었지만, 당장 실행 가능한 것부터 하나씩 진행하다 보니 어느새 단위 테스트 자동화, 설정 검증 자동화, 연동 컴포넌트 상태 체크 자동화, 인프라 검증 자동화 등이 가능한 환경이 구성되었습니다. 테스트를 자동화하면서 테스트 작업이 정형화되고, 반복 업무와 실수가 많이 줄어들면서 테스트 업무에 할당하는 시간을 많이 줄일 수 있었으며, 무엇보다 이전보다 훨씬 많은 테스트 작업을 수행하면서도 결과 확인까지 10분이 채 걸리지 않아 생산성 향상에 큰 도움이 되었습니다.

하지만 이 정도 테스트 자동화 활동으로는 앞서 말씀드렸던 오랜 기간에 걸쳐 정립한 일관된 기준에 따른 시스템 평가와 정량화된 테스트 지표 수집, 그리고 자동화 측면에서 부족하다는 문제를 해결하기에 충분하지 않다고 생각했습니다. 그래서 어떤 자동화 활동을 더 진행했는지 궁금하지 않으신가요? 이어지는 2편에서는 Docker를 활용하여 통합 테스트(integration test) 수준의 회귀 테스트(regression test) 환경을 구축한 사례를 소개하고자 합니다. 많이 기대해주세요!

Related Post