ABAP Test Cockpit (ATC): Code-Qualität automatisiert prüfen

kategorie
DevOps
Veröffentlicht
autor
Johannes

Das ABAP Test Cockpit (ATC) ist SAPs zentrales Werkzeug zur statischen Code-Analyse. Es prüft ABAP-Code automatisiert auf Fehler, Performance-Probleme, Sicherheitslücken und Verstöße gegen Coding-Guidelines. In ABAP Cloud ist ATC unverzichtbar, um die Qualität deines Codes zu sichern.

Was ist das ABAP Test Cockpit?

ATC ist ein Framework für statische Code-Prüfungen, das verschiedene Check-Kategorien kombiniert:

KomponenteBeschreibung
ATC ChecksStatische Code-Analysen für Syntax, Performance, Security
Code InspectorBasis-Framework für Prüfungen (Transaktion SCI)
ABAP UnitIntegration von Unit-Test-Ergebnissen
Custom ChecksEigene Prüfregeln definieren

Vorteile von ATC

  • Frühe Fehlererkennung: Probleme vor dem Transport finden
  • Konsistente Qualität: Einheitliche Standards für das gesamte Team
  • Security: Sicherheitslücken automatisch erkennen
  • ABAP Cloud Compliance: Nur freigegebene APIs verwenden
  • CI/CD Integration: Automatisierte Prüfungen in der Pipeline

ATC in ADT ausführen

In den ABAP Development Tools (ADT) ist ATC direkt integriert.

Quick Check einzelner Objekte

1. Objekt in ADT öffnen (Klasse, Report, CDS View)
2. Rechtsklick → Run As → ABAP Test Cockpit
oder Tastenkürzel: Ctrl+Shift+F2
3. Ergebnisse im "ATC Problems" View

ATC für komplette Packages

1. Package im Project Explorer auswählen
2. Rechtsklick → Run As → ABAP Test Cockpit With...
3. Check Variant auswählen (z.B. DEFAULT, ABAP_CLOUD)
4. Run
5. Ergebnisse analysieren

Praktisches Beispiel: Klasse prüfen

CLASS zcl_atc_demo DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
METHODS calculate_total
IMPORTING
it_items TYPE STANDARD TABLE
RETURNING
VALUE(rv_total) TYPE p.
ENDCLASS.
CLASS zcl_atc_demo IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Demonstration: Diese Klasse hat absichtlich ATC-Findings
DATA: lt_items TYPE TABLE OF i.
" ATC Finding: Variable nicht verwendet
DATA(lv_unused) = 'Hello'.
lt_items = VALUE #( ( 10 ) ( 20 ) ( 30 ) ).
DATA(lv_total) = calculate_total( lt_items ).
out->write( |Total: { lv_total }| ).
ENDMETHOD.
METHOD calculate_total.
" ATC Finding: Generic typing vermeiden
LOOP AT it_items ASSIGNING FIELD-SYMBOL(<item>).
rv_total = rv_total + <item>.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Erwartete ATC-Findings:

FINDING 1 (Priority 2):
Variable 'LV_UNUSED' wird deklariert aber nicht verwendet
→ Lösung: Variable entfernen
FINDING 2 (Priority 3):
Generische Typisierung von IT_ITEMS vermeiden
→ Lösung: Konkreten Tabellentyp verwenden

Wichtige Check-Kategorien

ATC gruppiert Prüfungen in verschiedene Kategorien:

Syntax und Robustheit

" SCHLECHT: Division ohne Prüfung
DATA(lv_result) = lv_total / lv_count. " ATC: Mögliche Division durch Null
" GUT: Mit Prüfung
IF lv_count <> 0.
DATA(lv_result) = lv_total / lv_count.
ENDIF.

Performance

" SCHLECHT: SELECT in Schleife
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<order>).
SELECT SINGLE * FROM vbak " ATC: SELECT in Schleife (N+1)
WHERE vbeln = @<order>-vbeln
INTO @DATA(ls_header).
ENDLOOP.
" GUT: Bulk SELECT
SELECT * FROM vbak
FOR ALL ENTRIES IN @lt_orders
WHERE vbeln = @lt_orders-vbeln
INTO TABLE @DATA(lt_headers).

Security

" SCHLECHT: SQL Injection möglich
DATA(lv_where) = |CARRID = '{ lv_input }'|.
SELECT * FROM sflight
WHERE (lv_where) " ATC: Dynamisches WHERE ohne Escaping
INTO TABLE @DATA(lt_flights).
" GUT: Mit Escaping oder Parametern
SELECT * FROM sflight
WHERE carrid = @lv_input " Parameter-basiert
INTO TABLE @DATA(lt_flights).

