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:
| Aspekt | Ohne CI/CD | Mit CI/CD |
|---|---|---|
| Tests | Manuell, oft vergessen | Automatisch bei jedem Commit |
| Code-Qualität | Sporadische ATC-Checks | Kontinuierliche Prüfung |
| Deployment | Manueller Transport | Automatisiert und reproduzierbar |
| Feedback | Verzögert, nach Tagen | Sofort, in Minuten |
| Dokumentation | Oft veraltet | Living Documentation |
| Risiko | Große Releases, hohes Risiko | Kleine Änderungen, geringes Risiko |
| Rollback | Komplex, manuell | Einfach, 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:
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 Environmentgeneral: 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
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:
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 fiNutzung des Reusable Workflows
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: inheritABAP 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( ).
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_SERVICECLASS 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
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
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' ).
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.mdBest Practices
| Thema | Empfehlung |
|---|---|
| Test-Isolation | CDS Test Environment für Unit Tests nutzen |
| Test-Daten | Testdaten in Setup-Methoden, nicht in Produktivdaten |
| ATC-Variante | Eigene CI/CD-Variante mit sinnvollen Checks |
| Secrets | GitHub Secrets oder SAP Credential Store nutzen |
| Parallelisierung | Unabhängige Jobs parallel ausführen |
| Caching | Dependencies und Build-Artefakte cachen |
| Environments | GitHub Environments für Approval-Workflows |
| Monitoring | Slack/Teams-Integration für Benachrichtigungen |
| Dokumentation | Pipeline-Konfiguration versioniert im Repository |
| Rollback | Schnellen Rollback-Mechanismus implementieren |
Weiterführende Themen
- gCTS: Git-enabled CTS - Git-basiertes Transport-Management
- ABAP Cleaner - Code-Qualität automatisieren
- ADT Tipps & Tricks - Effiziente Entwicklung