Методология тестирования, управляемого данными (DDT) применяется в автоматизации тестирования ПО, представляет собой тестирование, выполнение и верификация которого производится на основе данных, которые хранятся в БД или любых других источниках данных.
Обычно сравнивают эталонные данные с теми, что на выходе получает система из метода (функции, программы и т.п.).
Тестирование, управляемое данными подразумевает разделение юнит тестов и данных, которые в них проверяются. Юнит тесты получают эталонные данные из некого источника и сравнивают их с результатами, полученными при тестировании объекта.
Изначально ABAP Unit не предоставляет никакого сервиса для хранения и ведения тестируемых данных, как например это делают другие фреймворки для тестирования: jUnit, nUnit. В статье пойдет речь о том как обойти это недоразумение.
В качестве подобного сервиса можно использовать контейнеры данных eCATT. Что такое eCATT и для чего он нужен можно посмотреть тут. Нас интересует один из элементов eCATT, а именно контейнер тестовых данных. Исходя из названия, становится понятно, что контейнер позволяет хранить внутри себя какие-то данные, но кроме хранения можно так же и вести (изменять) эти данные.
Рассмотрим небольшой пример, допустим, есть метод рассчитывающий тип треугольника относительно размеров его сторон, как известно из школьной программы, тип может быть следующим:
- Равносторонний (все стороны равны)
- Равнобедренный (хотя бы две стороны равны)
- Разносторонний (нет равных сторон).
Создадим класс ZCL_TRIANGLE c указанными атрибутами:
Метод GET_TYPE:
Параметры метода:
Реализация:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
method GET_TYPE. IF ( iv_a <= 0 ) OR ( iv_b <= 0 ) OR ( iv_c <= 0 ). RAISE EXCEPTION TYPE zcx_wrong_tria_parameters. ENDIF. IF ( iv_a = iv_b AND iv_b = iv_c ). re_type = zcl_triangle=>cv_equilateral. " Односторонний RETURN. ENDIF. IF ( iv_a = iv_b ) OR ( iv_b = iv_c ) OR ( iv_c = iv_a ). re_type = zcl_triangle=>cv_isosceles. " равнобедренный RETURN. ENDIF. re_type = zcl_triangle=>cv_scalene. " разносторонний endmethod. |
Как видно из примера, в случае некорректных данных вызывается исключение. В данной статье не описывается создание и использование классов исключений, при желании можете посмотреть в ранее опубликованной статье.
Статья подразумевает что вы знаете как писать юнит тесты, а если нет, добро пожаловать в введение.
Напишем сначала юнит тест, проверяющий метод без использования каталогов тестов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
*"* use this source file for your ABAP unit test classes CLASS lcl_triangle_test DEFINITION FOR TESTING RISK LEVEL HARMLESS. PUBLIC SECTION. METHODS: test_scalene FOR TESTING, test_isosceles FOR TESTING, test_equilateral FOR TESTING. ENDCLASS. CLASS lcl_triangle_test IMPLEMENTATION. METHOD test_equilateral. DATA: lv_type TYPE i, lo_exc TYPE REF TO zcx_wrong_tria_parameters. " Равносторонний TRY. cl_abap_unit_assert=>assert_equals( act = zcl_triangle=>get_type( iv_a = 1 iv_b = 1 iv_c = 1 ) exp = zcl_triangle=>cv_equilateral ). CATCH zcx_wrong_tria_parameters INTO lo_exc. cl_abap_unit_assert=>fail( EXPORTING msg = lo_exc->get_text( ) ). ENDTRY. ENDMETHOD. METHOD test_isosceles. DATA: lv_type TYPE i, lo_exc TYPE REF TO zcx_wrong_tria_parameters. " Равнобедренный TRY. cl_abap_unit_assert=>assert_equals( act = zcl_triangle=>get_type( iv_a = 1 iv_b = 1 iv_c = 3 ) exp = zcl_triangle=>cv_isosceles ). CATCH zcx_wrong_tria_parameters INTO lo_exc. cl_abap_unit_assert=>fail( EXPORTING msg = lo_exc->get_text( ) ). ENDTRY. ENDMETHOD. METHOD test_scalene. DATA: lv_type TYPE i, lo_exc TYPE REF TO zcx_wrong_tria_parameters. " Разносторонний TRY. cl_abap_unit_assert=>assert_equals( act = zcl_triangle=>get_type( iv_a = 1 iv_b = 2 iv_c = 3 ) exp = zcl_triangle=>cv_scalene ). CATCH zcx_wrong_tria_parameters INTO lo_exc. cl_abap_unit_assert=>fail( EXPORTING msg = lo_exc->get_text( ) ). ENDTRY. ENDMETHOD. ENDCLASS. |
Как вы можете убедиться, запустив тест (ctrl+shift+F10), он будет успешно пройден:
Но что делать, если количество тестовых данных возрастает? Описывать множество возможных вариантов для тестирования в самом юнит тесте не корректно, т.к. объем кода в этом случае будет огромным. Тут к нам на помощь приходит методология тестирования, управляемого данными и каталог тестовых данных eCATT.
- Создаем каталог, запустив транзакцию SECATT:
- Определяем параметры (то из чего состоит) каталога:
- Далее необходимо определить сами данные, данные определяются в так называемых вариантах, по умолчанию всегда в системе есть вариант ECATTDEFAULT, его будем игнорировать в дальнейшем:
На данном этапе закончим с eCATT и создадим один небольшой класс, от которого будут наследоваться наши тестовые классы, в нем мы напишем метод, запускающий тестовые методы с указанным каталогом тестов.
Атрибуты:
У класса будет единственный метод run_variants, который используя API eCATT, будет получать тестовые данные, API реализовано в классе cl_apl_ecatt_tdc_api. Более подробное описание API можно найти официальной документации.
Параметры метода:
Код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
METHOD run_variants. DATA: lt_variants TYPE etvar_name_tabtype, lo_ex TYPE REF TO cx_root. " Получаем данные SECATT TRY . go_tdc_api = cl_apl_ecatt_tdc_api=>get_instance( iv_container_name ). " Считываем все варианты из контейнера lt_variants = go_tdc_api->get_variant_list( ). CATCH cx_ecatt_tdc_access INTO lo_ex. cl_aunit_assert=>fail( msg = |Варианты { gv_current_variant } не считаны: { lo_ex->get_text( ) }| quit = if_aunit_constants=>no ). RETURN. ENDTRY. " Удаляем вариант по умолчанию DELETE lt_variants WHERE table_line = 'ECATTDEFAULT'. " Запускаем тестовый метод со всеми тестовыми данными (вариантами) " Метод не должен содержать параметров и должен быть создан в дочерних классах LOOP AT lt_variants INTO gv_current_variant. TRY . CALL METHOD (iv_method_name). CATCH cx_root INTO lo_ex. cl_aunit_assert=>fail( msg = |Вариант { gv_current_variant } не выполнен: { lo_ex->get_text( ) }| quit = if_aunit_constants=>no ). ENDTRY. ENDLOOP. ENDMETHOD. |
Далее перепишем наш тестовый класс:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
*"* use this source file for your ABAP unit test classes DEFINE get_val. try. go_tdc_api->get_value( exporting i_param_name = &1 i_variant_name = gv_current_variant changing e_param_value = &2 ). catch cx_root. endtry. END-OF-DEFINITION. CLASS lcl_triangle_test DEFINITION FOR TESTING RISK LEVEL HARMLESS INHERITING FROM zcl_ecat_unit. PUBLIC SECTION. METHODS: test_type FOR TESTING, test_type_variant. ENDCLASS. CLASS lcl_triangle_test IMPLEMENTATION. METHOD test_type. run_variants( iv_container_name = 'ZTRIANGLE_CATALOG' iv_method_name = 'TEST_TYPE_VARIANT' ). ENDMETHOD. METHOD test_type_variant. DATA: lv_a TYPE i, lv_b TYPE i, lv_c TYPE i, lv_exp_type TYPE i, lo_exc TYPE REF TO zcx_wrong_tria_parameters. get_val: 'A' lv_a, 'B' lv_b, 'C' lv_c, 'EXP_TYPE' lv_exp_type. TRY. cl_abap_unit_assert=>assert_equals( exp = lv_exp_type act = zcl_triangle=>get_type( iv_a = lv_a iv_b = lv_b iv_c = lv_c ) quit = if_aunit_constants=>no msg = |Не верное значение типа для варианта { gv_current_variant }| ). CATCH zcx_wrong_tria_parameters INTO lo_exc. cl_abap_unit_assert=>fail( EXPORTING msg = |Ошибка при передаче тестовых параметров { gv_current_variant }| quit = if_aunit_constants=>no ). ENDTRY. ENDMETHOD. ENDCLASS. |
Как видно покрытие тестами возрасло, а код уменьшился на порядок. В итоге благодаря DDT и eCATT можно сильно облегчить себе жизнь в ходе тестирования, опираясь на данные, не зашитые в самом юнит тесте.
Материал данной статьи был позаимствован из следующего источника: