CI/CD mit ABAP Cloud

kategorie
DevOps
Veröffentlicht
autor
Johannes

CI/CD (Continuous Integration / Continuous Delivery) ermöglicht automatisierte Tests, Code-Qualitätsprüfungen und Deployments für ABAP Cloud. Mit den richtigen Tools und Pipelines wird ABAP-Entwicklung genauso agil wie moderne Cloud-Entwicklung.

CI/CD Konzept für ABAP

Continuous Integration und Continuous Delivery bringen bewährte DevOps-Praktiken in die ABAP-Welt:

AspektOhne CI/CDMit CI/CD
TestsManuell, oft vergessenAutomatisch bei jedem Commit
Code-QualitätSporadische ATC-ChecksKontinuierliche Prüfung
DeploymentManueller TransportAutomatisiert und reproduzierbar
FeedbackVerzögert, nach TagenSofort, in Minuten
DokumentationOft veraltetLiving Documentation
RisikoGroße Releases, hohes RisikoKleine Änderungen, geringes Risiko
RollbackKomplex, manuellEinfach, automatisiert

CI/CD Pipeline Übersicht

┌─────────────────────────────────────────────────────────────────────────┐
│ CI/CD Pipeline für ABAP Cloud │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Code │ │ Build │ │ Test │ │ Quality │ │ Deploy │ │
│ │ Push │──▶│ Check │──▶│ Stage │──▶│ Gate │──▶│ Stage │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │ │ │
│ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ │
│ Git Push Syntax & ABAP Unit ATC Checks gCTS Pull │
│ Trigger Activation Tests Code Review Transport │
│ Integration Coverage Release │
│ Tests Security │
│ │
└─────────────────────────────────────────────────────────────────────────┘

SAP CI/CD Service

Der SAP Continuous Integration and Delivery Service ist ein vollständig verwalteter BTP-Service, der speziell für SAP-Entwicklung optimiert ist.

Service-Übersicht

┌────────────────────────────────────────────────────────────────┐
│ SAP CI/CD Service │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Pipeline │ │ Jobs & │ │ Artifact │ │
│ │ Editor │ │ Stages │ │ Storage │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Credentials │ │ Webhooks │ │ Logs & │ │
│ │ Manager │ │ & Trigger │ │ Reports │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ Unterstützte Szenarien: │
│ • SAP Fiori in SAP BTP Cloud Foundry │
│ • SAP Cloud Application Programming Model (CAP) │
│ • ABAP Environment (SAP BTP) │
│ • gCTS Integration │
│ │
└────────────────────────────────────────────────────────────────┘

Pipeline-Konfiguration

Der SAP CI/CD Service nutzt eine YAML-basierte Konfiguration:

.pipeline/config.yml
general:
buildTool: "mta"
stages:
Build:
mtaBuild: true
Additional Unit Tests:
karmaExecuteTests: false
Integration:
cloudFoundryDeploy: false
Acceptance:
cloudFoundryDeploy: false
Release:
cloudFoundryDeploy: true
cfApiEndpoint: "https://api.cf.eu10.hana.ondemand.com"
cfOrg: "my-org"
cfSpace: "prod"
cfCredentialsId: "cf-credentials"

ABAP Environment Pipeline

Für ABAP Environment (Steampunk) gibt es spezielle Pipeline-Templates:

# .pipeline/config.yml für ABAP Environment
general:
buildTool: "abap"
abapSystemUrl: "https://my-system.abap.eu10.hana.ondemand.com"
stages:
ABAP Unit Tests:
abapEnvironmentRunATCCheck: true
atcVariant: "DEFAULT"
abapEnvironmentRunAUnitTest: true
aUnitResultsFileName: "AUnitResults.xml"
ATC Check:
abapEnvironmentRunATCCheck: true
atcVariant: "Z_CI_VARIANT"
atcResultsFileName: "ATCResults.xml"
Build:
abapEnvironmentBuild: true
Deploy:
abapEnvironmentRelease: true
cfApiEndpoint: "https://api.cf.eu10.hana.ondemand.com"
cfOrg: "my-org"
cfSpace: "dev"

Credentials konfigurieren

