자동화된 빌드와 테스트(간단 CI 개념)
개념 이해
지속적 통합(Continuous Integration, CI)은 소프트웨어를 개발하는 과정에서 코드가 변경될 때마다 자동으로 빌드, 테스트, 품질 검사 등을 수행하는 기법이다. 일반적으로 CI 서버(또는 서비스)를 운영하여 변경 사항이 저장소에 push되었을 때 즉시 빌드와 테스트가 진행된다. 이는 다음과 같은 장점이 있다.
코드 결함 조기 발견: 작은 단위로 자주 테스트하므로 오류나 품질 저하를 빠르게 파악 가능
일관성 있는 품질 유지: 매 빌드마다 동일한 프로세스로 테스트하여 프로젝트 품질을 일정 수준 이상으로 유지
빌드 자동화: 개발자가 직접 로컬에서 빌드하지 않아도, 서버에서 자동으로 빌드 진행
테스트 자동화: 여러 플랫폼이나 여러 구성에서 테스트를 자동으로 수행
ROS2 Humble 환경에서도 CI를 적극 도입하면, 메시지 타입 변경이나 새로운 패키지 추가 시 빌드와 관련된 문제를 빠르게 확인할 수 있다.
CI 파이프라인의 기본 구성 요소
CI 파이프라인은 일반적으로 다음 단계를 거친다.
코드 체크아웃(Checkout):
저장소(GitHub, GitLab 등)에 push된 최신 코드를 CI 서버에서 가져온다.
의존성 설치(Dependency Installation):
빌드와 테스트를 하기 위한 사전 준비 단계이다.
ROS2 Humble의 경우 필요한 패키지, 의존성 등을 apt, pip 등을 통해 설치하거나, rosdep 등을 이용해 의존성을 만족시킨다.
빌드(Build):
ROS2 빌드 도구(colcon 등)를 이용하여 패키지를 컴파일하고 링크한다.
예:
colcon build --symlink-install
테스트(Test):
빌드가 완료되면 단위 테스트, 통합 테스트, 로직 테스트 등을 자동으로 실행한다.
예:
결과 보고(Report):
빌드와 테스트 결과를 CI 콘솔이나 협업 도구(Slack, 이메일 등)로 알려준다.
테스트 실패 시 로그나 리포트를 바로 확인하고 수정할 수 있다.
CI 단계별 작업
ROS2 Humble 프로젝트에서 CI 설정 파일(예: GitHub Actions에서는 *.yml 파일, GitLab CI에서는 .gitlab-ci.yml, Jenkins에서는 Jenkinsfile 등)을 구성할 때 다음을 주의한다.
ROS2 Humble용 Docker 이미지 활용:
CI 환경이 매번 깨끗한 상태에서 실행되도록 Docker 이미지를 사용한다.
이미지 안에 ROS2 Humble과 관련 의존성이 미리 깔려 있으면 빌드 시간을 단축할 수 있다.
colcon + CTest 활용:
CMake 기반인 ROS2 프로젝트는 대부분 CTest로 테스트 스크립트를 구성하므로, colcon 빌드 후에는 colcon test를 실행하여 자동으로 모든 테스트를 진행한다.
전처리 단계:
rosdep이나 apt를 통해 필요한 패키지를 모두 설치해야 빌드와 테스트가 문제없이 진행된다.
예:
병렬 빌드 및 캐시:
CI 시간을 단축하기 위해 colcon build 시 $-j$ 옵션 등을 활용하거나, 빌드 캐시를 도입할 수 있다.
다만, 캐시가 잘못 관리되면 간헐적 빌드 실패가 발생할 수 있으므로 주의해야 한다.
ROS2 Humble에서의 테스트 적용 예시
아래는 GitHub Actions를 사용하여 ROS2 Humble 패키지를 자동 빌드 및 테스트하는 간단한 워크플로 예시다.
위와 같은 간단한 설정만으로도 CI에 대한 기초적인 로직이 완성된다. 실제 프로젝트 규모가 커지면 워크플로에 Lint, 코드 커버리지 측정, 문서 생성 등을 포함할 수 있다.
병렬 빌드와 병렬 테스트
CI 환경에서 빌드와 테스트를 병렬로 수행하면 전체 실행 시간을 단축할 수 있다. 로컬 환경에서처럼 -j 옵션을 적용하거나, colcon을 이용할 때 빌드를 병렬로 처리하도록 설정할 수 있다.
colcon build의 병렬 옵션:
일반적으로 colcon build에
--parallel-workers혹은-j옵션을 적용한다. CPU 코어 개수만큼 지정하면 빌드 시간을 크게 단축할 수 있다.예:
colcon test의 병렬 처리:
기본적으로 colcon test는 CTest가 호출되며,
-j옵션을 통해 동시에 여러 테스트를 수행할 수 있다.단, ROS 노드를 사용하는 테스트는 서로 간섭이 생기지 않도록 주의해야 한다.
다양한 플랫폼에서의 빌드와 테스트
ROS2는 여러 OS(Linux, Windows, macOS)에서 동작할 수 있으므로, CI에서 동시에 여러 플랫폼을 대상으로 빌드와 테스트가 가능하다. 예를 들어 GitHub Actions를 사용하면 다음과 같이 OS 매트릭스를 지정할 수 있다.
이와 같이 매트릭스를 통해 여러 플랫폼을 동시에 테스트하면 특정 플랫폼에서만 발생하는 오류를 사전에 발견할 수 있다.
아티팩트 저장
CI 실행 후, 빌드 결과물(바이너리, 로그, 테스트 리포트 등)을 **아티팩트(Artifacts)**로 저장할 수 있다. GitHub Actions에서는 이를 통해 테스트 로그나 문서 등이 보존된다.
이렇게 하면 CI가 끝난 후에도 테스트 결과를 내려받아 확인할 수 있으므로, 문제 발생 시 디버깅에 유용하다.
캐시(Cache) 활용
의존성 설치나 빌드에 필요한 파일을 캐싱하면 CI 시간을 크게 단축할 수 있다.
apt/yarn/npm 캐시: 의존성 설치 시간 단축
colcon 빌드 캐시: 빌드 중간 결과를 재사용
GitHub Actions의 예시:
단, ROS2 Humble 프로젝트에서 캐시를 잘못 사용하면 잔여 아티팩트가 예상치 못한 에러를 일으킬 수 있으니 주의가 필요하다. 캐시가 제대로 적용되지 않는 상황을 대비해 정기적으로 클린 빌드도 수행하는 전략이 필요하다.
Nightly 빌드
낮에는 CI가 풀 리퀘스트 빌드와 테스트 위주로 수행되고, 밤에는 전체 프로젝트에 대해 종합적인 테스트(예: 정적 분석, 코드 커버리지 측정 등)를 수행하는 Nightly 빌드를 운영하는 경우가 많다. 이를 통해 부하가 많은 테스트를 분산 처리할 수 있으며, 평소에 발견되지 않은 잠재적 문제를 찾는 데 유리하다.
코드 커버리지 측정
CI 과정에서 **코드 커버리지(Code Coverage)**를 측정하면, 테스트가 실제로 어느 정도 범위를 커버하고 있는지 정량적으로 파악할 수 있다. ROS2 프로젝트에서 코드 커버리지 측정은 주로 C++ 코드(또는 Python 코드)에 대한 테스트 적용 범위를 확인하기 위해 아래와 같은 흐름으로 진행된다.
커버리지 수집 도구 설정:
Linux 환경에서는 일반적으로
gcov및lcov를 많이 활용한다.CMake에서 커버리지 옵션을 활성화하기 위해선 빌드 시
$-fprofile-arcs -ftest-coverage$등을 적용해야 한다.
빌드 시 커버리지 플래그 추가:
예를 들어, CMakeLists.txt에 다음과 같은 처리를 추가할 수 있다.
colcon을 이용할 때는, 커맨드라인에서 다음과 같이 정의해주기도 한다:
테스트 실행 후 커버리지 리포트 생성:
테스트를 수행하면, 빌드 디렉터리에
.gcno,.gcda파일이 생성된다.lcov로 이 파일들을 수집하여.info형식으로 변환할 수 있다.
genhtml을 사용하면 HTML 형태의 시각적 리포트를 얻을 수 있다.
CI에서 자동화:
GitHub Actions 예시:
생성된 HTML 리포트를 아티팩트로 업로드해두면, 언제든지 다운로드하여 실제 테스트 커버리지를 확인할 수 있다.
정적 분석(Static Analysis)과 스타일 검사(Lint)
자동화된 빌드와 테스트에 정적 분석과 코드 스타일 검사도 포함하면, 코드 품질을 한층 더 높일 수 있다.
ament_lint: ROS2에서 권장되는 C++ 코드 스타일, Python 코드 스타일, CMake 스타일 등을 검사하는 패키지이다.
cppcheck, clang-tidy: C++ 정적 분석 도구로, 잠재적인 오류나 안전성 문제를 잡아준다.
ament_lint 예시
ROS2 Humble 프로젝트에서 package.xml 파일에 ament_lint를 의존성으로 추가해두고, 빌드 시 자동으로 린트 테스트를 수행할 수 있다.
이때, my_package 내 CMakeLists.txt에서 ament_lint_auto_find_test_dependencies()를 호출하면 해당 패키지에 린트 규칙을 자동으로 적용한다.
트러블슈팅: CI 과정에서 자주 발생하는 문제
자동화된 빌드와 테스트(CI)를 운영하다 보면, 아래와 같은 문제에 자주 봉착하게 된다. 이를 사전에 인지하고 대응 방안을 마련하면 CI 파이프라인을 안정적으로 유지할 수 있다.
의존성 버전 불일치
팀원 로컬 환경과 CI 서버 환경의 패키지 혹은 라이브러리 버전이 달라서 빌드 오류가 발생한다.
대응 방안:
CI 빌드 환경(예: Docker 컨테이너, VM 이미지)을 동일하게 유지한다.
ROS2 Humble 공식 Docker 이미지를 사용하고, 필요 의존성을 자동 설치하도록
rosdep을 신경 써서 구성한다.$rosdep$ 데이터베이스를 정기적으로 업데이트하여, 누락된 의존성으로 인해 빌드가 실패하는 경우를 최소화한다.
빌드 캐시 충돌
빌드 캐시를 잘못 사용하면, 이전 빌드의 산출물이 새 빌드에 영향을 미쳐 예측 불가능한 결과를 초래한다.
대응 방안:
정기적으로(예: 매주 1회) 캐시를 초기화하여 클린 빌드를 강제한다.
유닛 테스트 실패나 예상치 못한 동작이 발생하면, 먼저 캐시를 비운 뒤 다시 빌드하여 문제 발생 원인을 캐시에서 배제한다.
테스트 간 리소스 충돌
여러 테스트가 동시에 실행될 때, 서로 공유 리소스(포트, 파일, 하드웨어 등)에 동시 접근하여 충돌이 일어날 수 있다.
대응 방안:
각 테스트를 독립적으로 설계한다. (예: 임시 파일이나 임시 포트를 사용)
ROS2 노드를 띄워서 통신 테스트를 할 경우에는 서로 다른 네임스페이스(namespace)를 활용한다.
빌드 스크립트 또는 CTest 설정에서 테스트 실행 순서를 제어하거나, 병렬 테스트를 제한($-j 1$ 등)한다.
Ubuntu/Foxglove/Webots 등 다양한 시뮬레이터와의 호환성 이슈
ROS2 환경에서 Gazebo(또는 Ignition), Webots 등 여러 시뮬레이터를 사용하는 경우, CI 환경에서 헤드리스(headless)로 구동 시 시뮬레이터 자체가 정상 작동하지 않는 경우가 발생한다.
대응 방안:
X virtual framebuffer(Xvfb) 등 가상 디스플레이 환경을 설정한다.
실제 GPU가 필요한 경우, GPU가 활성화된 Runner(또는 Docker, VM)를 마련한다.
GUI 렌더링이 필요한 경우, CI에서 VNC 같은 원격 접속 방식을 택하기도 한다.
실행 시간 초과
테스트 케이스가 너무 많거나 시뮬레이션 시간이 길어 CI가 타임아웃에 걸릴 수 있다.
대응 방안:
테스트 케이스를 핵심 기능과 확장 기능으로 분리한다.
중요한 핵심 기능 테스트는 빠르게 수행하고, 나머지는 Nightly 빌드에서 수행
시뮬레이터 테스트를 실제 물리 엔진까지 모두 돌리지 않고, 간단한 Mock 노드나 스텁(stub)을 이용해 단위 테스트만 우선 수행한다.
모듈화된 CI 구성
프로젝트 규모가 확장되면 하나의 CI 파이프라인에 모든 단계를 넣는 대신, 다중 파이프라인 혹은 모듈화된 워크플로가 필요해진다.
빌드 파이프라인: 의존성 설치, 컴파일, 린트, 단위 테스트
통합 테스트 파이프라인: 여러 ROS 패키지를 통합하여 기능 검증(시뮬레이터나 Mock을 통한 자동화 테스트)
릴리스 파이프라인: 배포 전 최종 점검(버전 태깅, 바이너리 아티팩트 생성 등)
예를 들어, GitHub Actions에서는 워크플로 파일을 여러 개로 쪼개서 관리할 수 있다.
각 워크플로 간에는 아티팩트 업로드/다운로드, 혹은 Docker 레지스트리 등을 통해 결과물을 연계한다.
멀티 리포지토리(Multi-repo) 환경
ROS2 프로젝트가 커지면 여러 저장소(repo)로 분할되는 경우가 흔하다. 이때 각각의 저장소마다 CI를 설정하면, 의존 패키지를 포함하지 못해 테스트가 제한적일 수 있다.
서브모듈(Submodule) 활용: Git submodule로 다른 저장소를 가져와 빌드할 수 있다.
rosinstall 파일 활용: 여러 저장소를 한 번에 다운로드할 수 있도록
.rosinstall파일에 명시한다.통합 저장소(마스터 repo) 운영: 모든 ROS2 패키지를 종합 관리하는 마스터 리포지토리를 하나 두고, 여러 서브 리포지토리의 변화를 주기적으로 병합한다.
이와 같이 설계하면 각 저장소마다 독립적으로 CI를 거치면서도, 최종적으로는 통합 빌드와 통합 테스트를 통해 전체적인 호환성을 확인할 수 있다.
Last updated