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 :
- Clic droit sur la classe →
Run As→ABAP Unit Test - 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 valeurcl_abap_unit_assert=>assert_equals( act = lv_actual exp = lv_expected msg = 'Les valeurs devraient correspondre").
" Égalité d'objetcl_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/Falsecl_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 tablecl_abap_unit_assert=>assert_table_contains( table = lt_result line = ls_expected_line).
" Nombre de lignesDATA(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 productionCLASS 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 mockCLASS 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 :
- Clic droit sur la classe →
Coverage As→ABAP Unit Test - Le rapport de couverture s’ouvre
Objectif : >70% pour les classes critiques, >80% pour la logique métier
Interpréter la couverture
| Couverture | Qualité | Action |
|---|---|---|
| <50% | Insuffisant | Écrire plus de tests ! |
| 50-70% | Acceptable | Tester les chemins critiques |
| 70-90% | Bon | Compléter les cas limites |
| >90% | Excellent | Maintenir |
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éterExemple 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
- Écrire les tests en premier (TDD)
- Une assertion par test (Responsabilité unique)
- Noms de tests parlants (
test_discount_for_premium_customer) - Utiliser le pattern Given-When-Then
- Tests indépendants (pas de dépendance à l’ordre)
- Tests rapides (<1s par test)
- Données de test réalistes (mais anonymisées)
À NE PAS FAIRE
- Tester avec des données de production (uniquement des données de test !)
- Utiliser Sleep/Wait
- DB commits dans les tests (si évitable)
- Désactiver les tests (commentés)
- Logique de test complexe (les tests doivent être simples)
Résumé
Aide-mémoire ABAP Unit Testing :
| Aspect | Outil/Pattern |
|---|---|
| Tests de base | cl_abap_unit_assert |
| CDS Views | cl_cds_test_environment |
| RAP | MODIFY/READ ENTITIES + COMMIT ENTITIES |
| Mocking | Test Doubles (classes Mock personnalisées) |
| Coverage | Outil de couverture ADT (Objectif : >70%) |
| Pattern | Given-When-Then |
| Approche | Développement piloté par les tests (TDD) |
État d’esprit : Les tests sont un investissement, pas une perte de temps !
Voir aussi :
- RAP Tutorial Partie 3 : Bonnes pratiques
- Optimisation des performances ABAP Cloud
- ABAP Cloud Cheat Sheet
Bon testing !