ABAP Unit Testing dans ABAP Cloud : Guide complet (2025)

Catégorie
Testing
Publié
Auteur
Johannes

Le Unit Testing est plus important que jamais dans ABAP Cloud. Ce guide vous montre comment écrire du code testé et prêt pour la production.

Pourquoi le Unit Testing dans ABAP Cloud ?

Avantages

  • Détection précoce des erreurs (avant la production)
  • Sécurité du refactoring (modifications sans crainte)
  • Documentation (les tests = spécification)
  • Prêt pour CI/CD (tests automatisés)
  • Qualité du code (impose du code testable)

Particularités d’ABAP Cloud

  • Pas de debugger en production → Tests indispensables
  • Framework RAP → Outils de test spécifiques
  • CDS Views → CDS Test Environment

1. Les bases d’ABAP Unit

Première classe de test

CLASS zcl_calculator DEFINITION PUBLIC.
PUBLIC SECTION.
CLASS-METHODS add
IMPORTING iv_a TYPE i
iv_b TYPE i
RETURNING VALUE(rv_result) TYPE i.
ENDCLASS.
CLASS zcl_calculator IMPLEMENTATION.
METHOD add.
rv_result = iv_a + iv_b.
ENDMETHOD.
ENDCLASS.
" ===== CLASSE DE TEST =====
CLASS ltc_calculator_test DEFINITION FINAL
FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS test_add_positive FOR TESTING.
METHODS test_add_negative FOR TESTING.
METHODS test_add_zero FOR TESTING.
ENDCLASS.
CLASS ltc_calculator_test IMPLEMENTATION.
METHOD test_add_positive.
" Given
DATA(lv_a) = 5.
DATA(lv_b) = 3.
" When
DATA(lv_result) = zcl_calculator=>add(
iv_a = lv_a
iv_b = lv_b
).
" Then
cl_abap_unit_assert=>assert_equals(
act = lv_result
exp = 8
msg = '5 + 3 devrait égaler 8"
).
ENDMETHOD.
METHOD test_add_negative.
DATA(lv_result) = zcl_calculator=>add(
iv_a = -5
iv_b = 3
).
cl_abap_unit_assert=>assert_equals(
act = lv_result
exp = -2
).
ENDMETHOD.
METHOD test_add_zero.
DATA(lv_result) = zcl_calculator=>add(
iv_a = 0
iv_b = 0
).
cl_abap_unit_assert=>assert_equals(
act = lv_result
exp = 0
).
ENDMETHOD.
ENDCLASS.

Exécuter les tests

Dans ADT :

  1. Clic droit sur la classe → Run AsABAP Unit Test
  2. Résultats dans l’onglet ABAP Unit

Tous les tests au vert ? Parfait !


2. Structure des tests : Given-When-Then

Pattern de bonne pratique :

METHOD test_something.
" GIVEN (Arrange)
" - Configurer les données de test
" - Créer les préconditions
DATA(lv_input) = 'Test'.
DATA(lo_object) = NEW zcl_my_class( ).
" WHEN (Act)
" - Exécuter la méthode à tester
DATA(lv_result) = lo_object->process( lv_input ).
" THEN (Assert)
" - Vérifier le résultat
cl_abap_unit_assert=>assert_equals(
act = lv_result
exp = 'EXPECTED"
).
ENDMETHOD.

3. Méthodes d’assertion

Vérifier l’égalité

" Égalité de valeur
cl_abap_unit_assert=>assert_equals(
act = lv_actual
exp = lv_expected
msg = 'Les valeurs devraient correspondre"
).
" Égalité d'objet
cl_abap_unit_assert=>assert_bound(
act = lo_object
msg = 'L''objet devrait être instancié"
).
" Non-égalité
cl_abap_unit_assert=>assert_differs(
act = lv_actual
exp = lv_wrong_value
).

Vérifications booléennes

" True/False
cl_abap_unit_assert=>assert_true(
act = lv_condition
msg = 'Devrait être vrai"
).
cl_abap_unit_assert=>assert_false(
act = lv_condition
).

Initial/Not Initial

cl_abap_unit_assert=>assert_initial(
act = lv_value
msg = 'Devrait être initial"
).
cl_abap_unit_assert=>assert_not_initial(
act = lv_value
).

Vérifications de tables

" Taille de table
cl_abap_unit_assert=>assert_table_contains(
table = lt_result
line = ls_expected_line
).
" Nombre de lignes
DATA(lv_lines) = lines( lt_result ).
cl_abap_unit_assert=>assert_equals(
act = lv_lines
exp = 3
).

