ABAP Test Cockpit (ATC): Automated Code Quality Checking

Category
DevOps
Published
Author
Johannes

The ABAP Test Cockpit (ATC) is SAP’s central tool for static code analysis. It automatically checks ABAP code for errors, performance issues, security vulnerabilities, and violations of coding guidelines. In ABAP Cloud, ATC is indispensable for ensuring the quality of your code.

What is the ABAP Test Cockpit?

ATC is a framework for static code checks that combines various check categories:

ComponentDescription
ATC ChecksStatic code analysis for syntax, performance, security
Code InspectorBase framework for checks (Transaction SCI)
ABAP UnitIntegration of unit test results
Custom ChecksDefine your own check rules

Advantages of ATC

  • Early Error Detection: Find problems before transport
  • Consistent Quality: Uniform standards for the entire team
  • Security: Automatically detect security vulnerabilities
  • ABAP Cloud Compliance: Use only released APIs
  • CI/CD Integration: Automated checks in the pipeline

Running ATC in ADT

In the ABAP Development Tools (ADT), ATC is directly integrated.

Quick Check of Individual Objects

1. Open object in ADT (class, report, CDS view)
2. Right-click -> Run As -> ABAP Test Cockpit
or keyboard shortcut: Ctrl+Shift+F2
3. Results in "ATC Problems" view

ATC for Complete Packages

1. Select package in Project Explorer
2. Right-click -> Run As -> ABAP Test Cockpit With...
3. Select check variant (e.g., DEFAULT, ABAP_CLOUD)
4. Run
5. Analyze results

Practical Example: Check a Class

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: This class intentionally has ATC findings
DATA: lt_items TYPE TABLE OF i.
" ATC Finding: Variable not used
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: Avoid generic typing
LOOP AT it_items ASSIGNING FIELD-SYMBOL(<item>).
rv_total = rv_total + <item>.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Expected ATC Findings:

FINDING 1 (Priority 2):
Variable 'LV_UNUSED' is declared but not used
-> Solution: Remove variable
FINDING 2 (Priority 3):
Avoid generic typing of IT_ITEMS
-> Solution: Use concrete table type

Important Check Categories

ATC groups checks into different categories:

Syntax and Robustness

" BAD: Division without check
DATA(lv_result) = lv_total / lv_count. " ATC: Possible division by zero
" GOOD: With check
IF lv_count <> 0.
DATA(lv_result) = lv_total / lv_count.
ENDIF.

Performance

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

Security

" BAD: SQL injection possible
DATA(lv_where) = |CARRID = '{ lv_input }'|.
SELECT * FROM sflight
WHERE (lv_where) " ATC: Dynamic WHERE without escaping
INTO TABLE @DATA(lt_flights).
" GOOD: With escaping or parameters
SELECT * FROM sflight
WHERE carrid = @lv_input " Parameter-based
INTO TABLE @DATA(lt_flights).

ABAP Cloud Compliance

" BAD: Non-released API
CALL FUNCTION 'POPUP_TO_CONFIRM'. " ATC: API not released for ABAP Cloud
" GOOD: Use released API
" In RAP: Use message handler
" In Console: IF_OO_ADT_CLASSRUN for output

Naming Conventions

" ATC checks naming conventions:
" - LV_ prefix for local variables
" - LS_ prefix for local structures
" - LT_ prefix for local tables
" - IV_ prefix for importing parameters
" - RV_ prefix for returning parameters
" BAD
DATA: total TYPE i.
DATA: items TYPE TABLE OF i.
" GOOD
DATA: lv_total TYPE i.
DATA: lt_items TYPE TABLE OF i.

Priorities and Exemptions

ATC findings have priorities that indicate urgency:

PriorityMeaningAction
1Critical / ErrorMust be fixed
2Important / WarningShould be fixed
3RecommendationCan be fixed

Request Exemption

Sometimes findings are false positives or intentionally accepted:

