본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 26. 03:06

테스트 더블(Test Doubles)과 모킹 프레임워크(Mocking Frameworks)를 활용한 ABAP 유닛 테스트: SAP

요약

ABAP 유닛 테스트 시 발생하는 제어되지 않은 의존성 문제를 해결하기 위해 테스트 더블과 모킹 프레임워크를 활용하는 방법을 안내합니다. 데이터베이스나 RFC 호출로부터 테스트를 격리하여 빠르고 결정론적인 진정한 유닛 테스트를 작성하는 실무 패턴을 다룹니다.

핵심 포인트

  • 제어되지 않은 의존성 격리를 통한 진정한 유닛 테스트 구현
  • 테스트의 4대 원칙: 빠름, 격리됨, 결정론적, 자기 완결적
  • 더미, 스텁 등 테스트 더블의 유형별 활용법 이해
  • 통합 테스트와 유닛 테스트의 명확한 구분

만약 당신이 한동안 ABAP 유닛 테스트(unit tests)를 작성해 왔다면, 아마도 제가 몇 년 전에 겪었던 것과 똑같은 벽에 부딪혔을 것입니다. 테스트가 격리된 상태에서는 통과하지만, 데이터베이스 테이블(database table), 원격 함수 호출(remote function call, RFC), 또는 비즈니스 오브젝트 매니저(business object manager)에 닿는 순간 취약해지는 현상 말입니다. 근본 원인은 거의 항상 동일합니다. 바로 **제어되지 않는 의존성(uncontrolled dependencies)**입니다. **ABAP 테스트 더블(test doubles)과 모킹 프레임워크(mocking frameworks)**를 사용하여 이러한 의존성을 격리하는 방법을 배우는 것은 시니어 SAP 개발자가 갖출 수 있는 가장 영향력 있는 기술 중 하나입니다. 이 가이드는 즉시 적용할 수 있는 실무 패턴과 함께 해당 기술을 심도 있게 안내합니다.

본격적으로 시작하기 전에, 만약 제가 이전에 작성한 SAP S/4HANA에서의 ABAP 유닛 테스트실제 SAP 시스템을 위한 신뢰할 수 있고 유지보수 가능한 테스트 작성법에 관한 글을 읽지 않으셨다면, 거기서부터 시작하시길 권장합니다. 이 글은 해당 기초를 바탕으로 다음 단계인 _진정한 의존성 격리(true dependency isolation)_로 여러분을 안내합니다.

의존성 격리가 생각보다 중요한 이유

여러분이 공감할 만한 상황을 하나 그려보겠습니다. 여러분은 깨끗한 ABAP 클래스인 ZCL_ORDER_PROCESSOR를 작성했고, 이는 완벽하게 작동합니다. 심지어 유닛 테스트도 작성했습니다. 하지만 여러분의 테스트는 실제 BAPI_SALESORDER_CREATEFROMDAT2를 호출하며, 이는 개발 클라이언트(development client)에 실제 판매 오더(sales orders)를 생성합니다. 테스트 스위트(test suite)를 실행하는 데 40초가 걸립니다. 동료가 다른 시스템에서 테스트를 실행하면, 해당 시스템에 고객 번호가 존재하지 않기 때문에 테스트가 실패합니다.

그것은 유닛 테스트가 아닙니다. 유닛 테스트의 탈을 쓴 통합 테스트(integration test)일 뿐입니다.

진정한 유닛 테스트는 반드시 다음과 같아야 합니다:

  • 빠름 (Fast) — 초 단위가 아닌 밀리초(milliseconds) 단위

  • 격리됨 (Isolated) — 데이터베이스(database), RFC, 파일 시스템(file system) 없음

  • 결정론적임 (Deterministic) — 모든 시스템에서 매번 동일한 결과 도출

  • 자기 완결적임 (Self-contained) — 테스트 클래스 외부의 설정 데이터(setup data)가 필요 없음

이를 달성하려면 의존성(dependencies)을 제어해야 하며, 이는 곧 **테스트 더블(test doubles)**을 의미합니다.

반드시 알아야 할 네 가지 유형의 테스트 더블

여기서 사용되는 용어는 xUnit 패턴에 관한 고전적 저술을 남긴 Gerard Meszaros의 정의에서 유래되었으며, ABAP에도 명확하게 적용됩니다.

1. 더미 객체 (Dummy Objects)