ABAP Cloud Compliance

" SCHLECHT: Nicht freigegebene API
CALL FUNCTION 'POPUP_TO_CONFIRM'. " ATC: API nicht für ABAP Cloud freigegeben
" GUT: Freigegebene API verwenden
" In RAP: Message Handler nutzen
" In Console: IF_OO_ADT_CLASSRUN für Ausgaben

Namenskonventionen

" ATC prüft Namenskonventionen:
" - LV_ Prefix für lokale Variablen
" - LS_ Prefix für lokale Strukturen
" - LT_ Prefix für lokale Tabellen
" - IV_ Prefix für Importing-Parameter
" - RV_ Prefix für Returning-Parameter
" SCHLECHT
DATA: total TYPE i.
DATA: items TYPE TABLE OF i.
" GUT
DATA: lv_total TYPE i.
DATA: lt_items TYPE TABLE OF i.

Prioritäten und Exemptions

ATC-Findings haben Prioritäten, die die Dringlichkeit anzeigen:

PrioritätBedeutungHandlung
1Kritisch / ErrorMuss behoben werden
2Wichtig / WarningSollte behoben werden
3EmpfehlungKann behoben werden

Exemption beantragen

Manchmal sind Findings False Positives oder bewusst akzeptiert:

1. Im ATC Problems View: Finding auswählen
2. Rechtsklick → Request Exemption
3. Begründung eingeben:
"Legacy-Integration erfordert dynamisches SQL"
4. Genehmiger auswählen
5. Submit Request

Exemption-Workflow

Exemption-Prozess:
1. Entwickler beantragt Exemption
2. Genehmiger prüft Begründung
3. Genehmigung oder Ablehnung
4. Bei Genehmigung: Finding wird ausgeblendet

Exemption-Gültigkeitsdauer:

- Permanent: Für bewusste Architekturentscheidungen
- Zeitlich begrenzt: Für temporäre Workarounds (6-12 Monate)
- Objektspezifisch: Gilt nur für das betroffene Objekt
- Paketübergreifend: Gilt für alle Objekte im Package

CI/CD Integration

ATC lässt sich in CI/CD-Pipelines integrieren, um Code-Qualität automatisiert zu prüfen.

ATC über API aufrufen

CLASS zcl_atc_runner DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES:
BEGIN OF ty_finding,
object_type TYPE trobjtype,
object_name TYPE sobj_name,
priority TYPE i,
message TYPE string,
END OF ty_finding,
tt_findings TYPE STANDARD TABLE OF ty_finding WITH EMPTY KEY.
METHODS run_atc_check
IMPORTING
iv_package TYPE devclass
RETURNING
VALUE(rt_findings) TYPE tt_findings.
ENDCLASS.
CLASS zcl_atc_runner IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" ATC für ein Package ausführen
DATA(lt_findings) = run_atc_check( 'ZRAP_DEMO' ).
out->write( |ATC Findings: { lines( lt_findings ) }| ).
LOOP AT lt_findings INTO DATA(ls_finding).
out->write( |{ ls_finding-priority }: { ls_finding-object_name } - { ls_finding-message }| ).
ENDLOOP.
" Auf kritische Findings prüfen
DATA(lv_critical) = REDUCE i(
INIT count = 0
FOR finding IN lt_findings
WHERE ( priority <= 2 )
NEXT count = count + 1
).
IF lv_critical > 0.
out->write( |FEHLER: { lv_critical } kritische Findings!| ).
ELSE.
out->write( 'OK: Keine kritischen Findings.' ).
ENDIF.
ENDMETHOD.
METHOD run_atc_check.
" Vereinfachtes Beispiel - konzeptuelle Darstellung
" Die tatsächliche ATC-API ist komplexer
" In der Praxis:
" 1. CL_CI_OBJECTSET für Objektauswahl
" 2. CL_CI_INSPECTION für Prüfung
" 3. CL_CI_INSPECTION->GET_RESULTS für Ergebnisse
" Beispiel-Findings für Demo
rt_findings = VALUE #(
( object_type = 'CLAS'
object_name = 'ZCL_EXAMPLE'
priority = 2
message = 'Variable nicht verwendet' )
( object_type = 'CLAS'
object_name = 'ZCL_EXAMPLE'
priority = 3
message = 'Methodenlänge überschritten' )
).
ENDMETHOD.
ENDCLASS.

SAP CI/CD Service Integration

Im SAP CI/CD Service wird ATC über die Pipeline-Konfiguration eingebunden:

# .pipeline/config.yml für SAP CI/CD Service
stages:
- name: Build
steps:
- name: abapBuild
type: abapEnvironmentBuild
- name: ATC
steps:
- name: abapEnvironmentRunATCCheck
type: abapEnvironmentRunATCCheck
config:
atcCheckVariant: 'ABAP_CLOUD_DEVELOPMENT'
atcConfiguration: '/DEFAULT'
failOnSeverity: 'error'

GitHub Actions mit abaplint

Für Open-Source-Projekte mit abapGit:

.github/workflows/atc.yml
name: Code Quality
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
abaplint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install abaplint
run: npm install -g @abaplint/cli
- name: Run abaplint
run: abaplint
- name: Check results
run: |
if [ $? -ne 0 ]; then
echo "ATC-äquivalente Prüfungen fehlgeschlagen!"
exit 1
fi

Custom Checks erstellen

ATC kann durch eigene Prüfungen erweitert werden.

Custom Check Klasse

CLASS zcl_atc_check_method_length DEFINITION
PUBLIC FINAL
CREATE PUBLIC
INHERITING FROM cl_ci_test_root.
PUBLIC SECTION.
METHODS constructor.
METHODS run REDEFINITION.
METHODS get_attributes REDEFINITION.
METHODS put_attributes REDEFINITION.
PRIVATE SECTION.
DATA mv_max_statements TYPE i VALUE 50.
CONSTANTS c_msg_id TYPE scimessage VALUE 'ZCL_ATC_001'.
ENDCLASS.
CLASS zcl_atc_check_method_length IMPLEMENTATION.
METHOD constructor.
super->constructor( ).
description = 'Prüft maximale Methodenlänge'.
category = 'ZCL_CUSTOM_CHECKS'.
has_attributes = abap_true.
ENDMETHOD.
METHOD run.
" Implementierung der Prüflogik
" Analyse des Quellcodes auf Methodenlänge
" Vereinfachtes Beispiel:
" 1. Methoden im Prüfobjekt identifizieren
" 2. Anzahl Statements pro Methode zählen
" 3. Bei Überschreitung: Finding erzeugen
DATA(lv_method_statements) = 75. " Beispielwert
IF lv_method_statements > mv_max_statements.
" Finding erstellen
inform(
p_sub_obj_name = 'DO_SOMETHING'
p_kind = c_error
p_test = me->myname
p_code = c_msg_id
p_param_1 = |{ lv_method_statements }|
p_param_2 = |{ mv_max_statements }|
).
ENDIF.
ENDMETHOD.
METHOD get_attributes.
EXPORT max_statements = mv_max_statements TO DATA BUFFER p_attributes.
ENDMETHOD.
METHOD put_attributes.
IMPORT max_statements = mv_max_statements FROM DATA BUFFER p_attributes.
ENDMETHOD.
ENDCLASS.

Custom Check registrieren

1. Transaktion SCI öffnen
2. Code Inspector → Check Variant bearbeiten
3. Eigene Check-Klasse ZCL_ATC_CHECK_* auswählen
4. In Check Variant aufnehmen
5. Aktivieren

Nachrichten definieren

Transaktion SE91 - Nachrichtenklasse anlegen:
Nachrichtenklasse: ZCL_ATC_MESSAGES
Nachrichten:
001: Methode &1 hat &2 Statements (max: &3)
002: Klasse &1 hat zu viele öffentliche Methoden
003: Tabellentyp &1 sollte SORTED sein

Praktische Beispiele

Beispiel 1: ATC-konforme Klasse entwickeln