Vérifier les exceptions

METHOD test_exception_thrown.
DATA(lo_object) = NEW zcl_my_class( ).
TRY.
lo_object->method_that_throws( 'invalid' ).
" Si on arrive ici : Test échoué !
cl_abap_unit_assert=>fail(
msg = 'Une exception aurait dû être levée"
).
CATCH zcx_my_exception INTO DATA(lx_error).
" Exception attendue → Test réussi
cl_abap_unit_assert=>assert_bound( lx_error ).
ENDTRY.
ENDMETHOD.

4. Test Fixtures (Setup & Teardown)

CLASS ltc_test DEFINITION FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
" Niveau classe (une fois pour tous les tests)
CLASS-DATA lo_shared_object TYPE REF TO zcl_my_class.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
" Niveau instance (avant/après chaque test)
DATA lo_test_object TYPE REF TO zcl_my_class.
METHODS setup.
METHODS teardown.
METHODS test_1 FOR TESTING.
METHODS test_2 FOR TESTING.
ENDCLASS.
CLASS ltc_test IMPLEMENTATION.
METHOD class_setup.
" Exécuté 1x avant tous les tests
lo_shared_object = NEW zcl_my_class( ).
ENDMETHOD.
METHOD class_teardown.
" Exécuté 1x après tous les tests
FREE lo_shared_object.
ENDMETHOD.
METHOD setup.
" Exécuté avant CHAQUE test
lo_test_object = NEW zcl_my_class( ).
ENDMETHOD.
METHOD teardown.
" Exécuté après CHAQUE test
CLEAR lo_test_object.
ENDMETHOD.
METHOD test_1.
" lo_test_object est fraîchement initialisé ici
ENDMETHOD.
METHOD test_2.
" lo_test_object est à nouveau frais (non influencé par test_1)
ENDMETHOD.
ENDCLASS.

5. Test des CDS Views

Configurer l’environnement de test

CLASS ltc_cds_test DEFINITION FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA environment TYPE REF TO if_cds_test_environment.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
METHODS setup.
METHODS test_customer_view FOR TESTING.
ENDCLASS.
CLASS ltc_cds_test IMPLEMENTATION.
METHOD class_setup.
" Créer l'environnement de test CDS
environment = cl_cds_test_environment=>create_for_multiple_cds(
i_for_entities = VALUE #(
( i_for_entity = 'Z_CUSTOMER' )
( i_for_entity = 'Z_SALESORDER' )
)
).
ENDMETHOD.
METHOD class_teardown.
environment->destroy( ).
ENDMETHOD.
METHOD setup.
environment->clear_doubles( ).
ENDMETHOD.
METHOD test_customer_view.
" GIVEN : Insérer les données de test
DATA(lt_customers) = VALUE ztab_customers(
( customer_id = '0001' name = 'Test AG' country = 'DE' )
( customer_id = '0002' name = 'Demo GmbH' country = 'AT' )
).
environment->insert_test_data( lt_customers ).
" WHEN : Interroger la CDS View
SELECT customer_id, name, country
FROM z_customer
WHERE country = 'DE"
INTO TABLE @DATA(lt_result).
" THEN : Assertions
cl_abap_unit_assert=>assert_equals(
act = lines( lt_result )
exp = 1
msg = 'Devrait trouver 1 client allemand"
).
cl_abap_unit_assert=>assert_equals(
act = lt_result[ 1 ]-name
exp = 'Test AG"
).
ENDMETHOD.
ENDCLASS.

6. Test RAP

Tester un RAP Business Object

