如何对 ABAP 代码进行单元测试
ABAP 单元测试
单元测试是一种专注于软件产品组件的软件测试。目的是确保每个软件代码单元按预期工作。单元可以是应用程序源代码中的函数、方法、模块、对象或其他实体。
单元测试通常由开发人员在项目的编码阶段创建,并作为代码库中存在的代码编写,与它正在测试的应用程序代码一起。存在许多单元测试框架,可帮助开发人员管理和执行单元测试。
单元测试的作用:
- 确保单个软件单元(即执行具有可验证效果的代码单元)的功能正确性
- 旨在测试较大软件单元的各个组件在开发和质量保证阶段是否正常工作。通常,此类单个软件单元是方法
- 必须由开发人员创建和运行
在 ABAP 中,开发人员可以使用 ABAP Unit(集成到 ABAP 运行时框架中的测试工具)。它可用于运行单个或批量测试,以及评估测试结果。请注意,可以使用 ABAP 测试主控室执行全面的测试运行
在 ABAP 程序中,单个单元测试作为本地测试类的测试方法实现。
创建测试类
定义:测试类是特殊的本地或全局类,其中 ABAP 单元的测试以测试方法的形式实现。在特殊测试包含的类池中创建。请参阅 ADT 中的“测试类”选项卡。只能用作测试运行的一部分。不是在生产系统中生成的,即测试类的源代码不是其程序生产代码的一部分。
测试类的框架,主要是借助于 FOR TESTING
语句,如下:
"Test class in the test include
CLASS ltc_test_class DEFINITION
FOR TESTING "Defines a class to be used in ABAP Unit
RISK LEVEL HARMLESS "Defines risk level, options: HARMLESS/CRITICAL/DANGEROUS
DURATION SHORT. "Expected test execution time, options: SHORT/MEDIUM/LONG
...
ENDCLASS.
CLASS ltc_test_class IMPLEMENTATION.
...
ENDCLASS.
FOR TESTING
可用于多种用途:
- 创建包含测试方法的测试类
- 创建测试双精度
- 创建帮助程序方法以支持 ABAP 单元测试
可选条件 RISK LEVEL ...
:
CRITICAL
:测试更改系统设置或自定义数据(默认)-DANGEROUS
:测试更改持久数据HARMLESS
:test 不会更改系统设置或持久数据
可选条件 DURATION ...
:
SHORT
:预计执行时间仅为几秒钟MEDIUM
:预计执行时间约为一分钟LONG
:预计执行时间超过一分钟
如何写一个单元测试
通过 ABAP 单元测试自动检查可测试的代码,我们就会说可测试的代码。因此,方法或子例程有一个干净的接口,并且对全局数据的依赖性尽可能少,即代码的干净封装。
我们先来看一个报表程序,该程序是在没有 FORM
例程的情况下经典开发的,并且仍然包含许多全局变量和相互依赖关系。我们的新实现旨在向报表添加新函数,我们希望在其中使用其他地理数据来丰富输出数据。我们希望为新功能提供单元测试。
REPORT z_test_ioh.
*----------------------------------------------------------------------*
*--- Globale data
*----------------------------------------------------------------------*
TABLES:
t001.
TYPES:
BEGIN OF ts_output,
bukrs TYPE t001-bukrs,
butxt TYPE t001-butxt,
name1 TYPE adrc-name1,
name2 TYPE adrc-name2,
city1 TYPE adrc-city1,
addr_group TYPE adrc-addr_group,
END OF ts_output,
tt_output TYPE STANDARD TABLE OF ts_output WITH EMPTY KEY.
DATA:
gt_company_code TYPE SORTED TABLE OF t001 WITH UNIQUE KEY bukrs,
gs_company_code TYPE t001,
gs_address TYPE adrc,
gt_output_table TYPE tt_output,
gs_output_table TYPE ts_output,
gs_vari TYPE disvariant,
go_alv TYPE REF TO cl_salv_table.
*----------------------------------------------------------------------*
*--- Selection screen
*----------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK b01.
SELECT-OPTIONS:
s_bukrs FOR t001-bukrs,
s_waers FOR t001-waers.
PARAMETERS:
p_vari TYPE slis_vari DEFAULT '/DEFAULT',
p_test AS CHECKBOX DEFAULT abap_true.
SELECTION-SCREEN END OF BLOCK b01.
*----------------------------------------------------------------------*
*--- Events
*----------------------------------------------------------------------*
INITIALIZATION.
AUTHORITY-CHECK OBJECT 'S_TCODE'
ID 'TCD' FIELD 'Z60DUMMY_TCODE'.
IF sy-subrc <> 0.
MESSAGE e000(z60bc) WITH 'No authority for transaction' 'Z60DUMMY_TCODE'.
ENDIF.
AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_vari.
gs_vari-report = sy-repid.
gs_vari-username = sy-uname.
gs_vari-handle = 'HDL'.
CALL FUNCTION 'REUSE_ALV_VARIANT_F4'
EXPORTING
is_variant = gs_vari
i_save = 'A'
IMPORTING
es_variant = gs_vari
EXCEPTIONS
OTHERS = 1.
IF sy-subrc = 0.
p_vari = gs_vari-variant.
ENDIF.
START-OF-SELECTION.
CLEAR: gt_company_code, gt_output_table.
SELECT *
FROM t001
WHERE bukrs IN @s_bukrs
AND waers IN @s_waers
INTO TABLE @gt_company_code.
LOOP AT gt_company_code INTO gs_company_code.
CLEAR: gs_output_table.
gs_output_table = CORRESPONDING #( gs_company_code ).
SELECT SINGLE *
FROM adrc
WHERE addrnumber = @gs_company_code-adrnr
INTO @gs_address.
IF sy-subrc = 0.
gs_output_table = CORRESPONDING #( BASE ( gs_output_table ) gs_address ).
ENDIF.
INSERT gs_output_table INTO TABLE gt_output_table.
ENDLOOP.
cl_salv_table=>factory(
IMPORTING r_salv_table = go_alv
CHANGING t_table = gt_output_table ).
DATA(lo_func) = go_alv->get_functions( ).
lo_func->set_all( ).
DATA(lo_disp) = go_alv->get_display_settings( ).
lo_disp->set_striped_pattern( abap_true ).
lo_disp->set_list_header( 'Found data' ).
DATA(lo_lay) = go_alv->get_layout( ).
lo_lay->set_default( abap_true ).
lo_lay->set_key( VALUE #( report = sy-repid handle = 'HDL' ) ).
lo_lay->set_save_restriction( ).
IF p_vari IS NOT INITIAL.
lo_lay->set_initial_layout( p_vari ).
ENDIF.
go_alv->display( ).
我们在全局类中实现新函数。因此,我们提供了一个接口,该接口为报表提供接口。此外,我们需要方法接口的输出结构,然后我们必须相应地宣布。我们采用界面中的结构并替换报告中的定义。我们的界面如下所示:
INTERFACE zif_test_ioh PUBLIC.
TYPES:
td_geometry_data TYPE p LENGTH 15 DECIMALS 8,
BEGIN OF ts_output,
bukrs TYPE t001-bukrs,
butxt TYPE t001-butxt,
name1 TYPE adrc-name1,
name2 TYPE adrc-name2,
city1 TYPE adrc-city1,
addr_group TYPE adrc-addr_group,
" New Fields
latitude TYPE td_geometry_data,
longitude TYPE td_geometry_data,
END OF ts_output,
tt_output TYPE STANDARD TABLE OF ts_output WITH EMPTY KEY,
BEGIN OF ts_geometry,
latitude TYPE td_geometry_data,
longitude TYPE td_geometry_data,
END OF ts_geometry.
METHODS:
enrich_data
CHANGING
cs_output TYPE ts_output.
ENDINTERFACE.
CLASS zcl_test_ioh DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_test_ioh.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
get_geolocation_for_city
IMPORTING
id_city TYPE adrc-city1
RETURNING VALUE(rs_geometry) TYPE zif_60bs_test_ioh=>ts_geometry.
ENDCLASS.
CLASS zcl_test_ioh IMPLEMENTATION.
METHOD zif_60bs_test_ioh~enrich_data.
DATA(ls_geometry) = get_geolocation_for_city( cs_output-city1 ).
IF ls_geometry IS INITIAL.
RETURN.
ENDIF.
cs_output-latitude = ls_geometry-latitude.
cs_output-longitude = ls_geometry-longitude.
ENDMETHOD.
METHOD get_geolocation_for_city.
" Call geolocation api
ENDMETHOD.
ENDCLASS.
- 点赞
- 收藏
- 关注作者
评论(0)