CLASS zcl_order_validator DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_order,
order_id TYPE i,
customer TYPE i,
amount TYPE p LENGTH 15 DECIMALS 2,
currency TYPE waers,
status TYPE char1,
END OF ty_order.
TYPES:
BEGIN OF ty_validation_result,
valid TYPE abap_bool,
messages TYPE string_table,
END OF ty_validation_result.
METHODS validate_order
IMPORTING
is_order TYPE ty_order
RETURNING
VALUE(rs_result) TYPE ty_validation_result.
PRIVATE SECTION.
METHODS validate_customer
IMPORTING
iv_customer TYPE i
RETURNING
VALUE(rv_valid) TYPE abap_bool.
METHODS validate_amount
IMPORTING
iv_amount TYPE p
iv_currency TYPE waers
RETURNING
VALUE(rv_valid) TYPE abap_bool.
ENDCLASS.
CLASS zcl_order_validator IMPLEMENTATION.
METHOD validate_order.
" Initialisierung
rs_result-valid = abap_true.
" Customer validieren
IF NOT validate_customer( is_order-customer ).
rs_result-valid = abap_false.
APPEND 'Kunde nicht gefunden oder gesperrt' TO rs_result-messages.
ENDIF.
" Betrag validieren
IF NOT validate_amount(
iv_amount = is_order-amount
iv_currency = is_order-currency ).
rs_result-valid = abap_false.
APPEND 'Ungültiger Betrag oder Währung' TO rs_result-messages.
ENDIF.
" Status validieren
IF is_order-status NOT IN VALUE #( ( sign = 'I' option = 'EQ' low = 'N' )
( sign = 'I' option = 'EQ' low = 'A' ) ).
rs_result-valid = abap_false.
APPEND |Ungültiger Status: { is_order-status }| TO rs_result-messages.
ENDIF.
ENDMETHOD.
METHOD validate_customer.
" ATC-konform: Keine Division, keine dynamischen Statements
SELECT SINGLE @abap_true
FROM scustom
WHERE id = @iv_customer
INTO @rv_valid.
IF sy-subrc <> 0.
rv_valid = abap_false.
ENDIF.
ENDMETHOD.
METHOD validate_amount.
" Betrag muss positiv sein
IF iv_amount <= 0.
rv_valid = abap_false.
RETURN.
ENDIF.
" Währung muss existieren
SELECT SINGLE @abap_true
FROM tcurc
WHERE waers = @iv_currency
INTO @rv_valid.
IF sy-subrc <> 0.
rv_valid = abap_false.
ENDIF.
ENDMETHOD.
ENDCLASS.

ATC-Ergebnis: Keine Findings

Beispiel 2: ATC-Findings beheben

" === VORHER: Mit ATC-Findings ===
CLASS zcl_report_generator DEFINITION
PUBLIC FINAL.
PUBLIC SECTION.
" Finding: Fehlende CREATE-Angabe
METHODS generate.
ENDCLASS.
CLASS zcl_report_generator IMPLEMENTATION.
METHOD generate.
" Finding 1: Variable nicht verwendet
DATA: lv_unused TYPE string.
" Finding 2: SELECT * vermeiden
SELECT * FROM sflight INTO TABLE @DATA(lt_flights).
" Finding 3: Hardcoded Literal
IF lines( lt_flights ) > 100.
" Finding 4: WRITE in Cloud nicht erlaubt
WRITE 'Zu viele Datensätze'.
ENDIF.
ENDMETHOD.
ENDCLASS.
" === NACHHER: ATC-konform ===
CLASS zcl_report_generator DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
CONSTANTS c_max_records TYPE i VALUE 100.
METHODS generate
RETURNING
VALUE(rt_flights) TYPE /dmo/t_flight.
ENDCLASS.
CLASS zcl_report_generator IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_flights) = generate( ).
out->write( |Flüge geladen: { lines( lt_flights ) }| ).
ENDMETHOD.
METHOD generate.
" Nur benötigte Felder selektieren
SELECT carrid, connid, fldate, price, currency
FROM sflight
INTO CORRESPONDING FIELDS OF TABLE @rt_flights
UP TO @c_max_records ROWS.
" Kein WRITE - Ausgabe über Interface oder Rückgabewert
ENDMETHOD.
ENDCLASS.

Beispiel 3: Performance-Optimierung nach ATC