전달은 되지만 실제로 사용되지는 않습니다. 현재 테스트 케이스에서 값이 중요하지 않을 때 매개변수 목록(parameter lists)을 채우기 위해 주로 사용됩니다.

2. 스텁 (Stubs)

테스트 중에 발생하는 호출에 대해 미리 준비된 답변(canned answers)을 반환합니다. 스텁은 아무것도 검증하지 않으며, 단지 미리 정의된 응답을 제공할 뿐입니다. 테스트 대상 클래스가 의존성으로부터 데이터를 읽어올 때 이를 사용하십시오.

3. 모크 (Mocks)

어떤 호출을 받아야 하는지에 대한 기대치(expectations)가 미리 프로그래밍되어 있습니다. 모크는 *행위 검증 (verify behavior)*을 수행합니다. 즉, 기대했던 메서드가 호출되지 않았거나 잘못된 매개변수로 호출된 경우 테스트를 실패 처리합니다.

4. 페이크 (Fakes)

실제 구현체이지만 지름길을 택한 형태입니다. 예를 들어 실제 데이터베이스 대신 인메모리 리포지토리(in-memory repository)를 사용하는 식입니다. 이는 가장 정교하며 복잡한 ABAP 시나리오에서 가장 유용합니다.

실무에서 ABAP 테스트 더블 프레임워크(ABAP Test Double Framework)는 이러한 범주들을 다소 모호하게 통합하기도 하지만, 개념적 차이를 이해하는 것이 테스트 설계 방식을 결정짓습니다.

테스트 가능성을 위한 아키텍처 설정

구조가 잘못된 코드에 테스트 더블을 사후에 적용할 수는 없습니다. 가장 중요한 전제 조건은 **의존성 주입 (dependency injection)**입니다. 만약 클래스가 내부에서 자신의 의존성을 직접 인스턴스화(instantiate)한다면, 테스트 시 이를 교체할 방법이 없습니다.

제가 모든 프로젝트에서 사용하는 패턴은 다음과 같습니다:

"--- 인터페이스 정의 (Interface Definition) ---
INTERFACE zif_order_repository.
...

ZCL_ORDER_PROCESSOR가 구체 클래스(concrete class)가 아닌 인터페이스(interface)인 ZIF_ORDER_REPOSITORY에 의존하고 있다는 점에 주목하십시오. 이것이 모든 테스트 더블(test double) 전략을 가능하게 하는 토대입니다. 이 원칙은 제가 이전에 다루었던 클린 코드 및 리팩터링 원칙 (clean code and refactoring principles)과 직접적으로 맞닿아 있습니다. 즉, 클린 아키텍처 (clean architecture)와 테스트 가능성 (testability)은 함께 가는 것입니다.

ABAP 테스트 더블 프레임워크 (TDF) 사용하기

ABAP 7.50부터 사용할 수 있는 SAP의 내장 테스트 더블 프레임워크 (Test Double Framework, TDF)를 사용하면 별도의 테스트 클래스를 작성하지 않고도 모든 인터페이스에 대한 모의 객체 (mock objects)를 생성할 수 있습니다. 매우 강력하지만, 이를 사용하려면 구성 모델 (configuration model)을 이해해야 합니다.

1단계: 테스트 더블 생성하기

CLASS ltc_order_processor_test DEFINITION FINAL FOR TESTING
  DURATION SHORT RISK LEVEL HARMLESS.
...

2단계: 스텁 동작 (Stub Behavior) 구성하기

  METHOD test_returns_correct_order_count.
    " Arrange: 모의 객체가 반환할 값을 정의
...

3단계: 예외 처리 (Exception Handling) 검증하기

  METHOD test_raises_exception_when_no_orders.
    " 예외를 발생시키도록 모의 객체 구성
...

이 방식은 깔끔하고 가독성이 좋으며, 결정적으로 데이터베이스 액세스가 전혀 필요하지 않습니다. 테스트는 밀리초 단위로 실행되며, 모든 시스템에서 매번 정확히 동일한 결과를 생성합니다.

복잡한 시나리오를 위한 페이크 리포지토리 (Fake Repository) 구축

때로는 서로 다른 파라미터 조합을 가진 메서드 호출이 많아질 경우, TDF의 스텁 구성이 너무 장황해질 수 있습니다. 그런 경우에는 실제 의존성을 가볍게 메모리 상에서 구현한 버전인 **페이크 구현체 (fake implementation)**를 작성하는 것을 선호합니다.

