ABAP Unit est le framework de test intégré pour les tests unitaires automatisés en ABAP. Il permet le développement piloté par les tests (TDD) et assure la qualité du code grâce à des tests automatisés et reproductibles.
Concept de base
- Les classes de test sont des classes locales avec l’attribut
FOR TESTING - Les méthodes de test sont des méthodes avec l’attribut
FOR TESTING - Les assertions vérifient les résultats attendus avec
CL_ABAP_UNIT_ASSERT - Les tests sont exécutés via CTRL+SHIFT+F10 ou le menu contextuel
Syntaxe
CLASS ltc_test_class DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. METHODS: test_method FOR TESTING.ENDCLASS.
CLASS ltc_test_class IMPLEMENTATION. METHOD test_method. " Logique de test avec assertions cl_abap_unit_assert=>assert_equals( act = actual_value exp = expected_value ). ENDMETHOD.ENDCLASS.Exemples
1. Test unitaire simple
" Classe à testerCLASS lcl_calculator DEFINITION. PUBLIC SECTION. METHODS: add IMPORTING iv_a TYPE i iv_b TYPE i RETURNING VALUE(rv_result) TYPE i.ENDCLASS.
CLASS lcl_calculator IMPLEMENTATION. METHOD add. rv_result = iv_a + iv_b. ENDMETHOD.ENDCLASS.
" Classe de testCLASS ltc_calculator DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA: mo_cut TYPE REF TO lcl_calculator. " CUT = Class Under Test
METHODS: setup, test_add_positive_numbers FOR TESTING, test_add_negative_numbers FOR TESTING, test_add_zero FOR TESTING.ENDCLASS.
CLASS ltc_calculator IMPLEMENTATION. METHOD setup. " Exécuté avant chaque test mo_cut = NEW #( ). ENDMETHOD.
METHOD test_add_positive_numbers. DATA(lv_result) = mo_cut->add( iv_a = 5 iv_b = 3 ).
cl_abap_unit_assert=>assert_equals( act = lv_result exp = 8 msg = 'L''addition de nombres positifs a échoué" ). ENDMETHOD.
METHOD test_add_negative_numbers. DATA(lv_result) = mo_cut->add( iv_a = -5 iv_b = -3 ).
cl_abap_unit_assert=>assert_equals( act = lv_result exp = -8 ). ENDMETHOD.
METHOD test_add_zero. DATA(lv_result) = mo_cut->add( iv_a = 10 iv_b = 0 ).
cl_abap_unit_assert=>assert_equals( act = lv_result exp = 10 ). ENDMETHOD.ENDCLASS.2. Méthodes d’assertion importantes
CLASS ltc_assertions DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. METHODS: test_assert_equals FOR TESTING, test_assert_true_false FOR TESTING, test_assert_initial FOR TESTING, test_assert_bound FOR TESTING, test_assert_differs FOR TESTING, test_assert_char_cp FOR TESTING, test_fail FOR TESTING.ENDCLASS.
CLASS ltc_assertions IMPLEMENTATION. METHOD test_assert_equals. " Comparer des valeurs cl_abap_unit_assert=>assert_equals( act = 42 exp = 42 msg = 'Les valeurs ne sont pas égales" ).
" Comparer des structures DATA: ls_act TYPE ty_person, ls_exp TYPE ty_person. ls_act = VALUE #( name = 'Max' age = 30 ). ls_exp = VALUE #( name = 'Max' age = 30 ).
cl_abap_unit_assert=>assert_equals( act = ls_act exp = ls_exp ).
" Comparer des tables DATA: lt_act TYPE TABLE OF string, lt_exp TYPE TABLE OF string. lt_act = VALUE #( ( `A` ) ( `B` ) ). lt_exp = VALUE #( ( `A` ) ( `B` ) ).
cl_abap_unit_assert=>assert_equals( act = lt_act exp = lt_exp ). ENDMETHOD.
METHOD test_assert_true_false. DATA: lv_flag TYPE abap_bool VALUE abap_true.
" Vérifier si vrai cl_abap_unit_assert=>assert_true( act = lv_flag msg = 'Le flag devrait être vrai" ).
" Vérifier si faux cl_abap_unit_assert=>assert_false( act = xsdbool( 1 = 2 ) msg = 'L''expression devrait être fausse" ). ENDMETHOD.
METHOD test_assert_initial. DATA: lv_empty TYPE string, lv_filled TYPE string VALUE 'Test'.
" Vérifier si initial (vide) cl_abap_unit_assert=>assert_initial( act = lv_empty msg = 'La variable devrait être initiale" ).
" Vérifier si NON initial cl_abap_unit_assert=>assert_not_initial( act = lv_filled msg = 'La variable ne devrait pas être initiale" ). ENDMETHOD.
METHOD test_assert_bound. DATA: lo_object TYPE REF TO lcl_calculator, lo_null TYPE REF TO lcl_calculator.
lo_object = NEW #( ).
" Vérifier si la référence est liée cl_abap_unit_assert=>assert_bound( act = lo_object msg = 'La référence d''objet devrait être liée" ).
" Vérifier si NON liée cl_abap_unit_assert=>assert_not_bound( act = lo_null msg = 'La référence d''objet devrait être NULL" ). ENDMETHOD.
METHOD test_assert_differs. " Vérifier que les valeurs sont différentes cl_abap_unit_assert=>assert_differs( act = 'ABC" exp = 'XYZ" msg = 'Les valeurs devraient être différentes" ). ENDMETHOD.
METHOD test_assert_char_cp. " Pattern-Matching (comme CP dans IF) cl_abap_unit_assert=>assert_char_cp( act = 'Hello World" exp = 'Hello*" msg = 'La chaîne devrait commencer par Hello" ). ENDMETHOD.
METHOD test_fail. " Faire échouer le test intentionnellement IF 1 = 2. cl_abap_unit_assert=>fail( msg = 'Ce code ne devrait jamais être atteint" ). ENDIF. ENDMETHOD.ENDCLASS.3. Setup et Teardown
CLASS ltc_lifecycle DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. CLASS-DATA: gv_class_setup_done TYPE abap_bool. DATA: mv_setup_done TYPE abap_bool.
CLASS-METHODS: class_setup, " Une fois avant tous les tests class_teardown. " Une fois après tous les tests
METHODS: setup, " Avant chaque test teardown, " Après chaque test test_first FOR TESTING, test_second FOR TESTING.ENDCLASS.
CLASS ltc_lifecycle IMPLEMENTATION. METHOD class_setup. " Initialisation unique pour tous les tests " ex. Créer des données de test en DB gv_class_setup_done = abap_true. ENDMETHOD.
METHOD class_teardown. " Nettoyage après tous les tests " ex. Supprimer les données de test de la DB ENDMETHOD.
METHOD setup. " Avant CHAQUE cas de test " ex. Réinstancier l'objet mv_setup_done = abap_true. ENDMETHOD.
METHOD teardown. " Après CHAQUE cas de test " ex. Réinitialiser les variables CLEAR mv_setup_done. ENDMETHOD.
METHOD test_first. cl_abap_unit_assert=>assert_true( gv_class_setup_done ). cl_abap_unit_assert=>assert_true( mv_setup_done ). ENDMETHOD.
METHOD test_second. cl_abap_unit_assert=>assert_true( gv_class_setup_done ). cl_abap_unit_assert=>assert_true( mv_setup_done ). ENDMETHOD.ENDCLASS.4. Tests d’exceptions
CLASS lcl_validator DEFINITION. PUBLIC SECTION. METHODS: validate_age IMPORTING iv_age TYPE i RAISING cx_parameter_invalid.ENDCLASS.
CLASS lcl_validator IMPLEMENTATION. METHOD validate_age. IF iv_age < 0. RAISE EXCEPTION TYPE cx_parameter_invalid EXPORTING parameter = 'AGE'. ENDIF. ENDMETHOD.ENDCLASS.
CLASS ltc_validator DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA: mo_cut TYPE REF TO lcl_validator.
METHODS: setup, test_valid_age FOR TESTING, test_negative_age_raises FOR TESTING.ENDCLASS.
CLASS ltc_validator IMPLEMENTATION. METHOD setup. mo_cut = NEW #( ). ENDMETHOD.
METHOD test_valid_age. " Ne devrait pas lever d'exception TRY. mo_cut->validate_age( 25 ). CATCH cx_parameter_invalid. cl_abap_unit_assert=>fail( 'Exception inattendue' ). ENDTRY. ENDMETHOD.
METHOD test_negative_age_raises. " Exception attendue TRY. mo_cut->validate_age( -5 ). cl_abap_unit_assert=>fail( 'Exception attendue' ). CATCH cx_parameter_invalid INTO DATA(lx_error). " Exception attendue - test réussi cl_abap_unit_assert=>assert_equals( act = lx_error->parameter exp = 'AGE" ). ENDTRY. ENDMETHOD.ENDCLASS.5. Test Doubles (Mocking)
" Interface pour l'injection de dépendancesINTERFACE lif_database. METHODS: get_customer IMPORTING iv_id TYPE i RETURNING VALUE(rs_customer) TYPE ty_customer.ENDINTERFACE.
" Implémentation de productionCLASS lcl_database DEFINITION. PUBLIC SECTION. INTERFACES: lif_database.ENDCLASS.
" Classe à testerCLASS lcl_customer_service DEFINITION. PUBLIC SECTION. METHODS: constructor IMPORTING io_db TYPE REF TO lif_database. METHODS: get_customer_name IMPORTING iv_id TYPE i RETURNING VALUE(rv_name) TYPE string. PRIVATE SECTION. DATA: mo_db TYPE REF TO lif_database.ENDCLASS.
CLASS lcl_customer_service IMPLEMENTATION. METHOD constructor. mo_db = io_db. ENDMETHOD.
METHOD get_customer_name. DATA(ls_customer) = mo_db->get_customer( iv_id ). rv_name = ls_customer-name. ENDMETHOD.ENDCLASS.
" Test Double (Mock)CLASS ltd_database DEFINITION FOR TESTING. PUBLIC SECTION. INTERFACES: lif_database. DATA: ms_mock_customer TYPE ty_customer.ENDCLASS.
CLASS ltd_database IMPLEMENTATION. METHOD lif_database~get_customer. " Retourne des données mock au lieu d'un accès DB rs_customer = ms_mock_customer. ENDMETHOD.ENDCLASS.
" Classe de test avec MockCLASS ltc_customer_service DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA: mo_cut TYPE REF TO lcl_customer_service, mo_mock_db TYPE REF TO ltd_database.
METHODS: setup, test_get_customer_name FOR TESTING.ENDCLASS.
CLASS ltc_customer_service IMPLEMENTATION. METHOD setup. " Créer le mock mo_mock_db = NEW #( ). mo_mock_db->ms_mock_customer = VALUE #( id = 1 name = 'Client Test" ).
" Injecter le mock dans la CUT mo_cut = NEW #( io_db = mo_mock_db ). ENDMETHOD.
METHOD test_get_customer_name. DATA(lv_name) = mo_cut->get_customer_name( 1 ).
cl_abap_unit_assert=>assert_equals( act = lv_name exp = 'Client Test" ). ENDMETHOD.ENDCLASS.6. Attributs de test
CLASS ltc_attributes DEFINITION FOR TESTING DURATION MEDIUM " SHORT | MEDIUM | LONG RISK LEVEL DANGEROUS. " HARMLESS | DANGEROUS | CRITICAL
PRIVATE SECTION. METHODS: " Catégorisation test_quick FOR TESTING,
" Ignorer le test test_not_yet_implemented FOR TESTING.ENDCLASS.
CLASS ltc_attributes IMPLEMENTATION. METHOD test_quick. " Test normal cl_abap_unit_assert=>assert_true( abap_true ). ENDMETHOD.
METHOD test_not_yet_implemented. " Marquer le test comme "pas encore implémenté" cl_abap_unit_assert=>skip( 'Pas encore implémenté' ). ENDMETHOD.ENDCLASS.7. Données de test avec méthodes helper
CLASS ltc_with_helpers DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. METHODS: test_process_orders FOR TESTING, " Méthodes helper (pas FOR TESTING) given_orders RETURNING VALUE(rt_orders) TYPE ty_orders, given_customer RETURNING VALUE(rs_customer) TYPE ty_customer.ENDCLASS.
CLASS ltc_with_helpers IMPLEMENTATION. METHOD test_process_orders. " Given DATA(lt_orders) = given_orders( ). DATA(ls_customer) = given_customer( ).
" When DATA(lv_result) = process( lt_orders ).
" Then cl_abap_unit_assert=>assert_equals( act = lv_result exp = 'OK" ). ENDMETHOD.
METHOD given_orders. rt_orders = VALUE #( ( id = 1 amount = 100 ) ( id = 2 amount = 200 ) ). ENDMETHOD.
METHOD given_customer. rs_customer = VALUE #( id = 1 name = 'Test' ). ENDMETHOD.ENDCLASS.8. Tests de méthodes privées
" Classe à testerCLASS lcl_processor DEFINITION FRIENDS ltc_processor. " Déclarer la classe de test comme friend
PUBLIC SECTION. METHODS: process RETURNING VALUE(rv_result) TYPE string.
PRIVATE SECTION. METHODS: calculate_internal RETURNING VALUE(rv_value) TYPE i.ENDCLASS.
" La classe de test peut accéder aux méthodes privéesCLASS ltc_processor DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA: mo_cut TYPE REF TO lcl_processor.
METHODS: setup, test_calculate_internal FOR TESTING.ENDCLASS.
CLASS ltc_processor IMPLEMENTATION. METHOD setup. mo_cut = NEW #( ). ENDMETHOD.
METHOD test_calculate_internal. " Accès direct à la méthode privée grâce à FRIENDS DATA(lv_value) = mo_cut->calculate_internal( ).
cl_abap_unit_assert=>assert_equals( act = lv_value exp = 42 ). ENDMETHOD.ENDCLASS.9. SQL Test Double Framework
CLASS ltc_with_sql_double DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. CLASS-DATA: mo_environment TYPE REF TO if_osql_test_environment.
CLASS-METHODS: class_setup, class_teardown.
METHODS: setup, test_read_customer FOR TESTING.ENDCLASS.
CLASS ltc_with_sql_double IMPLEMENTATION. METHOD class_setup. " Créer l'environnement de test pour les tables DB mo_environment = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( 'KNA1' ) ) ). ENDMETHOD.
METHOD class_teardown. mo_environment->destroy( ). ENDMETHOD.
METHOD setup. " Insérer les données de test DATA: lt_kna1 TYPE TABLE OF kna1.
lt_kna1 = VALUE #( ( mandt = sy-mandt kunnr = '0000001000' name1 = 'Client Test' ) ).
mo_environment->insert_test_data( lt_kna1 ). ENDMETHOD.
METHOD test_read_customer. " Le SELECT lit maintenant depuis le Test Double au lieu de la vraie DB SELECT SINGLE name1 FROM kna1 WHERE kunnr = '0000001000" INTO @DATA(lv_name).
cl_abap_unit_assert=>assert_equals( act = lv_name exp = 'Client Test" ). ENDMETHOD.ENDCLASS.Exécution des tests
" Dans Eclipse/ADT :CTRL + SHIFT + F10 " Exécuter tous les testsClic droit → Run As → ABAP Unit Test
" Dans SE80 :Clic droit → Exécuter le test unitaireDURATION et RISK LEVEL
| DURATION | Durée d’exécution attendue |
|---|---|
| SHORT | < 1 seconde |
| MEDIUM | < 5 secondes |
| LONG | > 5 secondes |
| RISK LEVEL | Description |
|---|---|
| HARMLESS | Aucune modification de base de données |
| DANGEROUS | Modifications possibles des données de test |
| CRITICAL | Les données de production pourraient être affectées |
Conseils importants / Bonnes pratiques
- Classes de test dans les classes locales (définition/implémentation à la fin de la classe).
- Nommage :
ltc_pour les classes de test,ltd_pour les Test Doubles,test_pour les méthodes. - Pattern AAA : Arrange (Given), Act (When), Assert (Then).
- Une assertion par test - des tests focalisés sont plus faciles à maintenir.
- Setup/Teardown pour l’initialisation/nettoyage répété.
- Injection de dépendances pour une architecture testable avec des mocks.
- FRIENDS permet de tester les méthodes privées (à utiliser avec parcimonie).
cl_abap_unit_assert=>skip()pour les tests pas encore implémentés.- SQL Test Double Framework pour des tests indépendants de la base de données.
- Exécuter les tests régulièrement - idéalement dans le pipeline CI/CD.