CI/CD (Continuous Integration / Continuous Delivery) permet des tests automatisés, des vérifications de qualité du code et des déploiements pour ABAP Cloud. Avec les bons outils et pipelines, le développement ABAP devient aussi agile que le développement cloud moderne.
Concept CI/CD pour ABAP
L’intégration continue et la livraison continue apportent les pratiques DevOps éprouvées dans le monde ABAP :
| Aspect | Sans CI/CD | Avec CI/CD |
|---|---|---|
| Tests | Manuel, souvent oublié | Automatique à chaque commit |
| Qualité du code | Vérifications ATC sporadiques | Vérification continue |
| Déploiement | Transport manuel | Automatisé et reproductible |
| Feedback | Retardé, après plusieurs jours | Immédiat, en quelques minutes |
| Documentation | Souvent obsolète | Living Documentation |
| Risque | Grandes releases, risque élevé | Petites modifications, faible risque |
| Rollback | Complexe, manuel | Simple, automatisé |
Vue d’ensemble du pipeline CI/CD
┌─────────────────────────────────────────────────────────────────────────┐│ Pipeline CI/CD pour 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 ││ │└─────────────────────────────────────────────────────────────────────────┘Service SAP CI/CD
Le SAP Continuous Integration and Delivery Service est un service BTP entièrement géré, spécialement optimisé pour le développement SAP.
Vue d’ensemble du service
┌────────────────────────────────────────────────────────────────┐│ Service SAP CI/CD ││ ││ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ││ │ Pipeline │ │ Jobs & │ │ Artifact │ ││ │ Editor │ │ Stages │ │ Storage │ ││ └──────────────┘ └──────────────┘ └──────────────┘ ││ ││ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ││ │ Credentials │ │ Webhooks │ │ Logs & │ ││ │ Manager │ │ & Trigger │ │ Reports │ ││ └──────────────┘ └──────────────┘ └──────────────┘ ││ ││ Scénarios supportés : ││ • SAP Fiori dans SAP BTP Cloud Foundry ││ • SAP Cloud Application Programming Model (CAP) ││ • ABAP Environment (SAP BTP) ││ • Intégration gCTS ││ │└────────────────────────────────────────────────────────────────┘Configuration du pipeline
Le service SAP CI/CD utilise une configuration basée sur YAML :
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"Pipeline ABAP Environment
Pour ABAP Environment (Steampunk), il existe des templates de pipeline spéciaux :
# .pipeline/config.yml pour 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"Configurer les credentials
Dans le service SAP CI/CD, les credentials sont gérés de manière sécurisée :
┌────────────────────────────────────────────────────────────────┐│ 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 pour ABAP
GitHub Actions offre une flexibilité maximale pour CI/CD ABAP. Voici un pipeline complet :
Pipeline de base
name: Pipeline CI/CD ABAP
on: push: branches: [ main, develop, 'release/**' ] pull_request: branches: [ main, develop ] workflow_dispatch: inputs: deploy_target: description: 'Déployer vers l\'environnement" 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 : Vérification syntaxique et activation 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 : Tests ABAP Unit unit-tests: name: Tests ABAP Unit 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: | # Analyser les résultats if grep -q 'alerts severity="error"' aunit_results.xml; then echo "::error::Les tests ABAP Unit ont échoué !" exit 1 fi
- name: Upload Test Results uses: actions/upload-artifact@v4 with: name: aunit-results path: aunit_results.xml
# Job 3 : Vérifications ATC atc-checks: name: Vérifications de qualité ATC needs: build runs-on: ubuntu-latest
steps: - name: Run ATC Checks id: atc run: | # Démarrer l'exécution ATC 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"
# Attendre le résultat sleep 30
# Récupérer les résultats 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: | # Vérifier les findings critiques ERRORS=$(grep -c 'priority="1"' atc_results.xml || true) WARNINGS=$(grep -c 'priority="2"' atc_results.xml || true)
echo "Résultats ATC : $ERRORS erreurs, $WARNINGS avertissements"
if [ "$ERRORS" -gt 0 ]; then echo "::error::ATC a trouvé $ERRORS problèmes critiques !" exit 1 fi
- name: Upload ATC Results uses: actions/upload-artifact@v4 with: name: atc-results path: atc_results.xml
# Job 4 : Rapport de couverture de code coverage: name: Couverture de code 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: | # Extraire la couverture du XML COVERAGE=$(grep -oP 'statementCoverage="\K[^"]+' aunit_results.xml | head -1) echo "Couverture des instructions : $COVERAGE%"
# Créer un badge de couverture 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 : Déploiement deploy-dev: name: Déployer vers 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: Déployer vers 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: Déployer vers 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 }}"}"Workflow réutilisable pour les tests ABAP Unit
Créez des workflows réutilisables :
name: Test ABAP Unit (Réutilisable)
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: "Pourcentage de couverture de code" 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: | # Exécuter les tests et déterminer la couverture 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>')
# Extraire la couverture COVERAGE=$(echo "$RESULT" | grep -oP 'statementCoverage="\K[^"]+' || echo "0") echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
# Vérifier le seuil if (( $(echo "$COVERAGE < ${{ inputs.coverage_threshold }}" | bc -l) )); then echo "::error::La couverture $COVERAGE% est inférieure au seuil ${{ inputs.coverage_threshold }}%" exit 1 fiUtilisation du workflow réutilisable
name: Pipeline principale
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: inheritTests ABAP Unit dans le pipeline
Optimiser les classes de test pour CI/CD
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, " Méthodes de test - nommées clairement pour les rapports CI/CD 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. " Framework CDS Test Double pour des tests isolés 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. " Données de test avant chaque test mo_environment->clear_doubles( ).
" Insérer des données mock 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 = 'Nouveau client" ).
" When DATA(ls_result) = lo_cut->create_customer( ls_input ).
" Then cl_abap_unit_assert=>assert_not_initial( act = ls_result-customer_id msg = 'L''ID client devrait être généré" ). cl_abap_unit_assert=>assert_equals( act = ls_result-status exp = 'A" msg = 'Le nouveau client devrait être actif" ). ENDMETHOD.
METHOD test_create_customer_invalid_email. " Given DATA(lo_cut) = NEW zcl_customer_service( ). DATA(ls_input) = VALUE zs_customer_create( name = 'Client invalide" email = 'not-an-email" ).
" When / Then TRY. lo_cut->create_customer( ls_input ). cl_abap_unit_assert=>fail( 'Exception attendue pour email invalide' ). 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 = 'Le statut devrait être mis à jour à inactif" ). ENDMETHOD.
METHOD test_delete_customer_with_orders. " Given: Client avec commandes 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: La suppression devrait échouer TRY. lo_cut->delete_customer( '1' ). cl_abap_unit_assert=>fail( 'Exception attendue - le client a des commandes' ). 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.Annotations de couverture de test
"! <p class="shorttext">Couverture de test du service client</p>"! @testing ZCL_CUSTOMER_SERVICECLASS zcl_customer_test DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.Intégration ATC (ABAP Test Cockpit)
Variante ATC personnalisée pour CI/CD
Créez votre propre variante ATC pour les vérifications CI/CD :
┌────────────────────────────────────────────────────────────────┐│ Variante de vérification ATC : Z_CICD_CHECKS │├────────────────────────────────────────────────────────────────┤│ ││ Priorité 1 (Blocker - échec du pipeline) : ││ ☑ Erreurs de syntaxe ││ ☑ Vulnérabilités de sécurité (injection SQL, XSS) ││ ☑ Critique de performance (SELECT sans WHERE) ││ ☑ Instructions obsolètes (non cloud-ready) ││ ││ Priorité 2 (Avertissement - rapport uniquement) : ││ ☑ Conventions de nommage ││ ☑ Style de code ││ ☑ Documentation manquante ││ ☑ Variables non utilisées ││ ││ Priorité 3 (Info - Ignoré dans CI/CD) : ││ ☐ Suggestions d'orthographe ││ ☐ Opportunités de refactoring ││ ││ Exemptions : ││ - Classes de test (vérifications RISK LEVEL, DURATION) ││ - Code généré (gestionnaires SICF) ││ │└────────────────────────────────────────────────────────────────┘Exemptions ATC par 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. " Exemption ATC pour code legacy utilisé consciemment "#EC CI_NOCHECK - Intégration legacy, migration prévue Q3 CALL FUNCTION 'Z_LEGACY_FUNCTION" EXPORTING iv_input = mv_input. ENDMETHOD.ENDCLASS.Pipeline pour ABAP BTP Environment
Déploiement SAP BTP ABAP Environment
name: Déploiement BTP ABAP Environment
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: | # Déploiement ABAP via API REST 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"}"Stratégie de déploiement multi-systèmes
name: Déploiement multi-systèmes
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 }}" }"Scénarios de tests d’intégration
Tests d’intégration API
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. " Récupérer le $metadata du service OData 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 = 'Les métadonnées devraient être accessibles" ).
" Valider le schéma DATA(lv_metadata) = lo_response->get_text( ). cl_abap_unit_assert=>assert_true( act = xsdbool( lv_metadata CS 'EntityType Name="Customer"' ) msg = 'L''entité Customer devrait exister dans les métadonnées" ).
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 = 'L''entité devrait être créée" ).
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 = 'Une clé invalide devrait renvoyer 404" ).
lo_client->close( ). ENDMETHOD.ENDCLASS.Notifications et rapports
Intégration Slack
# Dans le pipeline GitHub Actions- name: Notify Slack on Failure if: failure() uses: slackapi/slack-github-action@v1 with: channel-id: 'C01234567" slack-message: | :x: *Échec du pipeline ABAP* Repository: ${{ github.repository }} Branch: ${{ github.ref_name }} Commit: ${{ github.sha }} Author: ${{ github.actor }} <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Voir les détails> 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: *Pipeline ABAP réussi* Repository: ${{ github.repository }} Branch: ${{ github.ref_name }} Déployé vers: ${{ matrix.environment }} env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}Générer un rapport de test
- name: Generate Test Report run: | cat << EOF > test-report.md # Rapport de test ABAP
**Build:** ${{ github.run_number }} **Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") **Branch:** ${{ github.ref_name }}
## Tests unitaires - Total: $(grep -c 'testmethod' aunit_results.xml || echo "0") - Réussis: $(grep -c 'type="success"' aunit_results.xml || echo "0") - Échoués: $(grep -c 'type="failure"' aunit_results.xml || echo "0")
## Couverture de code - Instructions: ${{ steps.coverage.outputs.statement }}% - Branches: ${{ steps.coverage.outputs.branch }}%
## Résultats ATC - Erreurs: $(grep -c 'priority="1"' atc_results.xml || echo "0") - Avertissements: $(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.mdBonnes pratiques
| Thème | Recommandation |
|---|---|
| Isolation des tests | Utiliser CDS Test Environment pour les tests unitaires |
| Données de test | Données de test dans les méthodes setup, pas dans les données de production |
| Variante ATC | Variante CI/CD personnalisée avec des vérifications pertinentes |
| Secrets | Utiliser GitHub Secrets ou SAP Credential Store |
| Parallélisation | Exécuter les jobs indépendants en parallèle |
| Caching | Mettre en cache les dépendances et les artefacts de build |
| Environments | GitHub Environments pour les workflows d’approbation |
| Monitoring | Intégration Slack/Teams pour les notifications |
| Documentation | Configuration du pipeline versionnée dans le repository |
| Rollback | Implémenter un mécanisme de rollback rapide |
Sujets avancés
- gCTS: Git-enabled CTS - Gestion des transports basée sur Git
- ABAP Cleaner - Automatiser la qualité du code
- ADT Tipps & Tricks - Développement efficace