Show TOC

Private-Dependency-Injection für ABAP-ModultestsLocate this document in the navigation structure

Vorgehensweise

Sie können bessere Modultests erzielen, wenn Sie zur Design-Zeit oder während des Code-Refactoring das Einfügen von Abhängigkeiten in den Code erlauben. Dort wo Sie die Dependency-Injection ermöglicht haben, können Ihre ABAP-Modultests somit Testattrappen (Test-Doubles) gegen die Produktionseinheiten eintauschen.

Dieser Abschnitt zeigt eine beispielhafte Private-Injection von Abhängigkeiten in ABAP. Dieses Muster ist hilfreich, wenn Sie den Benutzern einer Klasse keine kritischen Abhängigkeiten exponieren wollen, wie es ggf. bei der Constructor-Dependency der Fall ist.

Private-Dependency-Injection

Wenn Sie eine lokale Testklasse als LOCAL FRIEND der getesteten Klasse deklarieren, kann die Testklasse auf geschützte und private Komponenten der Klasse zugriefen. Dies ist dann nützlich, wenn die Testklasse private und geschützte Methoden testen soll. Aber es ermöglicht der Testklasse auch Abhängigkeiten einzufügen, die als private Attribute der getesteten Klasse deklariert sind.

Mit einer Private-Injection durch einen LOCAL FRIEND können Sie Abhängigkeiten durchbrechen, diese aber weiterhin vor Manipulationen von außerhalb der getesteten Klasse schützen.

Der folgende Abschnitt zeigt ein Code-Beispiel für das Anlegen einer Private-Injection:

*----------------------------------*
* In CLASS_UNDER_TEST...
*----------------------------------*

*----------------------------------*
* The dependency needed by 
* CLASS_UNDER_TEST is instantiated
* in the CONSTRUCTOR method. The  
* constructor sets the private variable 
* MY_DEPENDENCY to reference the 
* depended-on object.
*----------------------------------*
CLASS class_under_test DEFINITION CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS constructor.
    METHODS method_under_test
      RETURNING
        value(useful_value) TYPE i.
  PRIVATE SECTION.
    DATA my_dependency TYPE REF TO if_my_dependency.
ENDCLASS.

CLASS class_under_test IMPLEMENTATION.
  METHOD constructor.
    CREATE OBJECT my_dependency TYPE REF TO cl_my_dependency.
  ENDMETHOD.          

  METHOD  method_under_test.
      useful_value = my_dependency->calc_value( ).
  ENDMETHOD.
ENDCLASS. 



*----------------------------------*
* In the local ABAP Unit include
* Goto -> Local Definitions/
*   Implementations -> Local Test 
*   Classes... 
*----------------------------------*

*----------------------------------*
* There is a local test double class
* that reimplements IF_MY_DEPENDENCY
* for use in testing.
*----------------------------------*
  CLASS ltd_test_double DEFINITION.
    PUBLIC SECTION.
      INTERFACES if_my_dependency.
      ...
  ENDCLASS.
  CLASS ltd_test_double IMPLEMENTATION.
    METHOD if_my_dependency~calc_value.
      r_value = 1.
    ENDMETHOD.
  ENDCLASS.

*----------------------------------*
* LTC_ABAP_UNIT_TESTS is declared as
* a LOCAL FRIEND of the class under 
* test. As a LOCAL FRIEND, the test
* class can access protected and 
* private members of CLASS_UNDER_TEST.
*----------------------------------*
CLASS ltc_abap_unit_tests DEFINITION DEFERRED.
CLASS class_under_test DEFINITION 
  LOCAL FRIENDS ltc_abap_unit_tests.


*----------------------------------*
* In LTC_ABAP_UNIT_TESTS, the test 
* method substitutes a test double for
* the production object referenced 
* by the private attribute MY_DEPENDENCY
*----------------------------------*
CLASS ltc_abap_unit_tests DEFINITION FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS
  PRIVATE SECTION.
    DATA:
      cut TYPE REF TO class_under_test.  
    METHODS: calc_value_test FOR TESTING.
ENDCLASS.       

CLASS ltc_abap_unit_tests IMPLEMENTATION.
  METHOD calc_value_test.
    DATA: useful_value TYPE i,
          test_double TYPE REF TO ltd_test_double.

    CREATE OBJECT test_double.
    CREATE OBJECT cut.

    cut->my_dependency = test_double.
    useful_value = cut->method_under_test(  ).

    cl_abap_unit_assert=>assert_equals(
      act   = useful_value
      exp   = 2 ).
  ENDMETHOD.    

            

Das Code-Beispiel funktioniert folgendermaßen.

  • Statt abhängige Objekte (Abhängigkeiten) inline im Code anzulegen, macht CLASS_UNDER_TEST in dem Beispiel folgendes:

    • Abhängigkeiten werden als PROTECTED- oder PRIVATE-Attribute erklärt.

    • Diese abhängigen Objekte werden außerhalb der Methoden, die sie eigentlich verwenden, instanziiert. In diesem Fall führt der CONSTRUCTOR die Instanziierung durch.

    Die Instanziierung von abhängigen Objekten wird vor den Benutzern von CLASS_UNDER_TEST verborgen.

  • Eine lokale Hilfsklasse - im obigen Beispiel LTD_TEST_DOUBLE- stellt eine Testversion des IF_MY_DEPENDENCY-Objekts, das von CLASS_UNDER_TEST verwendet wird, zur Verfügung.

  • In der ABAP-Modultestklasse - im obigen Beispiel LTC_ABAP_UNIT_TESTS- legt die Testmethode eine Instanz des Test-Doubles an. Sie legt auch eine Instanz von CLASS_UNDER_TEST an und ersetzt das Test-Double für das Produktivobjekt MY_DEPENDENCY zur Verwendung beim Testen.

    Alternativ können Sie diese Tasks auch in den ABAP-Unit-Methoden SETUP und TEARDOWN ausführen. Hiermit verhindern Sie, dass die Testmethode durch die Instanzierung des Test-Doubles und des Testobjekts der getesteten Komponente zu unübersichtlich wird.