ROS2 전용 테스트 프레임워크 개요
ROS2 테스트 개념과 필요성
ROS2에서의 테스트는 시스템의 안정성과 신뢰성을 보장하기 위해 필수적으로 수행해야 하는 단계이다. 특히 여러 노드가 상호작용하며 동작하는 분산 구조에서, 각 노드의 독립적인 동작뿐 아니라 상호 간의 메시지 교환이 의도한 대로 이뤄지는지 검사하는 과정은 매우 중요하다. 이를 위해 ROS2는 자체적으로 테스트 프레임워크와 도구들을 제공하며, 다음과 같은 특징을 가진다.
단위(Unit) 테스트부터 통합(Integration) 테스트, 시스템(System) 테스트까지 폭넓게 지원한다.
ament 빌드 시스템과 긴밀히 연동되어 빌드 시 자동으로 테스트가 실행되도록 설정할 수 있다.
C++, Python, 그리고 ROS2의 특정 기능(토픽, 서비스, 액션 등)을 간편하게 테스트하도록 전용 테스트 라이브러리를 제공한다.
ament와 테스트
ROS2 패키지의 빌드 시스템으로 ament_cmake 또는 ament_python 등을 사용하는데, 이때 테스트 코드는 자동으로 테스트 타겟에 포함된다. CMakeLists.txt 또는 setup.py에 미리 테스트 관련 설정을 포함해두면, 사용자는 추가적인 명령어 없이도 아래 명령으로 프로젝트 빌드와 동시에 테스트를 수행할 수 있다.
colcon build --symlink-install --cmake-args -DBUILD_TESTING=ON
colcon test
colcon test-result여기서 colcon test는 BUILD_TESTING=ON인 경우 테스트가 존재하는 모든 패키지의 테스트를 자동으로 수행한다. 이후 colcon test-result를 통해 종합 결과를 확인할 수 있다.
ament_cmake 테스트 구조
C++ 기반 ROS2 패키지에서 주로 사용하는 ament_cmake 빌드 방식을 예로 들면, 테스트를 위해 다음과 같은 구성을 갖는다.
테스트 실행 파일: GTest, GMock 등을 포함해 작성된 C++ 테스트 소스.
테스트 선언: CMakeLists.txt에
ament_add_gtest()매크로로 테스트 실행 파일을 등록.빌드 옵션:
BUILD_TESTINGCMake 옵션을 통해 테스트 빌드 활성화 여부를 결정.
아래는 간단한 예시이다.
위 예시에서 ament_cmake_gtest를 이용해 GTest가 적용된 테스트 타겟을 만들고, my_test라는 이름으로 등록한다. 이후 my_test 타겟에 필요한 헤더 경로 등 추가 설정을 수행한다.
ament_python 테스트 구조
Python 기반 ROS2 패키지에서는 ament_python 빌드 방식을 사용한다. 이 경우 setup.py에 entry_points나 pytest 관련 설정을 추가해 테스트를 등록한다. 예를 들면 다음과 같은 구성이 가능하다.
그리고 패키지 디렉터리에 tests 폴더를 두고, 그 내부에 test_something.py 파일을 둔다면, colcon 빌드 과정에서 자동으로 pytest 기반 테스트가 수행된다.
launch_testing과 통합 테스트
ROS2에서 노드와 노드 사이의 상호작용을 실제 구동 환경과 유사하게 테스트하고 싶다면, 단순히 GTest나 PyTest만으로는 부족하다. 이럴 때 사용하는 것이 ROS2에서 제공하는 통합 테스트 도구인 launch_testing이다. 이 도구는 다음과 같은 특징을 갖는다.
ROS2 launch 파일과 함께 동작하여, 테스트 대상 노드들을 실제 실행 프로세스로 띄운 뒤 테스트 스크립트가 이를 관찰하고 검증한다.
Python 기반으로 작성되어, 테스트 흐름을 다양한 방식으로 구성 가능하다.
ROS2 통신 기능(토픽, 서비스, 액션 등)의 실질적인 동작을 확인할 수 있다. 예컨대, 특정 메시지가 실제로 퍼블리시되는지, 서비스 요청에 올바르게 응답하는지 등을 테스트 코드에서 직접 확인한다.
launch_testing을 이용하는 과정은 크게 다음 단계로 요약할 수 있다.
launch 파일 준비: 테스트를 위해 실행할 노드들을 명시한 ROS2 launch 파일이 필요하다.
테스트 스크립트 작성: Python의 unittest 혹은 pytest 스타일로 작성하며, 실행된 노드들의 동작 상태를 점검한다.
종합 실행: launch_testing이 제공하는 특정 인터페이스를 통해, 테스트 스크립트와 launch 파일을 결합해 테스트를 수행한다.
예를 들어, 다음과 같은 구조를 가정해보자.
예시: test_integration.launch.py
여기서는 ROS2 노드인
talker와listener를 동시에 실행하도록 구성했다.
예시: test_integration.py
위 코드는 가장 단순화된 형태로, 실제로는 generate_test_description()에서 launch 파일을 함께 불러오는 방식이 권장된다. 실제 예시를 좀 더 살펴보면:
이런 식으로 노드를 실제로 실행한 뒤 테스트 함수에서 메시지 송수신을 테스트할 수 있다. 또한 launch_testing을 실행하려면 다음과 같이 pytest를 활용해 수행한다.
pytest 기반에서는 @pytest.mark.rostest 데커레이터를 사용하여, ROS2의 launch_testing과 결합한 테스트임을 명시한다.
launch_testing_ros
launch_testing_ros는 launch_testing에 ROS2 전용 유틸리티가 추가된 확장판으로, 토픽, 서비스, 파라미터 등을 보다 편리하게 검사할 수 있는 기능을 제공한다. 예컨대, 특정 노드가 퍼블리시하는 토픽의 메시지를 일정 시간 안에 반드시 수신해야 한다거나, 특정 서비스가 호출될 때의 응답을 확인하는 작업 등을 쉽게 설정할 수 있다.
코드 커버리지와 ament_cmake의 연동
테스트의 완성도를 높이기 위해서는 단순한 테스트 실행 결과(통과/실패)뿐 아니라, 코드의 어느 부분까지 테스트가 수행되었는지 정량적으로 확인할 필요가 있다. 이를 위해 ROS2의 빌드 시스템(ament_cmake)을 활용하여 코드 커버리지를 측정할 수 있다. 일반적으로 다음과 같은 툴체인이 사용된다.
gcov: GCC 컴파일러에서 제공하는 커버리지 측정 도구
lcov: gcov의 결과를 수집·정리하여 HTML 리포트 등으로 시각화
CMake 설정:
BUILD_TESTING=ON과 함께 컴파일 플래그(--coverage 등)를 추가 설정
아래는 간단한 CMakeLists.txt 예시이다.
이렇게 설정해둔 뒤 테스트를 수행하면, gcov 파일이 생성되고 이를 lcov로 후처리할 수 있다.
결과적으로 coverage_report 디렉터리에 HTML 형식의 리포트가 생성되며, 이를 통해 테스트가 실제로 어느 소스 코드를 얼마나 커버했는지 확인할 수 있다.
ament_lint 자동화
ROS2에서는 코드 스타일, 포맷, 정적 분석 등을 자동으로 검사할 수 있는 ament_lint 계열의 패키지들을 제공한다. 예컨대 다음과 같은 린트(lint) 패키지가 있다.
ament_lint_auto: 여러 린트 플러그인을 한 번에 적용
ament_cpplint, ament_uncrustify: C++ 코드 스타일 검사
ament_pep257, ament_pep8: Python 코드 스타일 검사
ament_xmllint: XML 포맷 검사
C++ 패키지의 CMakeLists.txt에서 다음과 같이 선언함으로써 자동화가 가능하다.
그러면 colcon 빌드 시에 자동으로 스타일 및 포맷 검사가 이루어지며, 문제 발견 시 테스트가 실패한다. Python 패키지에서도 setup.py 혹은 해당되는 파일에 유사하게 설정할 수 있다. 이를 통해 단위 테스트, 통합 테스트 뿐 아니라 코드 품질 측면에서도 일관된 관리를 할 수 있다.
특수한 테스트 요구사항
ROS2 기반 로봇 애플리케이션에서는 일반적인 유닛 테스트와 통합 테스트 외에도 아래와 같은 요구사항이 자주 등장한다.
실시간 성능 시험: 특정 노드가 주기적으로 퍼블리시하는 토픽의 주기가 매우 짧은 경우, 실제로 지연이 발생하지 않는지 측정.
하드웨어 의존성 시험: 센서나 액추에이터가 연결된 물리 장비와 상호작용하는 노드의 테스트. 이 경우 HIL(Hardware In the Loop) 테스트나 시뮬레이터를 활용하기도 한다.
장시간 스트레스 시험: 로봇 애플리케이션이 오랜 시간 동안 안정적으로 동작하는지 살펴보는 내구성 테스트.
이러한 특수 요구사항에서는 ROS2의 launch_testing, rosbag 등을 조합해서 실제 데이터를 재현하거나 시뮬레이터 환경을 구성해 테스트할 수 있다.
ros2test 유틸리티
ROS2 Foxy 이후 버전부터는 편의성을 높이기 위해 ros2test 패키지가 제공되기 시작했다. 이는 ROS2에서 테스트를 좀 더 일관성 있게 수행할 수 있도록 돕는 도구로, 크게 다음과 같은 기능을 갖는다.
테스트 자동 디스커버리: ros2test 명령어가 패키지 내의 테스트를 자동으로 찾고 실행
별도의 프로세스 관리: 여러 노드를 띄우고 종료하는 과정을 편리하게 제어
로그/출력 관리: 각 노드 및 테스트의 로그를 체계적으로 수집
아직 다른 툴보다 사용 사례가 많진 않지만, 버전이 올라가면서 점차 안정화되고 있는 추세다.
고급 테스트 기법과 모범 사례
Mocking과 의존성 분리
ROS2 노드는 보통 외부 시스템(예: 센서, 액추에이터, 클라우드 서비스 등)에 의존성을 가지는 경우가 많다. 이때 외부 시스템이 실제로 연결되어 있지 않더라도 테스트가 가능하도록, Mock 객체나 **테스트 더블(Test Double)**을 활용하여 의존성을 분리하는 기법이 중요하다. C++에서는 Google Mock(GMock) 라이브러리를 사용하는 사례가 많고, Python에서는 unittest.mock을 주로 사용한다.
예를 들어, 특정 센서 메시지를 구독하는 노드가 있다고 할 때, 실제 센서 없이도 테스트가 가능하도록 가짜 퍼블리셔(Mock Publisher)를 생성해 해당 노드에 메시지를 보내고, 노드의 응답 혹은 내부 상태 변화를 확인한다. 이를 통해 하드웨어 환경이 갖춰지지 않은 CI(Continuous Integration) 환경에서도 테스트를 자동화할 수 있다.
파라미터(Parameters) 테스트
ROS2 노드는 토픽, 서비스 뿐 아니라 **파라미터(Parameters)**를 통해 런타임에 동작 방식을 바꿀 수 있다. 예컨대 로봇 주행 속도, 센서 업데이트 주기 등을 파라미터로 정의해 놓을 때, 이를 다양한 값으로 바꿔가며 노드 동작을 검증하는 것은 매우 중요한 작업이다. 이때 다음과 같은 접근을 할 수 있다.
PyTest parametrize: Python 테스트에서
@pytest.mark.parametrize데커레이터를 이용해 파라미터 값들을 간단히 반복 테스트한다.GTest value-parameterized tests: C++ 테스트에서
INSTANTIATE_TEST_SUITE_P매크로 등을 이용해 여러 파라미터 조합으로 테스트를 반복 수행한다.자동화 스크립트: launch_testing을 통해 특정 파라미터 세트를 넣어 노드를 띄우고, 결과를 관찰하는 과정을 스크립트화한다.
이를 통해 단일 코드베이스로 다양한 환경 설정을 시험해볼 수 있고, 버그가 특정 파라미터 조합에서만 발생하는 경우를 조기에 발견하기에도 유용하다.
멀티 노드/멀티 로봇 시나리오
ROS2는 분산 환경을 염두에 두고 설계되었으므로, 복수의 노드가 동시에 통신하는 시나리오가 흔하다. 더 나아가 멀티 로봇 환경에서 각 로봇이 서로 다른 네임스페이스(namespace)로 노드를 구동하거나, 토픽을 구독/퍼블리시하는 복수의 로봇 네트워크를 시뮬레이션하는 등의 복잡한 테스트가 필요할 수 있다. 이를 수행하기 위한 일반적인 방법은 아래와 같다.
멀티플 launch 파일: launch_testing에서 여러 개의 노드를 서로 다른 네임스페이스에 할당하여 동시에 띄운 뒤, 서로의 상태를 모니터링한다.
Mock 로봇 노드: 실제 로봇 대신에 각종 토픽을 유발하는 노드를 가짜로 띄워, 통신 흐름만 검증한다.
네트워크 설정 테스트: ROS2의 DDS(Datadistribution Service) 설정을 바꿔가며, QoS(품질 정책) 테스트 등을 병행한다.
실시간성 검증
실시간성이 중요한 로봇 제어나 산업용 시스템에서는 메시지 송수신 지연, 주기 지연 등이 치명적인 이슈가 될 수 있다. 따라서 단순히 메시지가 올바른 내용을 전달하는지뿐 아니라, 얼마나 빠르고 안정적으로 전달되는지도 측정해야 한다.
Timestamp 기반 측정: talker와 listener 노드에서 송신 시각, 수신 시각을 각각 기록하고, 두 시각의 차이로 지연 시간(latency)을 구한다.
Cycle time 측정: 주기적으로 토픽을 퍼블리시하는 노드가 실제로 설정한 주기를 만족하는지(예: 100 Hz, 1 kHz 등) 연속 샘플링하여 통계 분석.
부하(Load) 테스트: CPU, 네트워크 사용량이 증가했을 때, 여전히 실시간성을 지킬 수 있는지 확인한다.
시뮬레이션 도구 연동
Gazebo, Ignition, Webots 등의 시뮬레이션 환경과 연동하여 테스트를 자동화하면, 물리적인 로봇 없이도 복잡한 동작 시나리오를 시험할 수 있다. 이 경우 ROS2 launch 파일에서 시뮬레이터를 자동으로 구동한 뒤, 시뮬레이터 내에서 가상의 센서, 액추에이터를 통해 노드를 테스트할 수 있다. 이를테면 다음과 같은 구조가 가능하다.
CI/CD 환경과 통합
GitHub Actions, GitLab CI, Jenkins 등 CI/CD 도구에서 ROS2 테스트를 통합 실행하도록 구성하면, Pull Request(또는 Merge Request) 시 자동 빌드 및 테스트가 수행되어 코드 품질과 기능 유효성을 수시로 점검할 수 있다. 예시 GitHub Actions 워크플로우 YAML은 아래와 비슷한 형태가 될 수 있다.
이렇게 자동화하면 팀원 모두가 코드를 변경할 때마다 실시간으로 테스트 결과를 확인할 수 있으므로, 신속한 피드백과 안정적인 품질 관리가 가능해진다.
잠재적 문제와 디버깅 팁
테스트 환경을 구성하다 보면 다음과 같은 문제를 자주 겪는다.
DDS 설정 불일치: 로컬 네트워크 설정(Domain ID, QoS, Discovery 설정 등)이 달라서 노드가 서로 인식되지 않는 경우
타이밍 이슈: 노드가 초기화되는 시간을 충분히 주지 않은 채 메시지를 보냈을 때, 간헐적으로 테스트가 실패하는 경우
ROS_DOMAIN_ID 충돌: 동시에 여러 테스트를 실행하면서 같은 Domain ID를 사용해 노드들이 뒤섞이는 경우
이러한 문제를 해결하기 위해서는 아래와 같은 팁을 활용할 수 있다.
launch_testing의 post_shutdown_test: 노드가 종료된 뒤에도 일부 확인 로직을 수행하는 기능을 사용해, 종료 시점 문제를 디버깅할 수 있다.
로깅 수준 조정: rclcpp의 RCLCPP_DEBUG, RCLCPP_INFO 등을 적절히 조정하여 노드 내부 상태를 상세히 기록한다.
계층적 테스트: 유닛 테스트로 먼저 내부 로직을 검증한 뒤, 통합 테스트로 메시지 흐름을 검증함으로써 문제 지점을 좁히기 용이해진다.
Last updated