Im SAP CI/CD Service werden Credentials sicher verwaltet:

┌────────────────────────────────────────────────────────────────┐
│ Credentials Store │
├────────────────────────────────────────────────────────────────┤
│ │
│ Name: abap-credentials │
│ Type: Basic Authentication │
│ Username: CICD_USER │
│ Password: ******** │
│ │
│ Name: cf-credentials │
│ Type: Cloud Foundry │
│ API Endpoint: https://api.cf.eu10.hana.ondemand.com │
│ Org: my-organization │
│ Space: dev │
│ │
│ Name: git-credentials │
│ Type: SSH Key │
│ Private Key: ******** │
│ │
└────────────────────────────────────────────────────────────────┘

GitHub Actions für ABAP

GitHub Actions bietet maximale Flexibilität für ABAP CI/CD. Hier eine vollständige Pipeline:

Basis-Pipeline

.github/workflows/abap-ci.yml
name: ABAP CI/CD Pipeline
on:
push:
branches: [ main, develop, 'release/**' ]
pull_request:
branches: [ main, develop ]
workflow_dispatch:
inputs:
deploy_target:
description: 'Deploy to environment'
required: true
default: 'dev'
type: choice
options:
- dev
- qas
- prd
env:
ABAP_PACKAGE: ${{ vars.ABAP_PACKAGE }}
ATC_VARIANT: ${{ vars.ATC_VARIANT || 'DEFAULT' }}
jobs:
# Job 1: Syntax Check und Aktivierung
build:
name: Build & Syntax Check
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install ABAP Tools
run: npm install -g @sap/abap-deploy
- name: Verify ABAP Syntax
run: |
abap-deploy check \
--endpoint "${{ secrets.ABAP_ENDPOINT }}" \
--user "${{ secrets.ABAP_USER }}" \
--password "${{ secrets.ABAP_PASSWORD }}" \
--package "${{ env.ABAP_PACKAGE }}"
# Job 2: ABAP Unit Tests
unit-tests:
name: ABAP Unit Tests
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Run ABAP Unit Tests
id: aunit
run: |
curl -X POST \
"${{ secrets.ABAP_ENDPOINT }}/sap/bc/adt/abapunit/testruns" \
-u "${{ secrets.ABAP_USER }}:${{ secrets.ABAP_PASSWORD }}" \
-H "Content-Type: application/vnd.sap.adt.abapunit.testruns.config.v4+xml" \
-H "Accept: application/vnd.sap.adt.abapunit.testruns.result.v1+xml" \
-d @- << 'EOF' > aunit_results.xml
<?xml version="1.0" encoding="UTF-8"?>
<aunit:runConfiguration xmlns:aunit="http://www.sap.com/adt/aunit">
<external>
<coverage active="true" branchCoverage="true"/>
</external>
<options>
<uriType value="semantic"/>
<testDeterminationStrategy sameProgram="true" assignedTests="false"/>
<testRiskCoverage>
<harmless active="true"/>
<dangerous active="true"/>
<critical active="true"/>
</testRiskCoverage>
<durationCoverage short="true" medium="true" long="true"/>
<withNavigationUri enabled="true"/>
</options>
<adtcore:objectSets xmlns:adtcore="http://www.sap.com/adt/core">
<objectSet kind="inclusive">
<adtcore:objectReferences>
<adtcore:objectReference adtcore:uri="/sap/bc/adt/vit/wb/object_type/devck/object_name/${{ env.ABAP_PACKAGE }}"/>
</adtcore:objectReferences>
</objectSet>
</adtcore:objectSets>
</aunit:runConfiguration>
EOF
- name: Parse Test Results
run: |
# Ergebnisse analysieren
if grep -q 'alerts severity="error"' aunit_results.xml; then
echo "::error::ABAP Unit Tests failed!"
exit 1
fi
- name: Upload Test Results
uses: actions/upload-artifact@v4
with:
name: aunit-results
path: aunit_results.xml
# Job 3: ATC Checks
atc-checks:
name: ATC Quality Checks
needs: build
runs-on: ubuntu-latest
steps:
- name: Run ATC Checks
id: atc
run: |
# ATC Run starten
RUN_ID=$(curl -X POST \
"${{ secrets.ABAP_ENDPOINT }}/sap/bc/adt/atc/runs" \
-u "${{ secrets.ABAP_USER }}:${{ secrets.ABAP_PASSWORD }}" \
-H "Content-Type: application/vnd.sap.atc.run.parameters.v1+xml" \
-d @- << EOF | grep -oP 'id="\K[^"]+' | head -1
<?xml version="1.0" encoding="UTF-8"?>
<atc:run xmlns:atc="http://www.sap.com/adt/atc">
<checkVariant>${{ env.ATC_VARIANT }}</checkVariant>
<objectSets>
<objectSet name="run">
<softwareComponent name="${{ env.ABAP_PACKAGE }}"/>
</objectSet>
</objectSets>
</atc:run>
EOF
)
echo "ATC Run ID: $RUN_ID"
# Auf Ergebnis warten
sleep 30
# Ergebnisse abrufen
curl -X GET \
"${{ secrets.ABAP_ENDPOINT }}/sap/bc/adt/atc/runs/$RUN_ID/results" \
-u "${{ secrets.ABAP_USER }}:${{ secrets.ABAP_PASSWORD }}" \
-H "Accept: application/vnd.sap.atc.checkresult.v1+xml" \
> atc_results.xml
- name: Evaluate ATC Results
run: |
# Kritische Findings prüfen
ERRORS=$(grep -c 'priority="1"' atc_results.xml || true)
WARNINGS=$(grep -c 'priority="2"' atc_results.xml || true)
echo "ATC Results: $ERRORS errors, $WARNINGS warnings"
if [ "$ERRORS" -gt 0 ]; then
echo "::error::ATC found $ERRORS critical issues!"
exit 1
fi
- name: Upload ATC Results
uses: actions/upload-artifact@v4
with:
name: atc-results
path: atc_results.xml
# Job 4: Code Coverage Report
coverage:
name: Code Coverage
needs: unit-tests
runs-on: ubuntu-latest
steps:
- name: Download Test Results
uses: actions/download-artifact@v4
with:
name: aunit-results
- name: Generate Coverage Report
run: |
# Coverage aus XML extrahieren
COVERAGE=$(grep -oP 'statementCoverage="\K[^"]+' aunit_results.xml | head -1)
echo "Statement Coverage: $COVERAGE%"
# Coverage Badge erstellen
if (( $(echo "$COVERAGE < 60" | bc -l) )); then
COLOR="red"
elif (( $(echo "$COVERAGE < 80" | bc -l) )); then
COLOR="yellow"
else
COLOR="green"
fi
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
echo "color=$COLOR" >> $GITHUB_OUTPUT
# Job 5: Deployment
deploy-dev:
name: Deploy to DEV
needs: [unit-tests, atc-checks]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
environment: development
steps:
- name: Pull to DEV System
run: |
curl -X POST \
"${{ secrets.DEV_ENDPOINT }}/sap/bc/cts_abapvcs/repository/${{ vars.GCTS_REPO }}/pull" \
-u "${{ secrets.DEPLOY_USER }}:${{ secrets.DEPLOY_PASSWORD }}" \
-H "Content-Type: application/json" \
-d '{"branch": "develop"}'
deploy-qas:
name: Deploy to QAS
needs: [unit-tests, atc-checks]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: quality-assurance
steps:
- name: Pull to QAS System
run: |
curl -X POST \
"${{ secrets.QAS_ENDPOINT }}/sap/bc/cts_abapvcs/repository/${{ vars.GCTS_REPO }}/pull" \
-u "${{ secrets.DEPLOY_USER }}:${{ secrets.DEPLOY_PASSWORD }}" \
-H "Content-Type: application/json" \
-d '{"branch": "main"}'
deploy-prd:
name: Deploy to PRD
needs: deploy-qas
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch'
environment: production
steps:
- name: Pull to PRD System
run: |
curl -X POST \
"${{ secrets.PRD_ENDPOINT }}/sap/bc/cts_abapvcs/repository/${{ vars.GCTS_REPO }}/pull" \
-u "${{ secrets.DEPLOY_USER }}:${{ secrets.DEPLOY_PASSWORD }}" \
-H "Content-Type: application/json" \
-d '{"branch": "main", "tag": "${{ github.sha }}"}'

