CI/CD avec ABAP Cloud

Catégorie
DevOps
Publié
Auteur
Johannes

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 :

AspectSans CI/CDAvec CI/CD
TestsManuel, souvent oubliéAutomatique à chaque commit
Qualité du codeVérifications ATC sporadiquesVérification continue
DéploiementTransport manuelAutomatisé et reproductible
FeedbackRetardé, après plusieurs joursImmédiat, en quelques minutes
DocumentationSouvent obsolèteLiving Documentation
RisqueGrandes releases, risque élevéPetites modifications, faible risque
RollbackComplexe, manuelSimple, 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 :

.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"

Pipeline ABAP Environment

Pour ABAP Environment (Steampunk), il existe des templates de pipeline spéciaux :

# .pipeline/config.yml pour 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"

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

.github/workflows/abap-ci.yml
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 :

.github/workflows/abap-unit-test.yml
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
fi

Utilisation du workflow réutilisable

.github/workflows/main.yml
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: inherit

Tests 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(
( 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 = '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_SERVICE
CLASS 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

.github/workflows/btp-abap-deploy.yml
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

.github/workflows/multi-system-deploy.yml
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' ).
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 = '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.md

Bonnes pratiques

ThèmeRecommandation
Isolation des testsUtiliser CDS Test Environment pour les tests unitaires
Données de testDonnées de test dans les méthodes setup, pas dans les données de production
Variante ATCVariante CI/CD personnalisée avec des vérifications pertinentes
SecretsUtiliser GitHub Secrets ou SAP Credential Store
ParallélisationExécuter les jobs indépendants en parallèle
CachingMettre en cache les dépendances et les artefacts de build
EnvironmentsGitHub Environments pour les workflows d’approbation
MonitoringIntégration Slack/Teams pour les notifications
DocumentationConfiguration du pipeline versionnée dans le repository
RollbackImplémenter un mécanisme de rollback rapide

Sujets avancés