Меню

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

У вас уже есть учетная запись?

Войти