CLASS ltc_rap_test DEFINITION FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA environment TYPE REF TO if_cds_test_environment.
CLASS-METHODS class_setup.
METHODS setup.
METHODS teardown.
METHODS test_create_book FOR TESTING.
METHODS test_action_start_reading FOR TESTING.
METHODS test_validation_isbn FOR TESTING.
ENDCLASS.
CLASS ltc_rap_test IMPLEMENTATION.
METHOD class_setup.
environment = cl_cds_test_environment=>create_for_multiple_cds(
i_for_entities = VALUE #( ( i_for_entity = 'ZI_BOOK' ) )
).
ENDMETHOD.
METHOD setup.
environment->clear_doubles( ).
ENDMETHOD.
METHOD teardown.
ROLLBACK ENTITIES.
ENDMETHOD.
METHOD test_create_book.
" GIVEN
DATA lt_create TYPE TABLE FOR CREATE zi_book.
lt_create = VALUE #(
( %cid = 'BOOK_1"
title = 'Test Book"
author = 'Test Author"
isbn = '1234567890"
pages = 100 )
).
" WHEN
MODIFY ENTITIES OF zi_book
ENTITY Book
CREATE FROM lt_create
MAPPED DATA(mapped)
FAILED DATA(failed)
REPORTED DATA(reported).
COMMIT ENTITIES.
" THEN
cl_abap_unit_assert=>assert_initial(
act = failed
msg = 'La création devrait réussir"
).
" Lire le livre
READ ENTITIES OF zi_book
ENTITY Book
ALL FIELDS
WITH VALUE #( ( %cid = 'BOOK_1' ) )
RESULT DATA(lt_books).
cl_abap_unit_assert=>assert_equals(
act = lt_books[ 1 ]-title
exp = 'Test Book"
).
ENDMETHOD.
METHOD test_action_start_reading.
" GIVEN : Créer un livre avec le statut 'N"
MODIFY ENTITIES OF zi_book
ENTITY Book
CREATE FROM VALUE #(
( %cid = 'B1"
title = 'Test"
status = 'N' )
).
COMMIT ENTITIES.
" WHEN : Exécuter l'action
MODIFY ENTITIES OF zi_book
ENTITY Book
EXECUTE startReading FROM VALUE #( ( %cid = 'B1' ) )
RESULT DATA(result).
COMMIT ENTITIES.
" THEN : Le statut devrait être 'R"
READ ENTITIES OF zi_book
ENTITY Book
FIELDS ( status )
WITH VALUE #( ( %cid = 'B1' ) )
RESULT DATA(lt_books).
cl_abap_unit_assert=>assert_equals(
act = lt_books[ 1 ]-status
exp = 'R"
).
ENDMETHOD.
METHOD test_validation_isbn.
" GIVEN : ISBN invalide (trop court)
DATA lt_create TYPE TABLE FOR CREATE zi_book.
lt_create = VALUE #(
( %cid = 'B1"
title = 'Test"
isbn = '12345' ) " Seulement 5 caractères !
).
" WHEN
MODIFY ENTITIES OF zi_book
ENTITY Book
CREATE FROM lt_create.
COMMIT ENTITIES
FAILED DATA(failed).
" THEN : Devrait échouer
cl_abap_unit_assert=>assert_not_initial(
act = failed-book
msg = 'La validation devrait échouer"
).
ENDMETHOD.
ENDCLASS.

7. Test Doubles (Mocking)

Pour les objets dépendants :

" Interface (à mocker)
INTERFACE zif_email_sender.
METHODS send_email
IMPORTING iv_to TYPE string
iv_subject TYPE string
iv_body TYPE string
RETURNING VALUE(rv_success) TYPE abap_bool.
ENDINTERFACE.
" Classe de production
CLASS zcl_order_processor DEFINITION PUBLIC.
PUBLIC SECTION.
METHODS constructor
IMPORTING io_email_sender TYPE REF TO zif_email_sender.
METHODS process_order
IMPORTING iv_order_id TYPE string
RETURNING VALUE(rv_success) TYPE abap_bool.
PRIVATE SECTION.
DATA mo_email_sender TYPE REF TO zif_email_sender.
ENDCLASS.
CLASS zcl_order_processor IMPLEMENTATION.
METHOD constructor.
mo_email_sender = io_email_sender.
ENDMETHOD.
METHOD process_order.
" Traitement...
" Envoyer l'email
DATA(lv_email_sent) = mo_email_sender->send_email(
iv_subject = 'Confirmation de commande"
iv_body = |Votre commande { iv_order_id } a été traitée|
).
rv_success = lv_email_sent.
ENDMETHOD.
ENDCLASS.
" ===== TEST avec Mock =====
CLASS ltc_test DEFINITION FOR TESTING.
PRIVATE SECTION.
DATA mo_email_mock TYPE REF TO ltc_email_sender_mock.
DATA mo_processor TYPE REF TO zcl_order_processor.
METHODS setup.
METHODS test_order_processing FOR TESTING.
ENDCLASS.
CLASS ltc_test IMPLEMENTATION.
METHOD setup.
" Créer le mock
mo_email_mock = NEW ltc_email_sender_mock( ).
" Injecter le mock dans le processor
mo_processor = NEW zcl_order_processor( mo_email_mock ).
ENDMETHOD.
METHOD test_order_processing.
" GIVEN : Le mock retourne true
mo_email_mock->set_return_value( abap_true ).
" WHEN
DATA(lv_result) = mo_processor->process_order( '12345' ).
" THEN
cl_abap_unit_assert=>assert_true( lv_result ).
" Vérifier : send_email a-t-il été appelé ?
cl_abap_unit_assert=>assert_true(
act = mo_email_mock->was_send_email_called( )
).
ENDMETHOD.
ENDCLASS.
" Implémentation du mock
CLASS ltc_email_sender_mock DEFINITION.
PUBLIC SECTION.
INTERFACES zif_email_sender.
METHODS set_return_value
IMPORTING iv_value TYPE abap_bool.
METHODS was_send_email_called
RETURNING VALUE(rv_called) TYPE abap_bool.
PRIVATE SECTION.
DATA mv_return_value TYPE abap_bool.
DATA mv_was_called TYPE abap_bool.
ENDCLASS.
CLASS ltc_email_sender_mock IMPLEMENTATION.
METHOD zif_email_sender~send_email.
mv_was_called = abap_true.
rv_success = mv_return_value.
ENDMETHOD.
METHOD set_return_value.
mv_return_value = iv_value.
ENDMETHOD.
METHOD was_send_email_called.
rv_called = mv_was_called.
ENDMETHOD.
ENDCLASS.