"--- 페이크 리포지토리 (테스트 인클루드 내부에 정의) ---
CLASS ltc_fake_order_repository DEFINITION FINAL.
...

테스트에서 페이크를 사용하면 가독성이 매우 높아집니다:

  METHOD test_with_fake_repository.
    " Arrange
...

Fake는 여러 번의 메서드 호출(method calls), 상태 유지 동작(stateful behavior), 또는 복잡한 필터링 로직(filtering logic)을 테스트해야 할 때 특히 강력합니다. 작성하는 데 약간 더 많은 노력이 들지만, 훨씬 더 유연합니다. 여기서 좋은 예외 처리(exception handling) 설계와의 정렬 또한 중요합니다. Fake에서 오류를 깔끔하게 전파하는 방법을 이해하려면 제가 작성한 클래스 기반 예외 아키텍처(class-based exception architecture)에 대한 상세 시리즈를 참조하십시오.

테스트에서 CDS View 및 데이터베이스 액세스 처리하기

현대적인 SAP 개발에서 가장 까다로운 시나리오 중 하나는 CDS View로부터 데이터를 읽는 코드를 테스트하는 것입니다. 만약 클래스가 인라인 SELECT(inline SELECT)를 통해 CDS View를 직접 호출한다면, 아키텍처 변경 없이는 이를 쉽게 모킹(mock)할 수 없습니다.

해결책은 위에서 보여준 패턴과 정확히 일치하게, CDS 액세스를 리포지토리 인터페이스(repository interface)로 래핑(wrap)하는 것입니다. 운영(production) 리포지토리는 CDS View를 쿼리하고, 테스트에서는 Fake 또는 Mock을 사용합니다. 이 접근 방식은 제가 CDS View 성능 최적화 시리즈(CDS Views performance optimization series)에서 설명한 계층화된 CDS 아키텍처와 자연스럽게 통합됩니다. 즉, CDS 소비 계층(consumption layer)과 비즈니스 로직 계층(business logic layer) 사이의 인터페이스 경계가 곧 테스트 가능성(testability)의 경계가 됩니다.

만약 반드시 실제 CDS 데이터로 테스트해야만 한다면, 최후의 수단으로 ABAP의 테스트 심(test seams) (7.40 버전에서 도입됨)을 사용하십시오. 하지만 이를 리팩토링(refactoring) 기회를 알리는 코드 스멜(code smell)로 취급해야 합니다.

흔한 실수와 방지 방법

실수 1: 로직이 아닌 Mock을 테스트하는 것

놀라울 정도로 흔한 실수는 모킹(mock)이 당신이 반환하도록 설정한 값을 반환하는지만을 검증하는 테스트를 작성하는 것입니다. 스스로에게 물어보십시오: 나는 *테스트 대상 클래스의 동작(behavior)*을 테스트하고 있는가, 아니면 단순히 테스트 설정의 연결(wiring)만을 테스트하고 있는가?

실수 2: 과도한 명세 (Over-specification)

특정 파라미터가 정확히 이 순서로 호출되었는지 확인하도록 모크(Mock)를 설정하는 것은 테스트를 취약하게 만듭니다. 구현 세부 사항(Implementation details)이 아닌 모크의 동작(Mock behavior)을 검증하세요. 메서드의 내부 호출 순서가 공개 계약(Public contract)의 일부가 아니라면, 이를 단언(Assert)하지 마십시오.

실수 3: 테스트 간 가변 상태 공유 (Sharing Mutable State Between Tests)

항상 SETUP 메서드에서 새로운 테스트 더블(Test doubles)을 생성하십시오. 만약 한 테스트가 페이크 레포지토리(Fake repository)의 상태를 수정하고 다음 테스트가 그 상태를 상속받는다면, 실행 순서에 따라 결과가 달라지는 원인 불명의 실패가 발생합니다. 이는 테스트 스위트에서 진단하기 가장 어려운 버그 중 하나입니다.

실수 4: 리팩터링 비용 무시 (Ignoring the Refactoring Cost)

강하게 결합된 레거시 코드(Legacy code)에 의존성 주입(Dependency injection)을 사후 적용하는 것은 어렵습니다. 그 비용에 대해 솔직해지십시오. 저는 팀들이 실제 테스트를 작성하는 시간보다 레거시 코드 구조와 싸우는 데 더 많은 시간을 소비하는 것을 보았습니다. 새로운 코드에서는 테스트 가능성(Testability)을 우선시하고, 레거시 코드는 저의 clean code refactoring guide에 소개된 기술들을 사용하여 점진적으로 해결하십시오.