Reusable Workflow für ABAP Unit Tests

Erstelle wiederverwendbare Workflows:

.github/workflows/abap-unit-test.yml
name: ABAP Unit Test (Reusable)
on:
workflow_call:
inputs:
package:
required: true
type: string
coverage_threshold:
required: false
type: number
default: 70
secrets:
ABAP_ENDPOINT:
required: true
ABAP_USER:
required: true
ABAP_PASSWORD:
required: true
outputs:
coverage:
description: "Code coverage percentage"
value: ${{ jobs.test.outputs.coverage }}
jobs:
test:
runs-on: ubuntu-latest
outputs:
coverage: ${{ steps.run-tests.outputs.coverage }}
steps:
- name: Run ABAP Unit Tests
id: run-tests
run: |
# Tests ausführen und Coverage ermitteln
RESULT=$(curl -s -X POST \
"${{ secrets.ABAP_ENDPOINT }}/sap/bc/adt/abapunit/testruns" \
-u "${{ secrets.ABAP_USER }}:${{ secrets.ABAP_PASSWORD }}" \
-H "Content-Type: application/vnd.sap.adt.abapunit.testruns.config.v4+xml" \
-H "Accept: application/vnd.sap.adt.abapunit.testruns.result.v1+xml" \
-d '<aunit:runConfiguration xmlns:aunit="http://www.sap.com/adt/aunit">
<options><uriType value="semantic"/></options>
<adtcore:objectSets xmlns:adtcore="http://www.sap.com/adt/core">
<objectSet kind="inclusive">
<adtcore:objectReferences>
<adtcore:objectReference adtcore:uri="/sap/bc/adt/vit/wb/object_type/devck/object_name/${{ inputs.package }}"/>
</adtcore:objectReferences>
</objectSet>
</adtcore:objectSets>
</aunit:runConfiguration>')
# Coverage extrahieren
COVERAGE=$(echo "$RESULT" | grep -oP 'statementCoverage="\K[^"]+' || echo "0")
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
# Threshold prüfen
if (( $(echo "$COVERAGE < ${{ inputs.coverage_threshold }}" | bc -l) )); then
echo "::error::Coverage $COVERAGE% is below threshold ${{ inputs.coverage_threshold }}%"
exit 1
fi

Nutzung des Reusable Workflows

.github/workflows/main.yml
name: Main Pipeline
on:
push:
branches: [ main ]
jobs:
test-customer-package:
uses: ./.github/workflows/abap-unit-test.yml
with:
package: Z_CUSTOMER
coverage_threshold: 80
secrets: inherit
test-order-package:
uses: ./.github/workflows/abap-unit-test.yml
with:
package: Z_ORDER
coverage_threshold: 75
secrets: inherit

ABAP Unit Tests in Pipeline

Test-Klassen für CI/CD optimieren

CLASS zcl_customer_test DEFINITION
FOR TESTING
RISK LEVEL HARMLESS
DURATION SHORT.
PRIVATE SECTION.
CLASS-DATA:
mo_environment TYPE REF TO if_cds_test_environment.
CLASS-METHODS:
class_setup,
class_teardown.
METHODS:
setup,
teardown,
" Test Methods - klar benannt für CI/CD Reports
test_create_customer_success FOR TESTING,
test_create_customer_invalid_email FOR TESTING,
test_update_customer_status FOR TESTING,
test_delete_customer_with_orders FOR TESTING.
ENDCLASS.
CLASS zcl_customer_test IMPLEMENTATION.
METHOD class_setup.
" CDS Test Double Framework für isolierte Tests
mo_environment = cl_cds_test_environment=>create_for_multiple_cds(
VALUE #(
( i_for_entity = 'ZI_CUSTOMER' )
( i_for_entity = 'ZI_ORDER' )
)
).
ENDMETHOD.
METHOD class_teardown.
mo_environment->destroy( ).
ENDMETHOD.
METHOD setup.
" Testdaten vor jedem Test
mo_environment->clear_doubles( ).
" Mock-Daten einfügen
DATA(lt_customers) = VALUE zt_customer_t(
( customer_id = '1' name = 'Test GmbH' email = '[email protected]' status = 'A' )
( customer_id = '2' name = 'Demo AG' email = '[email protected]' status = 'I' )
).
mo_environment->insert_test_data( lt_customers ).
ENDMETHOD.
METHOD teardown.
ROLLBACK WORK.
ENDMETHOD.
METHOD test_create_customer_success.
" Given
DATA(lo_cut) = NEW zcl_customer_service( ).
DATA(ls_input) = VALUE zs_customer_create(
name = 'New Customer'
).
" When
DATA(ls_result) = lo_cut->create_customer( ls_input ).
" Then
cl_abap_unit_assert=>assert_not_initial(
act = ls_result-customer_id
msg = 'Customer ID should be generated'
).
cl_abap_unit_assert=>assert_equals(
act = ls_result-status
exp = 'A'
msg = 'New customer should be active'
).
ENDMETHOD.
METHOD test_create_customer_invalid_email.
" Given
DATA(lo_cut) = NEW zcl_customer_service( ).
DATA(ls_input) = VALUE zs_customer_create(
name = 'Invalid Customer'
email = 'not-an-email'
).
" When / Then
TRY.
lo_cut->create_customer( ls_input ).
cl_abap_unit_assert=>fail( 'Exception expected for invalid email' ).
CATCH zcx_customer_error INTO DATA(lx_error).
cl_abap_unit_assert=>assert_equals(
act = lx_error->error_code
exp = 'INVALID_EMAIL'
).
ENDTRY.
ENDMETHOD.
METHOD test_update_customer_status.
" Given
DATA(lo_cut) = NEW zcl_customer_service( ).
" When
lo_cut->set_customer_status(
iv_customer_id = '1'
iv_status = 'I'
).
" Then
DATA(ls_customer) = lo_cut->get_customer( '1' ).
cl_abap_unit_assert=>assert_equals(
act = ls_customer-status
exp = 'I'
msg = 'Status should be updated to inactive'
).
ENDMETHOD.
METHOD test_delete_customer_with_orders.
" Given: Kunde mit Bestellungen
DATA(lo_cut) = NEW zcl_customer_service( ).
DATA(lt_orders) = VALUE zt_order_t(
( order_id = '100' customer_id = '1' amount = 1000 )
).
mo_environment->insert_test_data( lt_orders ).
" When / Then: Löschen sollte fehlschlagen
TRY.
lo_cut->delete_customer( '1' ).
cl_abap_unit_assert=>fail( 'Exception expected - customer has orders' ).
CATCH zcx_customer_error INTO DATA(lx_error).
cl_abap_unit_assert=>assert_equals(
act = lx_error->error_code
exp = 'CUSTOMER_HAS_ORDERS'
).
ENDTRY.
ENDMETHOD.
ENDCLASS.

