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( )
Инициализирует метод, который требуется замокать на данную конфигурацию.
И дальше уже можно спокойно использовать этот псевдо-объект в тестах, когда дойдёт дело до нужного метода, то заданное значение всплывёт из конфигурации.