Test the untestable

Одной из отличительных черт профессионального программного продукта является покрытие кода юнит-тестами. Это позволяет безболезненно внедрять новый функционал и редактировать старый, не боясь что-то сломать. К тому же это позволяет проверить работоспособность программы даже в случае отсутствия тестовых данных, что так часто встречается в ABAP-разработке. Однако одно из узких мест - запросы к БД, которые мешают построить полноценные тесты, и которые зачастую бывает трудно или нецелесообразно вынести в отдельные модули или зависимости.

Современный ABAP предлагает достаточно широкий функционал для юнит-тестирования. В него входит (с версии 7.5) и фреймворк, позволяющий заменить запросы к БД заглушками с тестовыми данными. В этой заметке разберемся с ним подробнее и рассмотрим возможности практического применения.

##Open SQL Test Double Framework

Современные принципы программирования указывают на то, что разрабатываемый продукт должен быть обязательно покрыт тестами. В случае юнит-тестирования целью данного процесса является проверка работоспособности кода, фиксация ожидаемого поведения его интерфейса (реже - внутренней логики работы), формальное описание ограничений и ожиданий от интерфейса модуля, а также некоторые методологии вроде TDD. Для этого код необходимо проектировать определенным образом - делить на отдельные сущности, которые можно независимо протестировать. Этого несложно добиться, однако узким местом часто становится взаимодействие с внешними интерфейсами, например, с пользовательским интерфейсом, чтением данных из файла, сетевой обмен данными, взаимодействие с БД и другие.

Все эти взаимодействия принято инкапсулировать в отдельные сущности и работать с ними через их интерфейс, а уже их самих покрывать тестами отдельно. Однако на практике реализовать это для взаимодействия с базой данных не так то просто ввиду того, что структура БД часто определяет модель данных, да и подвержена изменениям на стадии активной разработки. На помощь в этом приходят разные подходы вроде DI и ORM, я также люблю вынести взаимодействие с БД в отдельную сущность с красивым интерфейсом, внутри которой реализовать всю “некрасивую” логику по оптимальной выборке и изменению данных. Однако это не всегда удобно, а некоторый существующий код сложно или невозможно переделать так, чтобы отделить вызовы БД в отдельный модуль. Да и как не выноси, их рано или поздно тоже придется тестировать.

Начиная с версии 7.5 появился инструмент, позволяющий заменить реальные таблицы тестовыми данными - Open SQL Test Double Framework (который входит в набор ABAP Test Double Frameworks - ATDF). Он включает в себя набор классов, позволяющих заменять тестовыми данными любые Open SQL источники данных, например Transparent Tables, Views, CDS Views, CDS Table Functions. Путем нехитрых манипуляций он позволяет указать источники данных, которые будут заменены тестовыми копиями, задать необходимый набор тестовых данных, который будет использован вместо реального содержимого таблицы при работе с ней. После этих действий можно запускать выполнение тестируемого кода, не изменяя ни единой строки в нем. Все запросы к БД выполнятся как обычно, но не к реальной базе, а к тестовой заглушке.

##Использование тула

Класс, который используется для создания тестовых заглушек - cl_osql_test_environment.

Для начала работы нужно создать тестовое окружение. Делается это через фабричный метод, на вход которого передается список заменяемых таблиц. В результате вы получите объект тестового окружения, используя который осуществляется заполнение заглушек тестовыми данными. При этом в базе данных будут созданы временные заглушки (test doubles), к которым будут переадресованы SQL запросы.

METHOD class_setup.

  sr_env = cl_osql_test_environment=>create(
    VALUE #(
      ( 'MARA' )
      ( 'MAKT' )
      ( 'MARC' )
      ( 'ZTEST_C_MATERIAL_PLANT_AGGR' )
    )
  ).

ENDMETHOD. 

Этот вызов необходимо располагать в class_setup, так как окружение создается один раз перед запуском тестов. Может быть создана только одна тестовая среда. После выполнения тестов в class_teardown необходимо уничтожить тестовое окружение, вызвав метод destroy созданного объекта окружения.

METHOD class_teardown.
  sr_env->destroy( ).
ENDMETHOD.

Также перед каждым тестовым методом необходимо очистить буфер тестовых данных, для чего в методе setup вызывается метод clear_doubles тестового окружения.

METHOD setup.
  sr_env->clear_doubles( ).
ENDMETHOD.

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

sr_env->insert_test_data(
  i_data  = lt_marc
).
sr_env->insert_test_data(
  i_data              = lt_aggr
  i_parameter_values  = VALUE #( 
    ( 
      parameter_name  = 'P_YEAR' 
      parameter_value = '2018' 
    ) 
  )
).

Также можно указать список параметров (например, для CDS View с параметрами); указать мандант, если вы хотите добавить данные для манданта, отличного от системного; а также указать список полей, которые необходимо заполнить NULL значениями.

После этого можно запускать тестируемый код, все SQL запросы к указанным таблицам отработают в тестовом окружении. При этом запросы на изменение также отработают корректно, результаты можно проверить, считав данные из таблицы после выполнения тестового кода. COMMIT WORK и ROLLBACK WORK также отработают как надо.

Полный код примера можно посмотреть здесь

##Сухой остаток

Данный инструмент позволяет покрыть тестами самые сложные участки кода, где есть запросы к БД. Не всегда есть возможность перепроектировать код таким образом, чтобы вынести запросы к БД в отдельную зависимость. Не всегда это целесообразно. Иногда приходится работать с таким кодом, который состоит из переплетения запросов настолько, что распутывание этой цепочки может занять не одну неделю, которой у разработчика может и не быть. А современная парадигма разработки под S/4 толкает на перенос все большего количества логики на сторону БД. Здесь как никогда кстати инструмент, который позволяет без лишних затрат “подсунуть” тестовые данные в таблицы и покрыть тестами даже то, что не понятно как работает.

comments