1. In ATC Problems View: Select finding
2. Right-click -> Request Exemption
3. Enter justification:
"Legacy integration requires dynamic SQL"
4. Select approver
5. Submit Request

Exemption Workflow

Exemption process:
1. Developer requests exemption
|
2. Approver reviews justification
|
3. Approval or rejection
|
4. If approved: Finding is hidden

Exemption Validity Duration:

- Permanent: For deliberate architectural decisions
- Time-limited: For temporary workarounds (6-12 months)
- Object-specific: Applies only to affected object
- Package-wide: Applies to all objects in package

CI/CD Integration

ATC can be integrated into CI/CD pipelines to automatically check code quality.

Call ATC via API

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.
" Run ATC for a package
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.
" Check for critical findings
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( |ERROR: { lv_critical } critical findings!| ).
ELSE.
out->write( 'OK: No critical findings.' ).
ENDIF.
ENDMETHOD.
METHOD run_atc_check.
" Simplified example - conceptual representation
" The actual ATC API is more complex
" In practice:
" 1. CL_CI_OBJECTSET for object selection
" 2. CL_CI_INSPECTION for checking
" 3. CL_CI_INSPECTION->GET_RESULTS for results
" Example findings for demo
rt_findings = VALUE #(
( object_type = 'CLAS'
object_name = 'ZCL_EXAMPLE'
priority = 2
message = 'Variable not used' )
( object_type = 'CLAS'
object_name = 'ZCL_EXAMPLE'
priority = 3
message = 'Method length exceeded' )
).
ENDMETHOD.
ENDCLASS.

SAP CI/CD Service Integration

In the SAP CI/CD Service, ATC is integrated via pipeline configuration:

# .pipeline/config.yml for 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 with abaplint

For open-source projects with 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-equivalent checks failed!"
exit 1
fi

Creating Custom Checks

ATC can be extended with your own checks.

Custom Check Class

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 = 'Checks maximum method length'.
category = 'ZCL_CUSTOM_CHECKS'.
has_attributes = abap_true.
ENDMETHOD.
METHOD run.
" Implementation of check logic
" Analysis of source code for method length
" Simplified example:
" 1. Identify methods in check object
" 2. Count statements per method
" 3. Create finding if exceeded
DATA(lv_method_statements) = 75. " Example value
IF lv_method_statements > mv_max_statements.
" Create finding
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.

Register Custom Check

1. Open transaction SCI
2. Code Inspector -> Edit check variant
3. Select custom check class ZCL_ATC_CHECK_*
4. Add to check variant
5. Activate

Define Messages

Transaction SE91 - Create message class:
Message Class: ZCL_ATC_MESSAGES
Messages:
001: Method &1 has &2 statements (max: &3)
002: Class &1 has too many public methods
003: Table type &1 should be SORTED

Practical Examples

Example 1: Develop an ATC-Compliant Class

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.
" Initialization
rs_result-valid = abap_true.
" Validate customer
IF NOT validate_customer( is_order-customer ).
rs_result-valid = abap_false.
APPEND 'Customer not found or blocked' TO rs_result-messages.
ENDIF.
" Validate amount
IF NOT validate_amount(
iv_amount = is_order-amount
iv_currency = is_order-currency ).
rs_result-valid = abap_false.
APPEND 'Invalid amount or currency' TO rs_result-messages.
ENDIF.
" Validate status
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 |Invalid status: { is_order-status }| TO rs_result-messages.
ENDIF.
ENDMETHOD.
METHOD validate_customer.
" ATC-compliant: No division, no dynamic 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.
" Amount must be positive
IF iv_amount <= 0.
rv_valid = abap_false.
RETURN.
ENDIF.
" Currency must exist
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 Result: No Findings

Example 2: Fix ATC Findings

