ABAP Unit Testing en la nube: Tests en ABAP Cloud

Kategorie
ABAP-Statements
Veröffentlicht
Autor
Johannes

El testing unitario en ABAP Cloud sigue los mismos principios que en ABAP clásico, pero con algunas consideraciones especiales para el entorno cloud y RAP.

Estructura básica de test

CLASS ltcl_my_class DEFINITION FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA: mo_cut TYPE REF TO zcl_my_class. " Class Under Test
METHODS: setup.
METHODS: test_calculate_price FOR TESTING.
METHODS: test_validate_input FOR TESTING.
ENDCLASS.
CLASS ltcl_my_class IMPLEMENTATION.
METHOD setup.
mo_cut = NEW #( ).
ENDMETHOD.
METHOD test_calculate_price.
" Given
DATA(lv_quantity) = 10.
DATA(lv_unit_price) = CONV decfloat34( '25.50' ).
" When
DATA(lv_result) = mo_cut->calculate_price(
iv_quantity = lv_quantity
iv_unit_price = lv_unit_price
).
" Then
cl_abap_unit_assert=>assert_equals(
exp = CONV decfloat34( '255.00' )
act = lv_result
msg = 'El precio calculado es incorrecto'
).
ENDMETHOD.
METHOD test_validate_input.
" Given
DATA(lv_invalid_input) = ''.
" When/Then - Esperar excepción
TRY.
mo_cut->validate( lv_invalid_input ).
cl_abap_unit_assert=>fail( 'Se esperaba una excepción' ).
CATCH zcx_validation_error INTO DATA(lx_error).
cl_abap_unit_assert=>assert_bound( lx_error ).
ENDTRY.
ENDMETHOD.
ENDCLASS.

Test Doubles en ABAP Cloud

Interface para inyección de dependencias

" Interface para abstracción de base de datos
INTERFACE zif_order_repository PUBLIC.
METHODS: get_order
IMPORTING iv_order_id TYPE sysuuid_x16
RETURNING VALUE(rs_order) TYPE zorder.
METHODS: save_order
IMPORTING is_order TYPE zorder.
ENDINTERFACE.
" Implementación real
CLASS zcl_order_repository DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES: zif_order_repository.
ENDCLASS.
CLASS zcl_order_repository IMPLEMENTATION.
METHOD zif_order_repository~get_order.
SELECT SINGLE * FROM zorders
WHERE order_id = @iv_order_id
INTO @rs_order.
ENDMETHOD.
METHOD zif_order_repository~save_order.
MODIFY zorders FROM @is_order.
ENDMETHOD.
ENDCLASS.

Test Double manual

" Test Double
CLASS ltcl_order_repo_double DEFINITION FOR TESTING.
PUBLIC SECTION.
INTERFACES: zif_order_repository.
DATA: ms_return_order TYPE zorder,
mt_saved_orders TYPE TABLE OF zorder.
ENDCLASS.
CLASS ltcl_order_repo_double IMPLEMENTATION.
METHOD zif_order_repository~get_order.
rs_order = ms_return_order.
ENDMETHOD.
METHOD zif_order_repository~save_order.
APPEND is_order TO mt_saved_orders.
ENDMETHOD.
ENDCLASS.
" Test usando el double
CLASS ltcl_order_service DEFINITION FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA: mo_cut TYPE REF TO zcl_order_service,
mo_repo_mock TYPE REF TO ltcl_order_repo_double.
METHODS: setup.
METHODS: test_process_order FOR TESTING.
ENDCLASS.
CLASS ltcl_order_service IMPLEMENTATION.
METHOD setup.
mo_repo_mock = NEW #( ).
mo_cut = NEW #( io_repository = mo_repo_mock ).
ENDMETHOD.
METHOD test_process_order.
" Given - Configurar mock
mo_repo_mock->ms_return_order = VALUE #(
order_id = '12345'
status = 'NUEVO'
amount = '100.00'
).
" When
mo_cut->process_order( '12345' ).
" Then - Verificar que se guardó
cl_abap_unit_assert=>assert_equals(
exp = 1
act = lines( mo_repo_mock->mt_saved_orders )
msg = 'Se debería haber guardado un pedido'
).
cl_abap_unit_assert=>assert_equals(
exp = 'PROCESADO'
act = mo_repo_mock->mt_saved_orders[ 1 ]-status
).
ENDMETHOD.
ENDCLASS.

SQL Test Double Framework

CLASS ltcl_with_sql_double DEFINITION FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA: mo_environment TYPE REF TO if_cds_test_environment.
CLASS-METHODS: class_setup.
CLASS-METHODS: class_teardown.
METHODS: setup.
METHODS: test_get_active_orders FOR TESTING.
ENDCLASS.
CLASS ltcl_with_sql_double IMPLEMENTATION.
METHOD class_setup.
" Crear entorno de test para la CDS View
mo_environment = cl_cds_test_environment=>create( i_for_entity = 'ZI_ORDER' ).
ENDMETHOD.
METHOD class_teardown.
mo_environment->destroy( ).
ENDMETHOD.
METHOD setup.
" Limpiar datos de test antes de cada test
mo_environment->clear_doubles( ).
ENDMETHOD.
METHOD test_get_active_orders.
" Given - Insertar datos de test
DATA(lt_test_data) = VALUE ztab_order(
( order_id = '001' status = 'ACTIVO' amount = '100.00' )
( order_id = '002' status = 'CERRADO' amount = '200.00' )
( order_id = '003' status = 'ACTIVO' amount = '150.00' )
).
mo_environment->insert_test_data( lt_test_data ).
" When
SELECT * FROM zi_order
WHERE status = 'ACTIVO'
INTO TABLE @DATA(lt_result).
" Then
cl_abap_unit_assert=>assert_equals(
exp = 2
act = lines( lt_result )
msg = 'Deberían haber 2 pedidos activos'
).
ENDMETHOD.
ENDCLASS.

