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
ЗарегистрироватьсяУ вас уже есть учетная запись?
Войти