ZDOC_2769

ABAP Test Double Framework – первое знакомство

Cтолкнулся с новым фреймворком Test Double. В чём же основной смысл и назначение этого фреймворка, с чем его едят?

Дано: Основной класс ZCL_ACTION

CLASS zcl_action DEFINITION.
  PUBLIC SECTION.
    METHODS constructor IMPORTING actor TYPE REF TO zcl_actor.
    METHODS who_am_i RETURNING VALUE(rv_name) TYPE string.

  PRIVATE SECTION.
    DATA: mo_actor TYPE REF TO zcl_actor.
ENDCLASS.

CLASS zcl_action IMPLEMENTATION.
  METHOD constructor.
    mo_actor = actor.
  ENDMETHOD.

  METHOD who_am_i.
    rv_name = mo_actor->get_name( ).
  ENDMETHOD.
ENDCLASS.

И реализация класса ZCL_ACTOR при этом следующая:

CLASS zcl_actor DEFINITION .
  PUBLIC SECTION.
    METHODS: get_name RETURNING VALUE(rv_name) TYPE string.

ENDCLASS.

CLASS zcl_actor IMPLEMENTATION.
  METHOD get_name.
    rv_name = 'Real value'.
  ENDMETHOD.
ENDCLASS.

Требуется: написать юнит-тест для класса ZCL_ACTION.

Можно конечно написать так:

CLASS lcl_action_test DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
  PUBLIC SECTION.
    METHODS who_am_i FOR TESTING.
ENDCLASS.
CLASS lcl_action_test IMPLEMENTATION.
  METHOD who_am_i.

    DATA(lo_action) = NEW zcl_action( NEW zcl_actor( ) ).

    DATA(lv_return) = lo_action->who_am_i( ).

    cl_abap_unit_assert=>assert_equals( act = lv_return
                                        exp = 'Real value' ).

  ENDMETHOD.
ENDCLASS.

Но здесь всё не совсем честно. Каждый юнит-тест должен проверять свой отдельный класс и быть условно-независимым от других классов. Тем более, что зависмый класс может находиться в то же самое время в разработке у другого разработчика или содержать громоздкую инициализацию.

По стандартным правилам мы должны написать mock-объект для класса ZCL_ACTOR и в тестах ZCL_ACTION использовать именно его. И так как такое занятие может быть несколько утомительным, то при помощи фреймворка Test Double мы можем на лету создать mock-объект и подсунуть его в наш тестируемый класс. Именно в этом и заключается профит.

Необходимые требования:

  • для зависимого класса должен быть сделан глобальный интерфейс, обращение к зависимому классу должно быть сделано через этот интерфейс
  • количество использований за один раз несколько ограничено, так как данный фреймворк временно генерирует настоящий ABAP-класс, представляющий требуемый интерфейс (GENERATE SUBROUTINE POOL со всеми вытекающими)
  • поддерживается только в версиях 7.40/7.50 и выше
  • ну наверняка чего-то не умеет по мелочи (статические методы, исключения не-class-based)

Сначала разберёмся со вспомогательным классом, вынесем наружный интерфейс:

CLASS zcl_actor DEFINITION .
  PUBLIC SECTION.
    INTERFACES: zif_actor.

ENDCLASS.

CLASS zcl_actor IMPLEMENTATION.
  METHOD zif_actor~get_name.
    rv_name = 'Real value'.
  ENDMETHOD.
ENDCLASS.

Приспособим интерфейс внутри основного класса:

CLASS zcl_action DEFINITION.
  PUBLIC SECTION.
    METHODS constructor IMPORTING actor TYPE REF TO zif_actor.
    METHODS who_am_i RETURNING VALUE(rv_name) TYPE string.

  PRIVATE SECTION.
    DATA: mo_actor TYPE REF TO zif_actor.
ENDCLASS.

CLASS zcl_action IMPLEMENTATION.
  METHOD constructor.
    mo_actor = actor.
  ENDMETHOD.

  METHOD who_am_i.
    rv_name = mo_actor->get_name( ).
  ENDMETHOD.
ENDCLASS.

И hello-world в нашем случае будет таким:

CLASS lcl_action_test DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
  PUBLIC SECTION.
    METHODS who_am_i FOR TESTING.
ENDCLASS.
CLASS lcl_action_test IMPLEMENTATION.
  METHOD who_am_i.

    DATA: lo_actor TYPE REF TO zif_actor.
    lo_actor ?= cl_abap_testdouble=>create( 'zif_actor' ).

    cl_abap_testdouble=>configure_call( lo_actor )
                      ->returning( 'Fake value' ).
    lo_actor->get_name( ).

    DATA(lo_action) = NEW zcl_action( lo_actor ).

    DATA(lv_return) = lo_action->who_am_i( ).

    cl_abap_unit_assert=>assert_equals( act = lv_return
                                        exp = 'Fake value' ).
  ENDMETHOD.
ENDCLASS.

Разберём магию:

Шаг 1:

lo_actor ?= cl_abap_testdouble=>create( ‘zif_actor’ )
Создаёт и возвращает mock-объект по требуемому интерфейсу.

Шаг 2:

cl_abap_testdouble=>configure_call( lo_actor ) ->returning( ‘Fake value’ )
Задаёт конфигурацию, подготавливает выходные параметры для ожидаемого вызова.

Шаг 3:

lo_actor->get_name( )
Инициализирует метод, который требуется замокать на данную конфигурацию.

 

И дальше уже можно спокойно использовать этот псевдо-объект в тестах, когда дойдёт дело до нужного метода, то заданное значение всплывёт из конфигурации.