Testing de RAP Business Objects

Test de Behavior Implementation

CLASS ltcl_rap_behavior DEFINITION FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA: mo_environment TYPE REF TO if_cds_test_environment,
mo_bo_test TYPE REF TO if_rap_bo_test_run.
CLASS-METHODS: class_setup.
CLASS-METHODS: class_teardown.
METHODS: setup.
METHODS: test_create_order FOR TESTING.
METHODS: test_validation_fails FOR TESTING.
ENDCLASS.
CLASS ltcl_rap_behavior IMPLEMENTATION.
METHOD class_setup.
" Crear entorno para las entidades RAP
mo_environment = cl_cds_test_environment=>create_for_multiple_cds(
i_for_entities = VALUE #(
( i_for_entity = 'ZI_ORDER' )
( i_for_entity = 'ZI_ORDERITEM' )
)
).
" Crear test runner para RAP BO
mo_bo_test = cl_rap_bo_test_run=>create( 'ZI_ORDER' ).
ENDMETHOD.
METHOD class_teardown.
mo_environment->destroy( ).
ENDMETHOD.
METHOD setup.
mo_environment->clear_doubles( ).
mo_bo_test->clear( ).
ENDMETHOD.
METHOD test_create_order.
" Given/When - Ejecutar CREATE
MODIFY ENTITIES OF zi_order
ENTITY Order
CREATE FIELDS ( customer_id description )
WITH VALUE #( (
%cid = 'CID_1'
customer_id = '1000'
description = 'Test Order'
) )
MAPPED DATA(ls_mapped)
FAILED DATA(ls_failed)
REPORTED DATA(ls_reported).
" Then
cl_abap_unit_assert=>assert_initial(
act = ls_failed-order
msg = 'Create debería ser exitoso'
).
cl_abap_unit_assert=>assert_not_initial(
act = ls_mapped-order
msg = 'Mapped debería tener entrada'
).
ENDMETHOD.
METHOD test_validation_fails.
" Given - Datos inválidos
" When
MODIFY ENTITIES OF zi_order
ENTITY Order
CREATE FIELDS ( customer_id description )
WITH VALUE #( (
%cid = 'CID_1'
customer_id = '' " Vacío - debería fallar validación
description = 'Test'
) )
MAPPED DATA(ls_mapped)
FAILED DATA(ls_failed)
REPORTED DATA(ls_reported).
" Then
cl_abap_unit_assert=>assert_not_initial(
act = ls_failed-order
msg = 'Validación debería fallar'
).
ENDMETHOD.
ENDCLASS.

Assertions útiles

" Igualdad
cl_abap_unit_assert=>assert_equals(
exp = expected_value
act = actual_value
msg = 'Los valores deberían ser iguales'
).
" Tabla tiene n entradas
cl_abap_unit_assert=>assert_equals(
exp = 5
act = lines( lt_result )
).
" No inicial
cl_abap_unit_assert=>assert_not_initial(
act = lv_result
msg = 'El resultado no debería ser inicial'
).
" Inicial
cl_abap_unit_assert=>assert_initial(
act = lt_errors
msg = 'No debería haber errores'
).
" Bound (referencia)
cl_abap_unit_assert=>assert_bound(
act = lo_instance
msg = 'La instancia debería estar bound'
).
" True/False
cl_abap_unit_assert=>assert_true(
act = lv_is_valid
msg = 'Debería ser válido'
).
cl_abap_unit_assert=>assert_false(
act = lv_has_errors
msg = 'No debería tener errores'
).
" Forzar fallo
cl_abap_unit_assert=>fail( 'Este código no debería ejecutarse' ).
" Contiene texto
cl_abap_unit_assert=>assert_char_cp(
exp = '*error*'
act = lv_message
msg = 'El mensaje debería contener error'
).
" Tabla contiene línea
cl_abap_unit_assert=>assert_table_contains(
table = lt_result
line = ls_expected_line
).

Configuración de test

Niveles de riesgo

" HARMLESS - Sin efectos secundarios
CLASS ltcl_test DEFINITION FOR TESTING
RISK LEVEL HARMLESS.
" DANGEROUS - Puede modificar datos
CLASS ltcl_test DEFINITION FOR TESTING
RISK LEVEL DANGEROUS.
" CRITICAL - Puede afectar al sistema
CLASS ltcl_test DEFINITION FOR TESTING
RISK LEVEL CRITICAL.

Duración

" SHORT - Máx. 60 segundos
CLASS ltcl_test DEFINITION FOR TESTING
DURATION SHORT.
" MEDIUM - Máx. 300 segundos
CLASS ltcl_test DEFINITION FOR TESTING
DURATION MEDIUM.
" LONG - Sin límite
CLASS ltcl_test DEFINITION FOR TESTING
DURATION LONG.

Notas importantes / Mejores prácticas

  • Usa inyección de dependencias para facilitar el testing.
  • Crea Test Doubles para aislar la unidad bajo test.
  • El SQL Test Double Framework permite testear sin base de datos real.
  • Para RAP, usa cl_rap_bo_test_run para tests de comportamiento.
  • Sigue el patrón Given-When-Then para estructurar tests.
  • Usa RISK LEVEL HARMLESS para tests que no modifican datos.
  • Ejecuta tests con DURATION SHORT para feedback rápido.
  • Integra tests en el ABAP Test Cockpit.