개요 및 이 글에서 다룰 것
ABAP 단위 테스트에서 외부 의존(DB, RFC, 외부 API)을 그대로 호출하면 테스트가 느려지고 환경에 종속됩니다. 이 글에서는 인터페이스(INTERFACE) 기반으로 의존을 분리하고, Mock Class를 만들어 테스트 대상 클래스에 주입(Dependency Injection)하는 3단계 방식을 다룹니다. ABAP Unit과 함께 사용해 빠르고 격리된 테스트를 작성하는 흐름을 익히는 것이 목적입니다.
- 의존 로직을 INTERFACE로 분리하는 패턴 이해
- 로컬 테스트 클래스에 Mock 구현체 작성
- 생성자 주입(Constructor Injection)으로 Mock 연결
- cl_abap_unit_assert로 결과 검증
- 흔한 실수와 트러블슈팅 사례 점검
이 글을 보기 전에
이 글을 따라가려면 다음 내용에 어느 정도 익숙해야 합니다. ABAP OO 문법(CLASS, METHODS, INTERFACES), 로컬 클래스 정의, 그리고 ABAP Unit 기본 구조(FOR TESTING, RISK LEVEL HARMLESS)를 알면 좋습니다. 추가로 의존성 주입(DI) 개념과 생성자 파라미터를 통한 객체 전달 방식을 알고 있으면 이해가 수월합니다.
환경 / 버전 / 준비물
- 플랫폼: SAP NetWeaver AS ABAP 7.5x 이상 또는 ABAP Platform 2022 이상 권장
- 개발 도구: ABAP Development Tools (ADT) for Eclipse 또는 SE80/SE24
- 테스트 프레임워크: ABAP Unit (cl_abap_unit_assert)
- 문법: 인라인 DATA(...), NEW 연산자 사용 (7.40 SP08+)
- 패키지: 테스트 클래스는 메인 클래스의 로컬 테스트 인클루드(CCAU)에 작성
핵심 개념
Mock Class는 진짜 의존 객체의 흉내입니다. 환율을 외부 RFC에서 가져오는 get_rate() 메서드가 있다면, 단위 테스트마다 RFC를 호출하면 네트워크 장애·응답 지연·환율 변동 때문에 결과가 불안정합니다. RFC 호출 부분을 인터페이스로 추상화한 뒤, 테스트에서는 항상 고정값을 반환하는 Mock을 주입하면 예측 가능한 결과를 만들 수 있습니다.
핵심은 서비스 클래스가 구체 클래스 대신 인터페이스에 의존하도록 설계하는 것입니다. 이를 의존성 역전 원칙(DIP)이라 부르며, ABAP에서는 INTERFACES 문과 생성자 파라미터로 자연스럽게 구현됩니다. 실제 운영 코드에서는 진짜 구현체를 주입하고, 단위 테스트에서는 Mock을 주입하는 방식으로 같은 서비스 코드를 두 맥락에서 재사용합니다.
실전 코드 3단계
1단계: 인터페이스 분리
먼저 환율 조회 의존을 INTERFACE로 추출하고, 이를 사용하는 서비스 클래스를 정의합니다.
INTERFACE lif_rate_provider.
METHODS get_rate
IMPORTING iv_currency TYPE waers
RETURNING VALUE(rv_rate) TYPE p LENGTH 9 DECIMALS 4
RAISING cx_static_check.
ENDINTERFACE.
CLASS lcl_price_service DEFINITION.
PUBLIC SECTION.
METHODS constructor
IMPORTING io_provider TYPE REF TO lif_rate_provider.
METHODS calc_krw
IMPORTING iv_amount TYPE p LENGTH 9 DECIMALS 2
iv_currency TYPE waers
RETURNING VALUE(rv_krw) TYPE p LENGTH 11 DECIMALS 2.
PRIVATE SECTION.
DATA mo_provider TYPE REF TO lif_rate_provider.
ENDCLASS.
CLASS lcl_price_service IMPLEMENTATION.
METHOD constructor.
mo_provider = io_provider.
ENDMETHOD.
METHOD calc_krw.
DATA(lv_rate) = mo_provider->get_rate( iv_currency ).
rv_krw = iv_amount * lv_rate.
ENDMETHOD.
ENDCLASS.
lcl_price_service가 lif_rate_provider 타입에만 의존한다는 점이 핵심입니다. 실제 RFC 클래스든 Mock이든 인터페이스만 구현하면 주입 가능합니다.
2단계: Mock 클래스 구현
로컬 테스트 클래스에 Mock을 작성합니다. 단순 고정 반환을 넘어, 호출 인자 기록과 예외 시뮬레이션까지 다룹니다.
CLASS lcl_mock_rate DEFINITION FOR TESTING.
PUBLIC SECTION.
INTERFACES lif_rate_provider.
DATA: mv_rate_to_return TYPE p LENGTH 9 DECIMALS 4,
mv_raise_error TYPE abap_bool,
mv_last_currency TYPE waers,
mv_call_count TYPE i.
ENDCLASS.
CLASS lcl_mock_rate IMPLEMENTATION.
METHOD lif_rate_provider~get_rate.
mv_call_count = mv_call_count + 1.
mv_last_currency = iv_currency.
IF mv_raise_error = abap_true.
RAISE EXCEPTION TYPE cx_sy_conversion_no_number.
ENDIF.
rv_rate = mv_rate_to_return.
ENDMETHOD.
ENDCLASS.
어트리뷰트는 두 부류입니다. 입력성(mv_rate_to_return, mv_raise_error)은 테스트가 Mock 동작을 제어하고, 출력성(mv_last_currency, mv_call_count)은 Mock이 어떻게 호출됐는지 확인하는 데 씁니다.
3단계: Mock 주입 + 어서션
테스트 클래스에서 Mock을 만들어 서비스에 주입하고 결과를 검증합니다.
CLASS ltcl_price_service DEFINITION FINAL
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA: mo_mock TYPE REF TO lcl_mock_rate,
mo_cut TYPE REF TO lcl_price_service.
METHODS setup.
METHODS calc_krw_returns_amount FOR TESTING.
METHODS calc_krw_passes_currency FOR TESTING.
METHODS error_propagates FOR TESTING RAISING cx_static_check.
ENDCLASS.
CLASS ltcl_price_service IMPLEMENTATION.
METHOD setup.
CREATE OBJECT mo_mock.
mo_cut = NEW lcl_price_service( io_provider = mo_mock ).
ENDMETHOD.
METHOD calc_krw_returns_amount.
mo_mock->mv_rate_to_return = '1300.0000'.
DATA(lv_result) = mo_cut->calc_krw( iv_amount = '10.00' iv_currency = 'USD' ).
cl_abap_unit_assert=>assert_equals(
act = lv_result exp = CONV p( '13000.00' ) msg = 'USD 10 * 1300 = 13000 KRW' ).
ENDMETHOD.
METHOD calc_krw_passes_currency.
mo_mock->mv_rate_to_return = '1.0000'.
mo_cut->calc_krw( iv_amount = '1.00' iv_currency = 'EUR' ).
cl_abap_unit_assert=>assert_equals( act = mo_mock->mv_last_currency exp = 'EUR' ).
cl_abap_unit_assert=>assert_equals( act = mo_mock->mv_call_count exp = 1 ).
ENDMETHOD.
METHOD error_propagates.
mo_mock->mv_raise_error = abap_true.
TRY.
mo_cut->calc_krw( iv_amount = '1.00' iv_currency = 'JPY' ).
cl_abap_unit_assert=>fail( '예외가 발생해야 함' ).
CATCH cx_sy_conversion_no_number.
ENDTRY.
ENDMETHOD.
ENDCLASS.
이 구조의 장점은 다음과 같습니다.
- 속도: DB/RFC 미사용으로 수십 ms 내 종료, CI 파이프라인 부담 적음
- 격리성: 외부 시스템 상태와 무관, 결과 재현 가능
- 가독성: 테스트 메서드명이 시나리오를 그대로 설명
흔한 실수 / 트러블슈팅
FAQ 1. "Method GET_RATE is unknown" 컴파일 오류
Mock 구현부를 METHOD get_rate.로 작성한 경우입니다. 인터페이스 메서드 구현은 반드시 METHOD lif_rate_provider~get_rate. 형태로 인터페이스명을 접두어로 붙여야 합니다.
FAQ 2. 서비스가 여전히 진짜 클래스를 호출함
생성자에서 io_provider를 받지 않고 내부에서 NEW lcl_rfc_rate( )를 직접 생성하면 Mock 주입이 무용지물입니다. 의존 객체 생성은 반드시 외부에서 받도록 강제해야 합니다.
FAQ 3. 로컬 클래스라 재사용이 안 됨
인터페이스를 SE24 글로벌 인터페이스로 승격하면 여러 클래스에서 재사용 가능합니다. 처음에는 로컬에서 시작하고 재사용 요구가 생길 때 리팩터링하는 편이 안전합니다.
FAQ 4. PRIVATE 멤버를 Mock으로 갈아끼우고 싶을 때
테스트 클래스를 CLASS ... DEFINITION LOCAL FRIENDS로 친구 등록하면 PRIVATE 접근이 가능합니다. 단 남용은 캡슐화를 깨뜨리므로 생성자 주입을 우선합니다.
다음 단계 / 관련 주제
- Test Doubles 분류: Dummy, Stub, Spy, Mock, Fake의 차이
- ABAP Test Double Framework:
CL_ABAP_TESTDOUBLE로 동적 Mock 생성 (7.52+) - SQL Test Double:
cl_osql_test_environment로 DB 액세스 격리 - RAP/CDS 테스트: Behavior 구현 테스트와 Test Double 활용
- Dependency Lookup Pattern: 생성자 주입이 어려운 레거시에서의 대안
참고 자료
- SAP Help Portal — ABAP Unit
- SAP Help Portal — Test Doubles in ABAP Unit
- SAP Clean ABAP Styleguide — Testing 섹션
- SAP Community — ABAP Topic
핵심 한 줄
의존을 INTERFACE 뒤로 숨기고 생성자로 주입하면, Mock Class 하나로 테스트는 빠르고 격리되며 재현 가능해집니다.
댓글 0
아직 댓글이 없습니다.