Eigene Prüfungen implementieren

Nun wollen wir eine eigene Prüfung in den Code Inspector einbauen. Hierfür müssen wir eine der beiden Klasse CL_CI_TEST_ROOT_TEMPLATE oder CL_CI_TEST_SCAN_TEMPLATE kopieren und entsprechend ausprogrammieren.

Wozu eigentlich eigene Prüfungen?

Vielleicht fragen Sie sich die ganze Zeit, wozu Sie überhaupt eigene Prüfungen benötigen sollten, da ja bereits viele wichtige Checks im Standard vorhanden sind…

Erstens ist es immer gut zu wissen, dass es überhaupt geht. Zweitens gibt es eine Vielzahl von Prüfungen, die man vornehmen könnte:

  • Verweist die Änderung im Programmkopf auf eine gültige Ticket-Nummer?
  • Wurde beim Update bestimmter kundeneigener Tabellen darauf geachtet auch eine zweite zwingend notwendige Tabelle anzupassen?
  • Wurde ein Update auf eine Tabelle vorgenommen, für die nur ein bestimmter Funktionsbaustein verwendet werden soll?
  • Wurde eine Tabelle gelesen, die besser mit einem Methode gelesen werden sollte, da bei dieser Tabelle in der Regel immer auch Daten nachgelesen werden müssen, deren Beschaffung sehr performance-kritisch ist?
  • Wurde eine Programmänderung in einem anderen externen Tool ebenfalls dokumentiert?
  • Wird eine Standard-SAP-Klasse verwendet, für die es jedoch eine kundeneigene vererbte Klasse gibt, die wichtige Sonderfunktionen enthält?

Die Liste ließe sich noch weiter führen. Jedes Unternehmen hat spezifische Anforderungen, deren Einhaltung durch automatische Tests gut geprüft werden kann.

Aufgabe

Kommen wir nun zu des Pudels Kern oder wie das so schön heißt: Wir legen eine eigene Prüfung an. Da der Code Inspector ja helfen soll, die Programmqualität zu verbessern und den Anwender auf Fehler hinzuweisen, wollen wir eine Prüfung implementieren, die den Programmierer

  1. auf nicht zu verwendende BAPIs (da veraltet) hinweist und
  2. überprüft, ob nach einem BAPI auch ein BAPI_TRANSACTION_COMMIT erfolgt.

Erstere Prüfung kann wichtig sein, da bestimmte Funktionen in einigen BAPIs vielleicht nicht funktionieren oder diese BAPIs nicht mehr gewartet werden.

Zweitere Prüfung soll helfen, einen weit verbreiteten Anfängerfehler zu vermeiden: Das fehlende explizite COMMIT bei einem BAPI, der Änderungen ausführt.

Klasse anlegen

Als erstes kopieren wir die Klasse CL_CI_TEST_SCAN_TEMPLATE auf Z_CL_CI_TEST_SCAN_TT1.

Diese Klasse erbt von CL_CI_TEST_SCAN. Die folgenden drei Methoden sind bereits vorhanden und müssen angepasst (redefiniert) werden:

Methode CONSTRUCTOR

Zuerst passen wir den Konstruktor an. Hier wird definiert, wie der Test heißt, und welcher Kategorie er zugeordnet ist:

  super->constructor( ).
  description    = ‚Tricktresor BAPI‘.         „required
  category       = ‚Z_CL_CI_CATEGORY_TT‘.      „required
  version        = ‚000‘.                      „required
  has_attributes = ‚ ‚.                        „optional
  attributes_ok  = ‚ ‚.                        „optional
  myname         = ‚Z_CL_CI_TEST_SCAN_TT1‘.    „own name

Nachdem die Klasse aktiviert wurde und in Transaktion SCI in der Test-Verwaltung aktiviert wurde, ist die neue Prüfung bereits in der Prüfvariante sichtbar:

Damit die Prüfung allerdings auch funktioniert, müssen wir natürlich noch etwas mehr unternehmen…

Methode RUN

Hier erfolgt die eigentlich Prüfung anhand des Quelltextes. Es stehen einerseits der Quelltext als auch die Aufsplittung des Quelltextes in TOKENS zur Verfügung.

Sollte der Check der allererste in der Liste der auszuführenden Prüfungen sein, so muss erst einmal die Referenz auf das Prüfobjekt geholt werden:

  IF ref_scan IS INITIAL.
    CHECK get( ) = ‚X‘.
  ENDIF.

Nun nehmen wir den Quellcode in die Mangel und suchen veraltete BAPIs… Zuerst die Datendeklaration:

*** DATA
  FIELD-SYMBOLS <token> TYPE stokesx.
  DATA ls_token1        TYPE stokesx.
  DATA ls_token2        TYPE stokesx.
  DATA lv_tabix1        TYPE i.
  DATA lv_tabix2        TYPE i.
  DATA lv_level         TYPE i.
  DATA lv_include       TYPE program.
  DATA l_errcnt         TYPE sci_errcnt.
  DATA lv_objname       TYPE c LENGTH 50.
  DATA lv_bapi_used     TYPE c.
  DATA lv_bapi_commit   TYPE c.