" === BEFORE: With ATC Findings ===
CLASS zcl_report_generator DEFINITION
PUBLIC FINAL.
PUBLIC SECTION.
" Finding: Missing CREATE specification
METHODS generate.
ENDCLASS.
CLASS zcl_report_generator IMPLEMENTATION.
METHOD generate.
" Finding 1: Variable not used
DATA: lv_unused TYPE string.
" Finding 2: Avoid SELECT *
SELECT * FROM sflight INTO TABLE @DATA(lt_flights).
" Finding 3: Hardcoded literal
IF lines( lt_flights ) > 100.
" Finding 4: WRITE not allowed in Cloud
WRITE 'Too many records'.
ENDIF.
ENDMETHOD.
ENDCLASS.
" === AFTER: ATC-Compliant ===
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( |Flights loaded: { lines( lt_flights ) }| ).
ENDMETHOD.
METHOD generate.
" Select only needed fields
SELECT carrid, connid, fldate, price, currency
FROM sflight
INTO CORRESPONDING FIELDS OF TABLE @rt_flights
UP TO @c_max_records ROWS.
" No WRITE - output via interface or return value
ENDMETHOD.
ENDCLASS.

Example 3: Performance Optimization after 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.
" Example call
DATA(lt_ranges) = VALUE tt_range_i(
( sign = 'I' option = 'BT' low = 1 high = 1000 )
).
DATA(lt_details) = process_orders( lt_ranges ).
out->write( |Processed orders: { lines( lt_details ) }| ).
ENDMETHOD.
METHOD process_orders.
" ATC-compliant: No SELECTs in loops
" Step 1: All orders in one 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.
" Step 2: Customer data in one 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).
" Step 3: Merge in memory
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
).
" Assign customer name
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 Variants

ATC uses check variants to define which checks are executed.

Standard Check Variants

VariantDescriptionApplication
DEFAULTBasic checksGeneral development
ABAP_CLOUDABAP Cloud ComplianceTier-1 development
ABAP_CLOUD_DEVELOPMENTStrict cloud checkingBTP/S/4HANA Public Cloud
PERFORMANCEPerformance checksOptimization
SECURITYSecurity checksSecurity reviews

Create Your Own Check Variant

1. Open transaction SCI
2. Check Variant -> Create
3. Name: ZPROJECT_CHECKS
4. Select checks:
- Syntax Check
- ABAP Cloud Readiness
- Performance (selected)
- Security (all)
- Custom Checks
5. Set priorities
6. Activate

Enforce Check Variant in Team

Package Settings:
1. Open package in SE21/ADT
2. ATC Settings
3. Check Variant: ZPROJECT_CHECKS
4. Enforcement Level:
- Warning: ATC shows findings
- Error: Transport lock on P1 findings
5. Save

Best Practices

1. Run ATC Early and Often

Development workflow:
- After every major change: Run ATC
- Before every commit: ATC without findings
- Before transport: Complete ATC run
- In CI/CD: Automatic checking

2. Handle Priorities Correctly

Priority 1 (Critical):
-> ALWAYS fix before transport
-> No exemptions except in exceptions
Priority 2 (Important):
-> Should be fixed
-> Exemption possible with good justification
Priority 3 (Recommendation):
-> Fix when convenient
-> Exemption easier to obtain

3. Define Team Standards

ATC Governance:
- Define check variant for the project
- Document exemption process
- Maximum open findings per package
- Regular technical debt reviews

4. Work Through Findings Systematically

1. Sort by priority (1 -> 2 -> 3)
2. Group by object
3. Fix similar findings together
4. After fixing: Run ATC again
5. Address new findings immediately

Conclusion

The ABAP Test Cockpit is indispensable for professional ABAP development:

  • Automated Quality Checking: Detect errors early
  • ABAP Cloud Compliance: Use only released APIs
  • Performance: Avoid runtime problems
  • Security: Find security vulnerabilities
  • CI/CD: Seamless pipeline integration

Make ATC a fixed part of your development workflow to consistently deliver high code quality.

Further Reading