CLASS zcl_order_processor DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES:
BEGIN OF ty_order_detail,
order_id TYPE i,
customer TYPE i,
customer_name TYPE string,
total TYPE p LENGTH 15 DECIMALS 2,
END OF ty_order_detail,
tt_order_details TYPE STANDARD TABLE OF ty_order_detail WITH KEY order_id.
METHODS process_orders
IMPORTING
it_order_ids TYPE tt_range_i
RETURNING
VALUE(rt_details) TYPE tt_order_details.
ENDCLASS.
CLASS zcl_order_processor IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Beispielaufruf
DATA(lt_ranges) = VALUE tt_range_i(
( sign = 'I' option = 'BT' low = 1 high = 1000 )
).
DATA(lt_details) = process_orders( lt_ranges ).
out->write( |Verarbeitete Aufträge: { lines( lt_details ) }| ).
ENDMETHOD.
METHOD process_orders.
" ATC-konform: Keine SELECTs in Schleifen
" Schritt 1: Alle Orders in einem SELECT
SELECT order_id, customer, total
FROM zorders
WHERE order_id IN @it_order_ids
INTO TABLE @DATA(lt_orders).
IF lt_orders IS INITIAL.
RETURN.
ENDIF.
" Schritt 2: Kundendaten in einem SELECT
DATA(lt_customer_ids) = VALUE tt_range_i(
FOR order IN lt_orders
( sign = 'I' option = 'EQ' low = CONV #( order-customer ) )
).
SELECT id, name
FROM scustom
WHERE id IN @lt_customer_ids
INTO TABLE @DATA(lt_customers).
" Schritt 3: Zusammenführen im Speicher
LOOP AT lt_orders INTO DATA(ls_order).
DATA(ls_detail) = VALUE ty_order_detail(
order_id = ls_order-order_id
customer = ls_order-customer
total = ls_order-total
).
" Kundennamen zuordnen
READ TABLE lt_customers
WITH KEY id = ls_order-customer
INTO DATA(ls_customer).
IF sy-subrc = 0.
ls_detail-customer_name = ls_customer-name.
ENDIF.
APPEND ls_detail TO rt_details.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Check Varianten

ATC verwendet Check Varianten, um zu definieren, welche Prüfungen ausgeführt werden.

Standard Check Varianten

VarianteBeschreibungAnwendung
DEFAULTBasis-PrüfungenEntwicklung allgemein
ABAP_CLOUDABAP Cloud ComplianceTier-1 Entwicklung
ABAP_CLOUD_DEVELOPMENTStrenge Cloud-PrüfungBTP/S/4HANA Public Cloud
PERFORMANCEPerformance-ChecksOptimierung
SECURITYSicherheitsprüfungenSecurity Reviews

Eigene Check Variante erstellen

1. Transaktion SCI öffnen
2. Check Variant → Create
3. Name: ZPROJECT_CHECKS
4. Prüfungen auswählen:
✓ Syntax Check
✓ ABAP Cloud Readiness
✓ Performance (ausgewählte)
✓ Security (alle)
✓ Custom Checks
5. Prioritäten festlegen
6. Aktivieren

Check Variante im Team durchsetzen

Package-Einstellungen:
1. Package in SE21/ADT öffnen
2. ATC-Einstellungen
3. Check Variante: ZPROJECT_CHECKS
4. Enforcement Level:
- Warning: ATC zeigt Findings
- Error: Transport-Sperre bei Findings P1
5. Speichern

Best Practices

1. ATC früh und oft ausführen

Entwicklungsworkflow:
✓ Nach jeder größeren Änderung: ATC ausführen
✓ Vor jedem Commit: ATC ohne Findings
✓ Vor Transport: Vollständiger ATC-Lauf
✓ In CI/CD: Automatische Prüfung

2. Prioritäten richtig behandeln

Priorität 1 (Critical):
→ IMMER beheben vor Transport
→ Keine Exemptions außer in Ausnahmen
Priorität 2 (Important):
→ Sollte behoben werden
→ Exemption mit guter Begründung möglich
Priorität 3 (Recommendation):
→ Bei Gelegenheit beheben
→ Exemption einfacher zu bekommen

3. Team-Standards definieren

ATC-Governance:
- Check Variante für das Projekt festlegen
- Exemption-Prozess dokumentieren
- Maximale offene Findings pro Package
- Regelmäßige Technical-Debt-Reviews

4. Findings systematisch abarbeiten

1. Nach Priorität sortieren (1 → 2 → 3)
2. Nach Objekt gruppieren
3. Ähnliche Findings zusammen beheben
4. Nach Behebung: ATC erneut ausführen
5. Neue Findings sofort bearbeiten

Fazit

Das ABAP Test Cockpit ist unverzichtbar für professionelle ABAP-Entwicklung:

  • Automatisierte Qualitätsprüfung: Fehler früh erkennen
  • ABAP Cloud Compliance: Nur freigegebene APIs verwenden
  • Performance: Laufzeitprobleme vermeiden
  • Security: Sicherheitslücken finden
  • CI/CD: Nahtlose Pipeline-Integration

Mache ATC zu einem festen Bestandteil deines Entwicklungsworkflows, um konsistent hohe Code-Qualität zu liefern.

Weiterführende Artikel