Beschreibung

Nun kommt das Coding, in dem wir zuerst nach dem Vorkommen von „CALL FUNCTION“ und dann nach dem verwendeten Funktionsbaustein suchen. Wenn es sich bei dem Funktionsbaustein um enen BAPI handelt (Funktionsbaustein beginnt mit „BAPI“), dann prüfen wir, ob der Baustein irgend etwas mit Datenänderungen zu tun hat. Das tun wir ganz trivial indem wir den Namen nach „CREATE“ oder „CHANGE“ durchsuchen.

Desweiteren merken wir uns die Verwendung des Funktionsbausteins „BAPI_TRANSACTION_COMMIT“ mit dem Änderungen mit BAPIs zwingend bestätigt werden müssen.

Eine direkte Programmfolge von BAPI und COMMIT prüfen wir nicht, da sie zu umfangreich wäre. Unsere Prüfung würde also nicht erkennen, dass nach BAPI_CHANGE_1 zwar einen COMMIT abgesetzt wird, aber nach BAPI_CREATE_2 keiner. Wird prüfen nur, ob überhaupt ein BAPI_TRANSACTION_COMMIT im Programm vorhanden ist.

ANMERKUNG:
Die Prüfungen erfolgen hier nun explizit auf einen BAPI, der im Programm hart codiert ist. Im „wahren Leben“ wäre eine Tabelle, in der die zu prüfenden BAPIs aufgeführt werden, sicherlich sinnvoller.

Coding

*** seach for „CALLS“
  LOOP AT ref_scan->tokens ASSIGNING <token>
    WHERE type = ‚I‘
      AND str = ‚CALL‘.
*** Check next token
    lv_tabix1 = sy-tabix + 1.
    READ TABLE ref_scan->tokens INTO ls_token1 INDEX lv_tabix1.
    IF sy-subrc = 0 AND ls_token1-str = ‚FUNCTION‘.
*** Command is CALL FUNCTION…
      lv_tabix2 = sy-tabix + 1.
      READ TABLE ref_scan->tokens INTO ls_token2 INDEX lv_tabix2.
      IF sy-subrc = 0.
*** Get call level and current include
        LOOP AT ref_scan->statements INTO statement_wa
          WHERE from <= ls_token2-row AND to >= ls_token2-row.
          lv_level = statement_wa-level.
        ENDLOOP.
        lv_include = get_include( p_level = lv_level ).
*** transform name of function module as it is stored with
*** leading and ending inverted comma
        lv_objname = ls_token2-str.
        TRANSLATE lv_objname USING “‘ ‚.
        SHIFT lv_objname LEFT DELETING LEADING space.
*** called BAPI?
        IF lv_objname(4) = ‚BAPI‘.
*** Does the BAPI make changes?
          IF lv_objname CS ‚CREATE‘ OR
             lv_objname CS ‚CHANGE‘.
*** => yes: (=> Commit needed!!)
            lv_bapi_used = ‚X‘.
          ENDIF.
*** Check for several BAPIS
          CASE lv_objname.
            WHEN ‚BAPI_SALESORDER_CREATEFROMDATA‘.
*** send message
              inform( p_sub_obj_type = ‚PROG‘
                      p_sub_obj_name  = lv_include
                      p_line     = ls_token2-row
                      p_column   = ls_token2-col
                      p_errcnt   = l_errcnt
                      p_kind     = c_warning
                      p_test     = myname
                      p_code     = ‚0001‘
                      p_param_1  = ls_token2-str
                      p_param_2  = space
                      p_param_3  = space
                      p_param_4  = space ).
            WHEN ‚BAPI_TRANSACTION_COMMIT‘.
*** Commit coded
              lv_bapi_commit = ‚X‘.
          ENDCASE. „BAPI name
        ENDIF.     „BAPI?
      ENDIF.       „name of function module found
    ENDIF.         „Call Function found
  ENDLOOP.         „token „CALL“

*** If a BAPI is used that creates or changes an
*** object but no Commit-Statement found: Inform user
  IF lv_bapi_used <> space AND lv_bapi_commit = space.
    inform( p_sub_obj_type = ‚PROG‘
            p_sub_obj_name  = trdir-name
            p_line     = 1
            p_column   = 1
            p_errcnt   = l_errcnt
            p_kind     = c_warning
            p_test     = myname
            p_code     = ‚0002‘
            p_param_1  = ‚BAPI_TRANSACTION_COMMIT‘
            p_param_2  = space
            p_param_3  = space
            p_param_4  = space ).
  ENDIF.

Auf die Verwendung der Methode „INFORM“ gehen wir im nächsten Artikel „Fehlermeldungen“ näher ein.

INFO:
In der Variablen REF_INCLUDE steht der aktuelle Quellcode!

Methode GET_MESSAGE_TEXT

Auf diese Methode gehen wir auf der nächsten Seite „Fehlermeldungen“ genauer ein.

Series Navigation<< Eigener HierarchieknotenAttribute >>
image_pdfimage_print
Enno Wulff
follow me
Letzte Artikel von Enno Wulff (Alle anzeigen)

Leave a Comment

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.