Test-Coverage Annotations

"! <p class="shorttext">Customer Service Test Coverage</p>
"! @testing ZCL_CUSTOMER_SERVICE
CLASS zcl_customer_test DEFINITION
FOR TESTING
RISK LEVEL HARMLESS
DURATION SHORT.

ATC (ABAP Test Cockpit) Integration

Custom ATC Variant für CI/CD

Erstelle eine eigene ATC-Variante für CI/CD-Prüfungen:

┌────────────────────────────────────────────────────────────────┐
│ ATC Check Variant: Z_CICD_CHECKS │
├────────────────────────────────────────────────────────────────┤
│ │
│ Priority 1 (Blocker - Pipeline fails): │
│ ☑ Syntax Errors │
│ ☑ Security Vulnerabilities (SQL Injection, XSS) │
│ ☑ Performance Critical (SELECT without WHERE) │
│ ☑ Obsolete Statements (not cloud-ready) │
│ │
│ Priority 2 (Warning - Report only): │
│ ☑ Naming Conventions │
│ ☑ Code Style │
│ ☑ Missing Documentation │
│ ☑ Unused Variables │
│ │
│ Priority 3 (Info - Ignored in CI/CD): │
│ ☐ Spelling Suggestions │
│ ☐ Refactoring Opportunities │
│ │
│ Exemptions: │
│ - Test Classes (RISK LEVEL, DURATION checks) │
│ - Generated Code (SICF handlers) │
│ │
└────────────────────────────────────────────────────────────────┘

