ABAP 中 class 方法的依赖注入

Dependency injection to class-method in ABAP

我有一个场景,我的 class 方法 (A) 调用另一个 class 方法 (B)。所以 A 依赖于 B。我想摆脱依赖性以便能够 运行 单元测试。解耦和依赖注入在某种程度上是基于实例化的。但是 class 方法(静态方法)本质上不需要实例。 我已经使用了两个解决方案,但其中 none 对我来说似乎是 100%:

  1. 我们创建 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.

  1. 我们注入实际的 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-方法;没有创建实例。
  • 总共最短的代码。

缺点

  • 被社区认为是肮脏的技术(仅推荐用于遗留代码)。
  • 被测代码轻微污染。

备注