ABAP 中 class 方法的依赖注入
Dependency injection to class-method in ABAP
我有一个场景,我的 class 方法 (A) 调用另一个 class 方法 (B)。所以 A 依赖于 B。我想摆脱依赖性以便能够 运行 单元测试。解耦和依赖注入在某种程度上是基于实例化的。但是 class 方法(静态方法)本质上不需要实例。
我已经使用了两个解决方案,但其中 none 对我来说似乎是 100%:
- 我们创建 class B 的实例(生产代码)或测试替身的实例(用于单元测试)。我们将它作为参数注入到被测 class 方法中。内部 class 方法在注入实例上调用,而不是在 class 上调用。
I don’t like we need to make an instance of class, although we are
using class method. It can take time. It needs a bit more code.
- 我们注入实际的 class 名称作为字符串参数,我们使用动态
CALL METHOD
.
As I am not fan of interpreted languages, I consider this a mess that
can bring serious runtime problems. Since we do this to implement unit
tests and consequently to eliminate possible bugs; using dynamic calls seems
counterproductive. Also it is painful to work with parameters.
还有其他方法可以解决吗?我错过了一些重要的点吗?
下面是两种解决方案的关键部分。没有必要了解问题,但他们可能会有所帮助。
1)
INTERFACE lif_readfile.
CLASS-METHODS gui_upload
IMPORTING file_path TYPE string
CHANGING data_tab TYPE truxs_t_text_data.
ENDINTERFACE.
CLASS lcl_file_operations DEFINITION.
PUBLIC SECTION.
CLASS-METHODS:
get_file_length
IMPORTING
!file_path TYPE string
CHANGING
!filereader TYPE REF TO lif_readfile OPTIONAL
RETURNING
VALUE(text_length) TYPE i.
ENDCLASS.
CLASS lcl_file_operations IMPLEMENTATION.
METHOD get_file_length.
*create instance of default productive class
IF filereader IS NOT BOUND.
filereader = NEW lcl_readfile( ).
ENDIF.
*use instance to call class method
filereader->gui_upload(
EXPORTING file_path = file_path
CHANGING data_tab = lt_data
).
*code under test here..
ENDMETHOD.
ENDCLASS.
2)
CLASS lcl_file_operations DEFINITION.
PUBLIC SECTION.
CLASS-METHODS:
get_file_length
IMPORTING
!file_path TYPE string
!classname TYPE string DEFAULT 'LCL_READFILE'
RETURNING
VALUE(text_length) TYPE i.
ENDCLASS.
CLASS lcl_file_operations IMPLEMENTATION.
METHOD get_file_length.
*parameter definition
ptab = VALUE #( ( name = 'FILE_PATH'
kind = cl_abap_objectdescr=>exporting
value = REF #( file_path ) )
( name = 'DATA_TAB'
kind = cl_abap_objectdescr=>changing
value = REF #( lt_data ) ) ).
DATA(meth) = 'LIF_READFILE~GUI_UPLOAD'.
*dynamic call
CALL METHOD (classname)=>(meth) PARAMETER-TABLE ptab.
*code under test here..
ENDMETHOD.
ENDCLASS.
我认为你的问题与语言无关。您是否查看了论坛中提供的多个答案,您如何看待所提出的不同方法?
您可以将静态调用包装在新接口和 classes 中,使用 instance 方法映射到右侧 class 的静态方法,静态指定。
关于使用"class name"作为变量的ABAP特有的解决方案,正如你所展示的,我个人不喜欢它,但我认为这只是个人喜好问题,并不是很重要。 PS:术语"interpreted language"是一种编程语言,相反(简单地说)是"compiled language";你说的更多是 dynamic link.
到目前为止,我发现了两个对我来说更好的解决方案。但是,如果您知道更好的解决方案;我仍然期待着尝试一下。
工厂+注入器(依赖查找)
改进方案 1 – 实例处理移至工厂。未提供工厂和喷油器的代码——这是标准解决方案。
CLASS lcl_file_operations DEFINITION.
PUBLIC SECTION.
CLASS-METHODS:
get_file_length
IMPORTING
!file_path TYPE string
RETURNING
VALUE(text_length) TYPE i.
ENDCLASS.
CLASS lcl_file_operations IMPLEMENTATION.
METHOD get_file_length.
myfactory=>get_filereader( )->gui_upload(
EXPORTING file_path = file_path
CHANGING data_tab = lt_data
).
*code under test here..
ENDMETHOD.
ENDCLASS.
优点
- 被测代码更简洁、更短。实例在别处创建和测试。
缺点
- 它仍然创建实例。
- 总共有更多代码。
使用 TEST-SEAM 和 TEST-INJECTION
CLASS zcl_file_operations IMPLEMENTATION.
METHOD get_file_length.
TEST-SEAM seam_gui_upload.
zcl_filereader=>gui_upload(
EXPORTING file_path = file_path
CHANGING data_tab = lt_data
).
END-TEST-SEAM.
*code under test here..
ENDMETHOD.
ENDCLASS.
测试方法
*...
TEST-INJECTION seam_gui_upload.
ztc_testdouble=>gui_upload(
EXPORTING file_path = file_path
CHANGING data_tab = lt_data
).
END-TEST-INJECTION.
*...
优点
- 这似乎是迄今为止最好的解决方案。
- 使用class-方法;没有创建实例。
- 总共最短的代码。
缺点
- 被社区认为是肮脏的技术(仅推荐用于遗留代码)。
- 被测代码轻微污染。
备注
我有一个场景,我的 class 方法 (A) 调用另一个 class 方法 (B)。所以 A 依赖于 B。我想摆脱依赖性以便能够 运行 单元测试。解耦和依赖注入在某种程度上是基于实例化的。但是 class 方法(静态方法)本质上不需要实例。 我已经使用了两个解决方案,但其中 none 对我来说似乎是 100%:
- 我们创建 class B 的实例(生产代码)或测试替身的实例(用于单元测试)。我们将它作为参数注入到被测 class 方法中。内部 class 方法在注入实例上调用,而不是在 class 上调用。
I don’t like we need to make an instance of class, although we are using class method. It can take time. It needs a bit more code.
- 我们注入实际的 class 名称作为字符串参数,我们使用动态
CALL METHOD
.
As I am not fan of interpreted languages, I consider this a mess that can bring serious runtime problems. Since we do this to implement unit tests and consequently to eliminate possible bugs; using dynamic calls seems counterproductive. Also it is painful to work with parameters.
还有其他方法可以解决吗?我错过了一些重要的点吗?
下面是两种解决方案的关键部分。没有必要了解问题,但他们可能会有所帮助。
1)
INTERFACE lif_readfile.
CLASS-METHODS gui_upload
IMPORTING file_path TYPE string
CHANGING data_tab TYPE truxs_t_text_data.
ENDINTERFACE.
CLASS lcl_file_operations DEFINITION.
PUBLIC SECTION.
CLASS-METHODS:
get_file_length
IMPORTING
!file_path TYPE string
CHANGING
!filereader TYPE REF TO lif_readfile OPTIONAL
RETURNING
VALUE(text_length) TYPE i.
ENDCLASS.
CLASS lcl_file_operations IMPLEMENTATION.
METHOD get_file_length.
*create instance of default productive class
IF filereader IS NOT BOUND.
filereader = NEW lcl_readfile( ).
ENDIF.
*use instance to call class method
filereader->gui_upload(
EXPORTING file_path = file_path
CHANGING data_tab = lt_data
).
*code under test here..
ENDMETHOD.
ENDCLASS.
2)
CLASS lcl_file_operations DEFINITION.
PUBLIC SECTION.
CLASS-METHODS:
get_file_length
IMPORTING
!file_path TYPE string
!classname TYPE string DEFAULT 'LCL_READFILE'
RETURNING
VALUE(text_length) TYPE i.
ENDCLASS.
CLASS lcl_file_operations IMPLEMENTATION.
METHOD get_file_length.
*parameter definition
ptab = VALUE #( ( name = 'FILE_PATH'
kind = cl_abap_objectdescr=>exporting
value = REF #( file_path ) )
( name = 'DATA_TAB'
kind = cl_abap_objectdescr=>changing
value = REF #( lt_data ) ) ).
DATA(meth) = 'LIF_READFILE~GUI_UPLOAD'.
*dynamic call
CALL METHOD (classname)=>(meth) PARAMETER-TABLE ptab.
*code under test here..
ENDMETHOD.
ENDCLASS.
我认为你的问题与语言无关。您是否查看了论坛中提供的多个答案,您如何看待所提出的不同方法?
您可以将静态调用包装在新接口和 classes 中,使用 instance 方法映射到右侧 class 的静态方法,静态指定。
关于使用"class name"作为变量的ABAP特有的解决方案,正如你所展示的,我个人不喜欢它,但我认为这只是个人喜好问题,并不是很重要。 PS:术语"interpreted language"是一种编程语言,相反(简单地说)是"compiled language";你说的更多是 dynamic link.
到目前为止,我发现了两个对我来说更好的解决方案。但是,如果您知道更好的解决方案;我仍然期待着尝试一下。
工厂+注入器(依赖查找)
改进方案 1 – 实例处理移至工厂。未提供工厂和喷油器的代码——这是标准解决方案。
CLASS lcl_file_operations DEFINITION.
PUBLIC SECTION.
CLASS-METHODS:
get_file_length
IMPORTING
!file_path TYPE string
RETURNING
VALUE(text_length) TYPE i.
ENDCLASS.
CLASS lcl_file_operations IMPLEMENTATION.
METHOD get_file_length.
myfactory=>get_filereader( )->gui_upload(
EXPORTING file_path = file_path
CHANGING data_tab = lt_data
).
*code under test here..
ENDMETHOD.
ENDCLASS.
优点
- 被测代码更简洁、更短。实例在别处创建和测试。
缺点
- 它仍然创建实例。
- 总共有更多代码。
使用 TEST-SEAM 和 TEST-INJECTION
CLASS zcl_file_operations IMPLEMENTATION.
METHOD get_file_length.
TEST-SEAM seam_gui_upload.
zcl_filereader=>gui_upload(
EXPORTING file_path = file_path
CHANGING data_tab = lt_data
).
END-TEST-SEAM.
*code under test here..
ENDMETHOD.
ENDCLASS.
测试方法
*...
TEST-INJECTION seam_gui_upload.
ztc_testdouble=>gui_upload(
EXPORTING file_path = file_path
CHANGING data_tab = lt_data
).
END-TEST-INJECTION.
*...
优点
- 这似乎是迄今为止最好的解决方案。
- 使用class-方法;没有创建实例。
- 总共最短的代码。
缺点
- 被社区认为是肮脏的技术(仅推荐用于遗留代码)。
- 被测代码轻微污染。
备注