ATC Exemptions per Code

CLASS zcl_legacy_wrapper DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS: call_legacy_function.
ENDCLASS.
CLASS zcl_legacy_wrapper IMPLEMENTATION.
METHOD call_legacy_function.
" ATC Exemption für bewusst genutzten Legacy-Code
"#EC CI_NOCHECK - Legacy-Integration, Migration geplant Q3
CALL FUNCTION 'Z_LEGACY_FUNCTION'
EXPORTING
iv_input = mv_input.
ENDMETHOD.
ENDCLASS.

Pipeline für ABAP BTP Environment

SAP BTP ABAP Environment Deployment

.github/workflows/btp-abap-deploy.yml
name: BTP ABAP Environment Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Cloud Foundry CLI
run: |
wget -q -O cf-cli.deb "https://packages.cloudfoundry.org/stable?release=debian64"
sudo dpkg -i cf-cli.deb
- name: Login to Cloud Foundry
run: |
cf login \
-a "${{ secrets.CF_API }}" \
-u "${{ secrets.CF_USER }}" \
-p "${{ secrets.CF_PASSWORD }}" \
-o "${{ vars.CF_ORG }}" \
-s "${{ vars.CF_SPACE }}"
- name: Get ABAP Service Key
id: service-key
run: |
cf create-service-key abap-instance ci-key -c '{"type": "service_manager"}'
KEY=$(cf service-key abap-instance ci-key --guid)
echo "service_key=$KEY" >> $GITHUB_OUTPUT
- name: Deploy via ADT
run: |
# ABAP Deployment via REST API
curl -X POST \
"${{ secrets.ABAP_ENDPOINT }}/sap/bc/adt/abapgit/repos/${{ vars.REPO_NAME }}/pull" \
-u "${{ secrets.ABAP_USER }}:${{ secrets.ABAP_PASSWORD }}" \
-H "Content-Type: application/json" \
-d '{"branch": "main"}'

