Unit-тесты: универсальные ABAP test doubles для SAP BPC
Принцип автоматизированного тестирования и TDD (test driven development) всё больше из передового подхода превращается в обычный инструмент, используемый многими по умолчанию. В мире ABAP данный подход также распространяется всё более широко, хотя и с некоторым опозданием от остального мира программирования.
Оглавление
Подход к созданию test doubles
Программный код тестируемого класса
Программный код тестового дубля для чтения мастер-данных из тестового набора вместо БД
Программный код фабрики классов zcl_bpc_factory
Введение
Принцип автоматизированного тестирования и TDD (test driven development) всё больше из передового подхода превращается в обычный инструмент, используемый многими по умолчанию. В мире ABAP данный подход также распространяется всё более широко, хотя и с некоторым опозданием от остального мира программирования.
В ABAP для unit-тестов существует инструмент ABAPUnit, доступный с версии WAS 6.4. За основу взята архитектура xUnit, серии framework-ов для написания unit тестов на различных языках программирования. В иерархии автоматизированных тестов unit-тесты являются основой (см. рис 1).

Рис. 1. Пирамида тестов.
Ожидается, что таких тестов должно быть больше других, от них требуется высокая скорость выполнения и высокая стабильность. Общепринятым является мнение, что правильными способами достижения таких показателей является модуляризация, ограничение объёма тестируемой функциональности в каждой единице теста и изоляция от других тестов и внешних вызовов. К внешним вызовам относятся, прежде всего, вызовы уровня БД, а также вызовы API, интеграция с внешними системами.
Для изоляции теста код должен иметь определённую архитектуру, позволяющую приложению работать по-разному в продуктивном и тестовом режиме. При этом цель различий - возможность имитации реальной работы для всех внешних вызовов без реальной необходимости этих самых вызовов.
В данной статье речь пойдёт про изоляцию тестов от уровня БД при программировании для SAP BPC. Данный подход можно тиражировать и на другие модули, использующие ABAP.
Чаще всего для изоляции внешних вызовов, включая изоляцию уровня БД, предлагается использовать те или иные виды дублей реальных объектов (test doubles). Самый обычный подход - каждый доступ к БД, например, select или одно чтение модели BPC упаковывать в интерфейс работы с БД. Для тестирования такого кода необходимо указать дублю, какие выходные параметры возвращать при определённых входных параметрах.
Начиная с версии 7.40 SP9, в этом может помочь ABAP Test Double Framework. В версии 7.51 появился инструмент ABAP CDS Test Double Framework, позволяющий протестировать ABAP CDS Views с изоляцией уровня БД. В версии 7.52 к нему добавился ABAP SQL Test Double Framework, позволяющий изолировать вызовы OpenSQL без необходимости переписывать код.
Подход к созданию test doubles
В данной статье хочу поделиться своим подходом к тестированию ABAP кода для SAP BPC. Подход ближе к инструментам ABAP CDS Test Double Framework и ABAP SQL Test Double Framework, с точки зрения подготовки тестовых данных и почти полного отсутствия необходимости рефакторить код, но работает и в более ранних версиях (проверялся начиная с 7.30).
Для чтения и записи данных SAP BPC на ABAP существуют несколько API. Для чтения основных данных (измерений) используется интерфейс if_uja_dim_data, для чтения иерархий if_uja_hier, для транзакционных данных (данных модели) можно использовать if_ujo_query для чтения и if_ujo_write_back для записи. Также в написанных мной приложениях иногда требовалось считывать параметры модели и среды с помощью cl_uj0_param, а для реализации алгоритмов проверки полномочий смотреть наличие пользователя в группе (TEAM) или наличия у пользователя профиля доступа к данным (DAP, Data Access Profile).
Все указанные API являются классами и интерфейсами. Поэтому я решил создать фабрику классов, а для каждого интерфейса создал дубль для имитации реального класса во время теста. Задача фабрики - при запуске через abap unit вернуть дубль, а при продуктивном запуске обычный класс из стандартного API. То есть, для возможности тестирования кода следует лишь заменить код создания классов и интерфейсов на вызов созданной нами фабрики, и можно писать тест.
Реальный пример с кодом
Задание на тест
Рассмотрим пример на тестовой модели с тестовыми данными.
Предположим, что мы реализуем логику проверки возможности редактирования среза данных в BADI UJW_LOCKOUT_SCHEDULE_BADI. Есть измерение CAT "версия", у него есть атрибут LOCKED. Если он равен 'X', то ввод данных в эту версию запрещён, иначе разрешён.
В соответствии с подходом TDD (test driven development) пишем сначала тест. Я обычно делаю это после того, как создан пустой класс реализации, но логики ещё нет.
Программный код
Программный код теста
TYPES: BEGIN OF ty_trandata,
cat TYPE uj_dim_member,
signeddata TYPE uj_sdata,
END OF ty_trandata.
TYPES: BEGIN OF ty_masterdata,
id TYPE uj_dim_member,
locked TYPE uj_dim_member,
END OF ty_masterdata.
DATA: l_t_trandata TYPE TABLE OF ty_trandata,
l_t_trandata_s TYPE TABLE OF ty_trandata,
l_t_trandata_e TYPE TABLE OF ty_trandata,
l_t_masterdata TYPE TABLE OF ty_masterdata.
* Первый кейс - данные не должны быть блокированы
* Формируем тестовые мастер-данные
APPEND VALUE #( id = 'V1' locked = 'X' ) TO l_t_masterdata.
* Добавляем в тестовый буфер
DATA(lo_md_stub) = NEW zcl_masterdata_stub(
i_appset = 'APPSET1'
i_dimension = 'CAT' ).
lo_md_stub->append_itab(
i_appset = 'APPSET1' " среда BPC
i_dimension = 'CAT' " имя измерения
i_t_data = l_t_masterdata ).
* Передаём тестовый дубль в фабрику, чтобы во время теста основные данные возвращал он
zcl_bpc_factory=>set_masterdata_api(
i_appset = 'APPSET1' " среда BPC
i_dimension = 'CAT' " имя измерения
io_mdata = lo_md_stub ).
* Транзакционные данные для тестового вызова BADI
APPEND VALUE #( cat = 'V1' signeddata = 5 ) TO l_t_trandata.
* Запуск теста
DATA(lo_badi_under_test) = NEW zcl_our_imp( ).
CALL METHOD lo_badi_under_test->if_ujw_check_lock~check_lockout_schedule_status
EXPORTING
i_appset_id = 'APPSET1'
i_user_info = VALUE #( )
i_appl_id = 'MODEL1'
i_module_id = 'MAN'
i_blockaction = VALUE #( )
i_blockstatus = VALUE #( )
it_apptab = l_t_trandata
it_tdtab = VALUE #( )
IMPORTING
et_lockout_s = l_t_trandata_s
et_lockout_e = l_t_trandata_e.
* Проверяем правильность результата
* Данные должны быть блокированы, то есть вернуться из параметра et_lockout_e
* Из et_lockout_s ничего не должно вернуться
cl_aunit_assert=>assert_equals( exp = l_t_trandata
act = l_t_trandata_e ).
cl_aunit_assert=>assert_initial( l_t_trandata_s ).
* Второй кейс - данные не должны быть блокированы
REFRESH l_t_trandata_e.
* Формируем тестовые мастер-данные, на этот раз без установленного атрибута LOCKED
REFRESH l_t_masterdata.
APPEND VALUE #( id = 'V1' locked = ' ' ) TO l_t_masterdata.
* Добавляем в тестовый буфер
lo_md_stub = NEW zcl_masterdata_stub(
i_appset = 'APPSET1'
i_dimension = 'CAT' ).
lo_md_stub->append_itab(
i_appset = 'APPSET1' " среда BPC
i_dimension = 'CAT' " имя измерения
i_t_data = l_t_masterdata ).
zcl_bpc_factory=>set_masterdata_api(
i_appset = 'APPSET1' " среда BPC
i_dimension = 'CAT' " имя измерения
io_mdata = lo_md_stub ).
* Запуск теста
lo_badi_under_test = NEW zcl_our_imp( ).
CALL METHOD lo_badi_under_test->if_ujw_check_lock~check_lockout_schedule_status
EXPORTING
i_appset_id = 'APPSET1'
i_user_info = VALUE #( )
i_appl_id = 'MODEL1'
i_module_id = 'MAN'
i_blockaction = VALUE #( )
i_blockstatus = VALUE #( )
it_apptab = l_t_trandata
it_tdtab = VALUE #( )
IMPORTING
et_lockout_s = l_t_trandata_s
et_lockout_e = l_t_trandata_e.
* Проверяем правильность результата
* Теперь правильно наоборот.
* Данные должны быть не блокированы, то есть вернуться из параметра et_lockout_s
* Из et_lockout_e ничего не должно вернуться
cl_aunit_assert=>assert_equals( exp = l_t_trandata
act = l_t_trandata_s ).
cl_aunit_assert=>assert_initial( l_t_trandata_e ).
Запускаем тест и получаем ошибку, что корректно, ведь сам код ещё не написан. Теперь по методологии TDD нам следует написать код так, чтобы он проходил указанный тест.
Программный код тестируемого класса
CLASS zcl_our_imp DEFINITION.
PUBLIC SECTION.
INTERFACES if_ujw_check_lock.
ENDCLASS.
CLASS zcl_our_imp IMPLEMENTATION.
METHOD if_ujw_check_lock~check_lockout_schedule_status.
Если хотите прочитать статью полностью и оставить свои комментарии присоединяйтесь к sapland
ЗарегистрироватьсяУ вас уже есть учетная запись?
Войти