유지보수성을 위한 테스트 클래스 구조화

테스트 스위트가 커질수록 조직화가 매우 중요해집니다. 제가 권장하는 구조는 다음과 같습니다:

  • 행위 시나리오당 하나의 테스트 클래스 — 모든 테스트를 단일 LTC_ 클래스에 몰아넣지 마십시오. 서로 다른 행위(Happy path, 에러 케이스, 엣지 케이스)에 대해 별도의 로컬 테스트 클래스를 사용하십시오.

  • 서술적인 메서드 이름 — 새벽 2시에 실패 원인을 진단할 때는 test_01보다 test_returns_zero_when_customer_has_no_open_orders가 훨씬 더 유용합니다.

  • Arrange-Act-Assert 구조 — 모든 테스트 메서드는 이 패턴을 따라야 합니다. 이는 테스트를 한눈에 읽기 쉽게 만들며, 실제로 무엇을 테스트하고 있는지 명확하게 생각하도록 강제합니다.

  • 짧은 테스트 메서드 — 테스트 메서드가 20~25줄을 초과한다면, 아마도 너무 많은 것을 테스트하고 있는 것입니다. 이를 분리하십시오.

핵심 요약 (Key Takeaways)

수년간 ABAP 시스템을 구축하고 검토해 온 경험을 바탕으로, 제가 자신 있게 말씀드릴 수 있는 내용은 다음과 같습니다:

  • 테스트 더블 (Test Doubles)은 테스트를 위한 사치품이 아니라, 설계 품질의 지표입니다. 만약 의존성 (Dependency)을 모킹 (Mocking)할 수 없다면, 귀하의 아키텍처에는 결합도 (Coupling) 문제가 있는 것입니다.

  • ABAP 테스트 더블 프레임워크 (ABAP Test Double Framework)는 프로덕션 환경에서 사용할 준비가 되어 있으며, 대부분의 시나리오에 충분합니다. 더 복잡한 솔루션을 찾기 전에 이를 깊이 있게 학습하십시오.

  • 복잡한 상태 기반 상호작용 (Stateful Interactions)을 테스트할 때는 페이크 구현체 (Fake implementations)가 가장 좋은 도구입니다. 이를 작성하는 것을 두려워하지 마십시오. 이는 테스트 스위트 (Test suite)의 일부이지, 프로덕션 코드의 오버헤드가 아닙니다.

  • 생성자 (Constructor)를 통한 의존성 주입 (Dependency Injection)은 ABAP 테스트 가능성 (Testability)을 위한 가장 중요한 단일 패턴입니다. 모든 신규 코드에 이를 일관되게 적용하십시오.

  • 깨끗한 테스트 코드는 깨끗한 프로덕션 코드만큼 중요합니다. 비즈니스 클래스 (Business classes)에 적용하는 것과 동일한 아키텍처적 규율을 테스트 클래스에도 적용하십시오.

다음 단계는 무엇인가요? (What's Next?)

이 테스트 시리즈의 다음 기사에서는 독자분들이 가장 많이 요청하신 주제 중 하나인 RAP 비즈니스 오브젝트 (Business Objects) 및 ABAP RESTful 애플리케이션 핸들러 (ABAP RESTful application handlers) 테스트를 다룰 예정입니다. 이 영역에서는 프레임워크에 내장된 테스트 인프라가 진정으로 흥미로운 가능성을 열어줍니다. 기대해 주세요.

그동안, 현재 진행 중인 프로젝트에서 테스트가 없는 클래스를 하나 골라보십시오. 해당 클래스의 외부 의존성을 식별하고, 인터페이스 경계 (Interface boundaries)를 그려보십시오. 그런 다음 첫 번째 테스트 더블을 작성해 보십시오. 그 첫 걸음이 거의 항상 가장 어렵지만, 가장 보람찬 일이 될 것입니다.

현재 어려움을 겪고 있는 특정 모킹 (Mocking) 시나리오에 대해 질문이 있으신가요? 아래에 댓글을 남겨주세요. 모든 댓글을 읽고 구체적인 조언으로 답변드릴 수 있도록 최선을 다하겠습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0