Multi-System Deployment Strategy

.github/workflows/multi-system-deploy.yml
name: Multi-System Deployment
on:
release:
types: [published]
jobs:
deploy-matrix:
strategy:
matrix:
include:
- environment: development
system: DEV
auto_deploy: true
- environment: quality
system: QAS
auto_deploy: true
- environment: production
system: PRD
auto_deploy: false
runs-on: ubuntu-latest
environment: ${{ matrix.environment }}
steps:
- name: Deploy to ${{ matrix.system }}
if: matrix.auto_deploy || github.event.release.prerelease == false
run: |
curl -X POST \
"${{ secrets[format('{0}_ENDPOINT', matrix.system)] }}/sap/bc/cts_abapvcs/repository/${{ vars.GCTS_REPO }}/pull" \
-u "${{ secrets.DEPLOY_USER }}:${{ secrets.DEPLOY_PASSWORD }}" \
-H "Content-Type: application/json" \
-d '{
"branch": "main",
"tag": "${{ github.event.release.tag_name }}",
"commitMessage": "Release ${{ github.event.release.tag_name }}"
}'

Integrationstest-Szenarien

API-Integrationstests

CLASS zcl_api_integration_test DEFINITION
FOR TESTING
RISK LEVEL DANGEROUS
DURATION LONG.
PRIVATE SECTION.
METHODS:
test_odata_service_metadata FOR TESTING,
test_odata_create_entity FOR TESTING,
test_odata_error_handling FOR TESTING.
ENDCLASS.
CLASS zcl_api_integration_test IMPLEMENTATION.
METHOD test_odata_service_metadata.
" OData Service $metadata abrufen
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination(
cl_http_destination_provider=>create_by_url(
i_url = |http://localhost:8080/sap/opu/odata/sap/Z_CUSTOMER_SRV/$metadata|
)
).
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
cl_abap_unit_assert=>assert_equals(
act = lo_response->get_status( )-code
exp = 200
msg = 'Metadata should be accessible'
).
" Schema validieren
DATA(lv_metadata) = lo_response->get_text( ).
cl_abap_unit_assert=>assert_true(
act = xsdbool( lv_metadata CS 'EntityType Name="Customer"' )
msg = 'Customer entity should exist in metadata'
).
lo_client->close( ).
ENDMETHOD.
METHOD test_odata_create_entity.
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination(
cl_http_destination_provider=>create_by_url(
i_url = |http://localhost:8080/sap/opu/odata/sap/Z_CUSTOMER_SRV/CustomerSet|
)
).
DATA(lo_request) = lo_client->get_http_request( ).
lo_request->set_header_field( i_name = 'Content-Type' i_value = 'application/json' ).
lo_request->set_text( '{"Name": "Integration Test", "Email": "[email protected]"}' ).
DATA(lo_response) = lo_client->execute( if_web_http_client=>post ).
cl_abap_unit_assert=>assert_equals(
act = lo_response->get_status( )-code
exp = 201
msg = 'Entity should be created'
).
lo_client->close( ).
ENDMETHOD.
METHOD test_odata_error_handling.
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination(
cl_http_destination_provider=>create_by_url(
i_url = |http://localhost:8080/sap/opu/odata/sap/Z_CUSTOMER_SRV/CustomerSet('INVALID')|
)
).
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
cl_abap_unit_assert=>assert_equals(
act = lo_response->get_status( )-code
exp = 404
msg = 'Invalid key should return 404'
).
lo_client->close( ).
ENDMETHOD.
ENDCLASS.