8. Code Coverage

Mesurer la couverture

Dans ADT :

  1. Clic droit sur la classe → Coverage AsABAP Unit Test
  2. Le rapport de couverture s’ouvre

Objectif : >70% pour les classes critiques, >80% pour la logique métier

Interpréter la couverture

CouvertureQualitéAction
<50%InsuffisantÉcrire plus de tests !
50-70%AcceptableTester les chemins critiques
70-90%BonCompléter les cas limites
>90%ExcellentMaintenir

Important : 100% de couverture ne signifie pas des tests parfaits !


9. Développement piloté par les tests (TDD)

Cycle Red-Green-Refactor

1. RED : Écrire le test (qui échoue)
2. GREEN : Écrire le code minimal (le test passe)
3. REFACTOR : Améliorer le code (les tests restent verts)
Répéter

Exemple de session TDD

1. RED : Écrire le test

METHOD test_calculate_discount.
" Given
DATA(lv_amount) = 1000.
" When
DATA(lv_discount) = zcl_pricing=>calculate_discount( lv_amount ).
" Then
cl_abap_unit_assert=>assert_equals(
act = lv_discount
exp = 100 " 10% de remise
).
ENDMETHOD.

Exécuter le test : Échoue (la méthode n’existe pas)

2. GREEN : Implémentation minimale

CLASS zcl_pricing IMPLEMENTATION.
METHOD calculate_discount.
rv_discount = iv_amount * '0.1'.
ENDMETHOD.
ENDCLASS.

Exécuter le test : Passe

3. REFACTOR : Améliorer

METHOD calculate_discount.
CONSTANTS lc_discount_rate TYPE p LENGTH 3 DECIMALS 2 VALUE '0.1'.
IF iv_amount <= 0.
RAISE EXCEPTION TYPE zcx_invalid_amount.
ENDIF.
rv_discount = iv_amount * lc_discount_rate.
ENDMETHOD.

Exécuter les tests : Toujours verts !

4. Nouveau test : Cas limite

METHOD test_discount_negative_amount.
TRY.
zcl_pricing=>calculate_discount( -100 ).
cl_abap_unit_assert=>fail( 'Exception attendue' ).
CATCH zcx_invalid_amount.
" Attendu
ENDTRY.
ENDMETHOD.

10. Bonnes pratiques

À FAIRE

  1. Écrire les tests en premier (TDD)
  2. Une assertion par test (Responsabilité unique)
  3. Noms de tests parlants (test_discount_for_premium_customer)
  4. Utiliser le pattern Given-When-Then
  5. Tests indépendants (pas de dépendance à l’ordre)
  6. Tests rapides (<1s par test)
  7. Données de test réalistes (mais anonymisées)

À NE PAS FAIRE

  1. Tester avec des données de production (uniquement des données de test !)
  2. Utiliser Sleep/Wait
  3. DB commits dans les tests (si évitable)
  4. Désactiver les tests (commentés)
  5. Logique de test complexe (les tests doivent être simples)

Résumé

Aide-mémoire ABAP Unit Testing :

AspectOutil/Pattern
Tests de basecl_abap_unit_assert
CDS Viewscl_cds_test_environment
RAPMODIFY/READ ENTITIES + COMMIT ENTITIES
MockingTest Doubles (classes Mock personnalisées)
CoverageOutil de couverture ADT (Objectif : >70%)
PatternGiven-When-Then
ApprocheDéveloppement piloté par les tests (TDD)

État d’esprit : Les tests sont un investissement, pas une perte de temps !


Voir aussi :

Bon testing !