Notifications und Reporting

Slack-Integration

# In der GitHub Actions Pipeline
- name: Notify Slack on Failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
channel-id: 'C01234567'
slack-message: |
:x: *ABAP Pipeline Failed*
Repository: ${{ github.repository }}
Branch: ${{ github.ref_name }}
Commit: ${{ github.sha }}
Author: ${{ github.actor }}
<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Details>
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
- name: Notify Slack on Success
if: success()
uses: slackapi/slack-github-action@v1
with:
channel-id: 'C01234567'
slack-message: |
:white_check_mark: *ABAP Pipeline Successful*
Repository: ${{ github.repository }}
Branch: ${{ github.ref_name }}
Deployed to: ${{ matrix.environment }}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

Test-Report generieren

- name: Generate Test Report
run: |
cat << EOF > test-report.md
# ABAP Test Report
**Build:** ${{ github.run_number }}
**Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")
**Branch:** ${{ github.ref_name }}
## Unit Tests
- Total: $(grep -c 'testmethod' aunit_results.xml || echo "0")
- Passed: $(grep -c 'type="success"' aunit_results.xml || echo "0")
- Failed: $(grep -c 'type="failure"' aunit_results.xml || echo "0")
## Code Coverage
- Statement: ${{ steps.coverage.outputs.statement }}%
- Branch: ${{ steps.coverage.outputs.branch }}%
## ATC Findings
- Errors: $(grep -c 'priority="1"' atc_results.xml || echo "0")
- Warnings: $(grep -c 'priority="2"' atc_results.xml || echo "0")
- Info: $(grep -c 'priority="3"' atc_results.xml || echo "0")
EOF
- name: Upload Test Report
uses: actions/upload-artifact@v4
with:
name: test-report
path: test-report.md

Best Practices

ThemaEmpfehlung
Test-IsolationCDS Test Environment für Unit Tests nutzen
Test-DatenTestdaten in Setup-Methoden, nicht in Produktivdaten
ATC-VarianteEigene CI/CD-Variante mit sinnvollen Checks
SecretsGitHub Secrets oder SAP Credential Store nutzen
ParallelisierungUnabhängige Jobs parallel ausführen
CachingDependencies und Build-Artefakte cachen
EnvironmentsGitHub Environments für Approval-Workflows
MonitoringSlack/Teams-Integration für Benachrichtigungen
DokumentationPipeline-Konfiguration versioniert im Repository
RollbackSchnellen Rollback-Mechanismus implementieren

Weiterführende Themen