İçeriğe geç

ABAP Unit ile Test Yazmak: Test Edilebilir ABAP Tasarımı

Test yazamamanızın sebebi ABAP Unit değil, kodun tasarımıdır. Bağımlılıkları ayırmaktan test double'lara, gerçek kod örnekleriyle adım adım.

Mustafa Önder Mustafa Önder  ·  19 Eylül 2026  ·  14 dk okuma

İçindekiler

  1. ABAP Tarafında Neden Test Yazılmıyor?
  2. İlk Test: Test Sınıfının Anatomisi
  3. Doğru Assertion'ı Seçmek
  4. Asıl Sorun Test Değil, Tasarım
  5. Bağımlılığı Dışarı Almak: Arayüz ve Enjeksiyon
  6. ABAP Test Double Framework
  7. Veritabanına Dokunan Kodu Test Etmek
  8. Mevcut Legacy Kod: TEST-SEAM ve Sınırları
  9. Testleri Çalıştırmak, Kapsam ve ATC
  10. Sahada İşe Yarayan Kurallar
  11. Sonuç

ABAP Unit, SAP sistemlerinde uzun yıllardır var. Buna rağmen pek çok kurumsal ABAP deposunda tek bir test sınıfı bulunmaz. Sebebi çoğunlukla isteksizlik değil: mevcut kod test edilebilecek şekilde yazılmadığı için ilk testi yazmaya kalkan geliştirici duvara çarpar ve vazgeçer.

Bu yazıda önce ABAP Unit'in mekaniğini, sonra asıl meseleyi — kodu test edilebilir hâle getiren tasarım kararlarını — gerçek kod örnekleriyle anlatıyorum. Clean ABAP stil kılavuzunu okuduysanız bu yazı onun pratik devamı sayılır.

ABAP Tarafında Neden Test Yazılmıyor?

Sahada duyduğum gerekçeler neredeyse hep aynı dört başlıkta toplanıyor:

  • "Kodumuz veritabanına bağlı, test edemem." Doğru teşhis, yanlış sonuç. Sorun testin imkânsızlığı değil, iş mantığının SELECT ile aynı metoda gömülmüş olması.
  • "Test yazacak zamanım yok." Zaman zaten harcanıyor — SE38'den programı elle çalıştırıp ekrandaki sonuca bakarak. Fark, o kontrolün tekrar kullanılamaz olması.
  • "Transport sonrası zaten test ediliyor." Manuel kullanıcı testi regresyonu yakalamaz. Altı ay sonra aynı metoda dokunduğunuzda kimse eski senaryoyu hatırlamaz.
  • "Eski kodun tamamını test etmek imkânsız." Bu doğru ve kimse bunu istemiyor. Hedef, dokunduğunuz yeri test etmek.

Testin asıl getirisi hata bulmak değil; kodu korkusuzca değiştirebilmektir. S/4HANA geçişinde custom code uyarlaması yapan ekiplerin en pahalı sorunu tam olarak budur: dokunmaya korktukları için baştan yazmak zorunda kaldıkları modüller.

İlk Test: Test Sınıfının Anatomisi

ABAP Unit testleri, test ettikleri global sınıfın Test Classes include'unda yerel sınıf olarak yaşar (Eclipse ADT'de sınıfı açıp alttaki Test Classes sekmesine geçersiniz). Böylece üretim kodu ile aynı transport'ta taşınır ve üretim paketini kirletmez.

Test edeceğimiz basit bir sınıfla başlayalım:

CLASS zcl_discount_calculator DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS calculate
      IMPORTING order_value     TYPE netwr_ap
                customer_group  TYPE kdgrp
      RETURNING VALUE(discount) TYPE netwr_ap.

ENDCLASS.

Test sınıfı şöyle görünür:

CLASS ltcl_discount_calculator DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    " cut = code under test (test edilen nesne)
    DATA cut TYPE REF TO zcl_discount_calculator.

    METHODS setup.
    METHODS esik_altinda_iskonto_yok    FOR TESTING.
    METHODS anahtar_musteride_yuzde_on  FOR TESTING.
ENDCLASS.

CLASS ltcl_discount_calculator IMPLEMENTATION.

  METHOD setup.
    cut = NEW zcl_discount_calculator( ).
  ENDMETHOD.

  METHOD esik_altinda_iskonto_yok.
    cl_abap_unit_assert=>assert_equals(
      act = cut->calculate( order_value = '999' customer_group = '01' )
      exp = 0
      msg = 'Esik altindaki siparise iskonto uygulanmamali' ).
  ENDMETHOD.

  METHOD anahtar_musteride_yuzde_on.
    cl_abap_unit_assert=>assert_equals(
      act = cut->calculate( order_value = '1000' customer_group = 'KA' )
      exp = '100'
      msg = 'Anahtar musteride %10 iskonto beklenir' ).
  ENDMETHOD.

ENDCLASS.

Üç yapı taşı var:

1. Sınıf başlığındaki iki zorunlu bildirim

RISK LEVELHARMLESS, DANGEROUS veya CRITICAL. HARMLESS, testin veritabanını ve sistem ayarlarını değiştirmediği anlamına gelir. Birim testleriniz her zaman bu seviyede olmalıdır.

DURATIONSHORT, MEDIUM veya LONG. Beklenen çalışma süresini bildirir; süre aşılırsa test uyarı üretir. Gerçek bir birim testi SHORT'tur (saniyenin altında).

2. Fixture metotları

setup her test metodundan önce, teardown her testten sonra çalışır. class_setup ve class_teardown ise sınıf başına bir kez çalışır — pahalı kurulumlar (örneğin veritabanı double'ı) oraya konur.

Her test metodu setup ile temiz bir başlangıç aldığı için testler birbirine bağımlı olmaz. Bu, testlerin çalışma sırasından bağımsız olmasının tek güvencesidir.

3. FOR TESTING metotları

FOR TESTING eklenen her private metot bir testtir. İsim, doğrulanan davranışı anlatmalıdır. test_01 gibi isimler, test kırmızıya döndüğünde hiçbir şey söylemez; esik_altinda_iskonto_yok ise hata raporunda doğrudan teşhis verir.

Kontrollü istisna fırlatabilecek kod çağırıyorsanız metodu FOR TESTING RAISING cx_static_check olarak bildirin; beklenmeyen istisna testi otomatik olarak hataya düşürür.

Doğru Assertion'ı Seçmek

Bütün doğrulamalar cl_abap_unit_assert sınıfı üzerinden yapılır. Sık kullandıklarım:

" Değer karşılaştırma — en çok kullanılan
cl_abap_unit_assert=>assert_equals(  act = ... exp = ... msg = '...' ).
cl_abap_unit_assert=>assert_differs( act = ... exp = ... msg = '...' ).

" Boş / dolu kontrolü
cl_abap_unit_assert=>assert_initial(     act = ... msg = '...' ).
cl_abap_unit_assert=>assert_not_initial( act = ... msg = '...' ).

" Referans kontrolü
cl_abap_unit_assert=>assert_bound( act = lo_instance msg = '...' ).

" Mantıksal
cl_abap_unit_assert=>assert_true(  act = ... msg = '...' ).
cl_abap_unit_assert=>assert_false( act = ... msg = '...' ).

" Koşulsuz hata — beklenen istisna gelmediğinde
cl_abap_unit_assert=>fail( 'Beklenen istisna atilmadi' ).

assert_equals iç tablolarda ve yapılarda da çalışır; satır satır karşılaştırmak için döngü yazmanıza gerek yoktur. İki iç tabloyu doğrudan karşılaştırabilirsiniz.

İstisna beklediğiniz durumun kalıbı şudur — testin, istisna gelmediğinde de başarısız olması kritiktir:

METHOD olmayan_siparis_istisna_atar.
  TRY.
      cut->read( '9999999999' ).
      cl_abap_unit_assert=>fail( 'Olmayan siparis icin istisna bekleniyordu' ).
    CATCH zcx_order_not_found.
      " beklenen davranış — test başarılı
  ENDTRY.
ENDMETHOD.

msg parametresini atlamayın. Gece çalışan bir ATC koşusunda testiniz kırıldığında elinizdeki tek bilgi o cümledir.

Asıl Sorun Test Değil, Tasarım

Yukarıdaki örnek kolaydı çünkü calculate metodu saf bir hesaplama. Gerçek hayattaki ABAP kodu genellikle şöyle görünür:

METHOD process_overdue_order.

  SELECT SINGLE * FROM vbak
    INTO @DATA(order)
    WHERE vbeln = @order_id.

  IF sy-datum > order-erdat + 30.
    CALL FUNCTION 'Z_SEND_REMINDER_MAIL'
      EXPORTING iv_vbeln = order-vbeln.

    UPDATE vbak SET zzstatus = 'X' WHERE vbeln = @order_id.
    COMMIT WORK.
  ENDIF.

ENDMETHOD.

Bu metot test edilemez. Sebebi ABAP Unit'in yetersizliği değil, metodun dört ayrı işi birden yapması:

  • Veri okuma (SELECT) — test ortamında o siparişin var olmasını gerektirir.
  • Sistem zamanı (sy-datum) — testin sonucu, çalıştırıldığı güne göre değişir. Bugün geçen test altı ay sonra kırılır.
  • Yan etki (mail gönderimi) — test her koştuğunda gerçekten mail gider.
  • Veri yazma (UPDATE + COMMIT) — testi DANGEROUS yapar ve normal client'larda koşamaz hâle getirir.

Çözüm testi zorlamak değil; kararı yan etkilerden ayırmaktır. Ortada bir iş kuralı var: "oluşturulma tarihinden 30 gün geçtiyse sipariş gecikmiştir". Bu kural saf bir fonksiyon olarak yazılabilir ve tek başına test edilebilir. Okuma, yazma ve mail gönderme onun etrafındaki ince bir kabuk olarak kalır.

Bağımlılığı Dışarı Almak: Arayüz ve Enjeksiyon

Klasik ve ABAP'ta sorunsuz çalışan yöntem: bağımlılığı bir arayüzün arkasına koyup nesneye dışarıdan vermek (constructor injection).

Önce okuma sorumluluğu için bir arayüz:

INTERFACE zif_order_reader PUBLIC.

  TYPES: BEGIN OF ty_order,
           order_id  TYPE vbeln_va,
           created   TYPE erdat,
           net_value TYPE netwr_ap,
         END OF ty_order.

  METHODS read
    IMPORTING order_id     TYPE vbeln_va
    RETURNING VALUE(order) TYPE ty_order
    RAISING   zcx_order_not_found.

ENDINTERFACE.

Sistem zamanı da bir bağımlılıktır; onu da arayüzleştirin. Bu küçük hamle, tarih mantığı içeren testlerin yıllar sonra bile aynı sonucu vermesini sağlar:

INTERFACE zif_clock PUBLIC.
  METHODS today RETURNING VALUE(result) TYPE d.
ENDINTERFACE.

Karar sınıfı artık her iki bağımlılığı da dışarıdan alır ve hiçbir şey yazmaz:

CLASS zcl_overdue_policy DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS constructor
      IMPORTING order_reader TYPE REF TO zif_order_reader
                clock        TYPE REF TO zif_clock.

    METHODS is_overdue
      IMPORTING order_id      TYPE vbeln_va
      RETURNING VALUE(result) TYPE abap_bool
      RAISING   zcx_order_not_found.

  PRIVATE SECTION.
    CONSTANTS c_grace_days TYPE i VALUE 30.
    DATA order_reader TYPE REF TO zif_order_reader.
    DATA clock        TYPE REF TO zif_clock.

ENDCLASS.

CLASS zcl_overdue_policy IMPLEMENTATION.

  METHOD constructor.
    me->order_reader = order_reader.
    me->clock        = clock.
  ENDMETHOD.

  METHOD is_overdue.
    DATA(order) = order_reader->read( order_id ).
    result = xsdbool( clock->today( ) > order-created + c_grace_days ).
  ENDMETHOD.

ENDCLASS.

Üretimde gerçek uygulamaları bir factory bağlar; testte ise sahte nesneleri siz verirsiniz. xsdbool ve NEW gibi yapıları tanımıyorsanız modern ABAP syntax yazısına göz atabilirsiniz.

ABAP Test Double Framework

Basit durumlarda el yazımı yerel bir double, framework'ten daha okunaklıdır. Yerel test double sınıfları geleneksel olarak ltd_ öneki alır:

CLASS ltd_fixed_clock DEFINITION FOR TESTING.
  PUBLIC SECTION.
    INTERFACES zif_clock.
    METHODS constructor IMPORTING fixed_date TYPE d.
  PRIVATE SECTION.
    DATA fixed_date TYPE d.
ENDCLASS.

CLASS ltd_fixed_clock IMPLEMENTATION.
  METHOD constructor.
    me->fixed_date = fixed_date.
  ENDMETHOD.
  METHOD zif_clock~today.
    result = fixed_date.
  ENDMETHOD.
ENDCLASS.

Metodu çok olan arayüzler için her metodu elle yazmak yorucudur. SAP'nin ABAP Test Double Framework'ü bu double'ı sizin için üretir:

METHOD gecikmis_siparis_isaretlenir.

  " 1) Arayüzden otomatik double üret
  DATA(order_reader) = CAST zif_order_reader(
    cl_abap_testdouble=>create( 'ZIF_ORDER_READER' ) ).

  " 2) Bir sonraki çağrının ne döndüreceğini yapılandır
  cl_abap_testdouble=>configure_call( order_reader
    )->returning( VALUE zif_order_reader=>ty_order(
                    order_id  = '0000004711'
                    created   = '20260801'
                    net_value = '2500' ) ).

  " 3) Yapılandırmayı kaydeden çağrı (henüz doğrulama değil)
  order_reader->read( '0000004711' ).

  " 4) Test edilen nesneyi double'larla kur
  DATA(cut) = NEW zcl_overdue_policy(
    order_reader = order_reader
    clock        = NEW ltd_fixed_clock( '20260919' ) ).

  " 5) Doğrula
  cl_abap_unit_assert=>assert_true(
    act = cut->is_overdue( '0000004711' )
    msg = '30 gunu asan siparis gecikmis sayilmali' ).

ENDMETHOD.

Üçüncü adım ilk bakışta kafa karıştırır: framework, hangi çağrıya hangi cevabın verileceğini, o çağrıyı bir kez yaparak öğrenir. Bu satır bir doğrulama değil, kayıt işlemidir.

İki yararlı ek:

  • İstisna simülasyonu: configure_call( double )->raise_exception( NEW zcx_order_not_found( ) ) ile hata yolunu test edebilirsiniz. Hata yolları çoğu zaman mutlu yoldan daha kritiktir ve gerçek sistemde tetiklemesi en zor olanlardır.
  • Çağrı beklentisi: ->and_expect( )->is_called_times( 1 ) ile bağımlılığın kaç kez çağrıldığını şart koşar, testin sonunda cl_abap_testdouble=>verify_expectations( double ) ile doğrularsınız. Bunu ölçülü kullanın: davranış yerine iç yapıyı doğrulayan testler, refactoring'i kolaylaştırmak yerine engeller.

Veritabanına Dokunan Kodu Test Etmek

Bağımlılıkları ayırdıktan sonra geriye gerçekten veritabanına giden ince katman kalır — yukarıdaki örnekte zif_order_reader'ın asıl uygulaması. Onu da test edebilirsiniz; SAP bunun için Open SQL Test Double Framework'ü sunar. Framework, belirttiğiniz tablolara giden SELECT'leri kesip sizin verdiğiniz test verisini döndürür. Gerçek tabloya hiç dokunulmaz, dolayısıyla test HARMLESS kalır:

CLASS ltcl_order_reader DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    CLASS-DATA sql_environment TYPE REF TO if_osql_test_environment.

    CLASS-METHODS class_setup.
    CLASS-METHODS class_teardown.
    METHODS setup.
    METHODS mevcut_siparis_okunur FOR TESTING RAISING cx_static_check.
ENDCLASS.

CLASS ltcl_order_reader IMPLEMENTATION.

  METHOD class_setup.
    " Pahalı kurulum: sınıf başına bir kez
    sql_environment = cl_osql_test_environment=>create(
      i_dependency_list = VALUE #( ( 'VBAK' ) ) ).
  ENDMETHOD.

  METHOD class_teardown.
    sql_environment->destroy( ).
  ENDMETHOD.

  METHOD setup.
    " Her testten önce double'ları boşalt — testler birbirini kirletmesin
    sql_environment->clear_doubles( ).
  ENDMETHOD.

  METHOD mevcut_siparis_okunur.
    DATA orders TYPE STANDARD TABLE OF vbak.

    orders = VALUE #( ( vbeln = '0000004711'
                        kunnr = '0000001000'
                        erdat = '20260801'
                        netwr = '2500'
                        waerk = 'EUR' ) ).

    sql_environment->insert_test_data( orders ).

    DATA(order) = NEW zcl_order_reader( )->read( '0000004711' ).

    cl_abap_unit_assert=>assert_equals(
      act = order-net_value
      exp = '2500'
      msg = 'Test verisinden okunan net tutar eslesmeli' ).
  ENDMETHOD.

ENDCLASS.

CDS View okuyan kod için kardeş framework cl_cds_test_environment'tır. i_for_entity ile hedef entity'yi verirsiniz; framework, o view'ın dayandığı alt tabloları otomatik olarak double'lar:

cds_environment = cl_cds_test_environment=>create(
  i_for_entity = 'ZI_SALESORDER' ).

Bu, CDS tabanlı OData V4 servisleri geliştirirken oldukça değerlidir: consumption view'ın filtre ve hesaplama mantığını, üretim verisine hiç bağlanmadan doğrulayabilirsiniz.

Mevcut Legacy Kod: TEST-SEAM ve Sınırları

Elinizde arayüz de sınıf da olmayan, üç bin satırlık bir rapor varsa yukarıdaki yolların hiçbiri hemen uygulanamaz. ABAP bu durum için TEST-SEAM sunar: üretim kodunda, testin içeriğini değiştirebileceği bir dikiş yeri açarsınız.

" Üretim kodu
METHOD read_config.
  TEST-SEAM config_read.
    SELECT SINGLE value FROM ztconfig
      INTO @result
      WHERE key = @key.
  END-TEST-SEAM.
ENDMETHOD.
" Test kodu
METHOD enjekte_edilen_config_doner.
  TEST-INJECTION config_read.
    result = 'X'.
  END-TEST-INJECTION.

  cl_abap_unit_assert=>assert_equals(
    act = cut->read_config( 'MAIL_ACTIVE' )
    exp = 'X'
    msg = 'Enjekte edilen yapilandirma degeri donmeli' ).
ENDMETHOD.

TEST-SEAM'i kullanmadan önce bilin

Üretim kodunda test izi bırakır. TEST-SEAM bloğu üretim kaynağının parçasıdır; tasarım bozukluğunu düzeltmez, üzerini örter.

ABAP Cloud'da kullanılamaz. ABAP for Cloud Development dil sürümünde TEST-SEAM serbest değildir. Bugün seam ile test ettiğiniz kod, clean core'a taşındığında yeniden ele alınmak zorunda kalır.

Doğru kullanımı geçicidir. Legacy kodu kırmadan ilk güvenlik ağını kurmak için makuldür. Ağ kurulduktan sonra bağımlılığı arayüzün arkasına taşıyıp seam'i kaldırın.

Testleri Çalıştırmak, Kapsam ve ATC

Eclipse ADT'de günlük kullanımınız iki kısayoldan ibaret olacak:

  • Ctrl + Shift + F10 — açık nesnenin testlerini çalıştırır. Aynı kısayolu paket üzerinde kullanırsanız o paketteki bütün testler koşar; bir modülün bütününü doğrulamanın en hızlı yolu budur.
  • Ctrl + Shift + F11 — testleri kapsam ölçümüyle çalıştırır. Editörde çalışan satırlar yeşil, hiç girilmeyenler kırmızı işaretlenir.

Kapsam (coverage) oranı hakkında dürüst olmak gerekirse: hedef değil, göstergedir. Yüksek kapsama sahip ama hiçbir şey doğrulamayan bir test takımı yazmak mümkündür. Faydalı okuma biçimi şudur — kapsam raporundaki kırmızı satırlar arasında kritik bir iş kuralı varsa oraya test yazın; getter/setter'ları yeşile boyamak için efor harcamayın.

Testlerin gerçekten koruma sağlaması için otomatik çalışmaları gerekir. İki pratik yol:

  • ATC entegrasyonu: ABAP Test Cockpit kontrol varyantına birim test kontrolünü ekleyip transport release'ine bağlayabilirsiniz. Kırık testi olan bir transport release edilemez. Bu, disiplinin kişilerin hatırlamasına bağlı kalmasını engelleyen tek mekanizmadır.
  • Planlı koşu: Kritik paketler için zamanlanmış bir Code Inspector koşusu kurun; sonucu ekibe düşsün. Sabah kırmızı bir satır görmek, üretimde hata görmekten ucuzdur.

Bir uyarı: DANGEROUS veya CRITICAL risk seviyesindeki testler, client ayarı açıkça izin vermedikçe çalışmaz. Bu kasıtlı bir korumadır; testi çalıştırmak için client açmak yerine testi HARMLESS hâle getirin.

Sahada İşe Yarayan Kurallar

Yeni yazdığınız kod için

Testi önce yazın, hiç değilse birlikte yazın. Kodu yazdıktan sonra test yazmaya oturduğunuzda, o kodun test edilemez olduğunu fark etmek için çok geçtir.

Bir testte tek davranış doğrulayın. Beş assertion içeren bir test kırıldığında hangi kuralın bozulduğunu anlamak için hata ayıklayıcıya girmeniz gerekir.

Test adı bir cümle olsun. Kırılan testin adı, raporu okuyan kişiye ne bozulduğunu doğrudan söylemelidir.

Mevcut kod için

İzci kuralı: Bütün depoyu test etmeye kalkmayın. Bir metoda hata düzeltmek için dokunduğunuzda önce o hatayı gösteren kırmızı bir test yazın, sonra düzeltin. Test yeşile döndüğünde hem hatayı çözmüş hem kalıcı bir regresyon kalkanı bırakmış olursunuz.

Önceliği iş kuralı yoğun sınıflara verin. Fiyatlandırma, iskonto, vergi, onay akışı gibi mantığın yoğunlaştığı ve sık değişen yerler en yüksek getiriyi sağlar. Ekran akışı ve ALV yapılandırması en düşüğünü.

Test kodunun kendisi için

Test kodu da üretim kodudur. Clean ABAP kuralları test sınıfında da geçerlidir; kopyala-yapıştır ile büyüyen test sınıfları bir süre sonra bakımı yapılmadığı için silinir.

Kırık testi devre dışı bırakmayın. Yorum satırına alınmış bir test, olmayan testten daha zararlıdır: var olmayan bir güvence hissi verir. Ya düzeltin ya silin.

Veritabanına, RFC'ye veya mail'e dokunan test birim testi değildir. Bu tür senaryolar entegrasyon testidir; ayrı bir sınıfa alın, DURATION MEDIUM verin ve her değişiklikte değil planlı koşularda çalıştırın.

Sonuç

ABAP Unit öğrenmesi yarım gün süren bir araçtır: bir yerel sınıf, iki bildirim, bir avuç assertion metodu. Zor olan kısım araç değil, kodu test edilebilir hâle getiren tasarımdır — iş kuralını veri erişiminden, sistem zamanından ve yan etkilerden ayırmak.

İyi haber şu: bu ayrıştırmayı yaptığınızda yalnız test kazanmazsınız. Bağımlılıkları arayüzün arkasına alınmış, tek sorumluluklu sınıflar zaten daha okunabilir, daha kolay değiştirilebilir ve ABAP Cloud'a taşınmaya daha hazırdır. Test yazma çabası, aslında tasarımı düzeltme çabasının faturasıdır — ve o fatura her durumda ödenir; testle şimdi, testsizse üretimde.

Bir sonraki dokunacağınız metotla başlayın. Tek bir test, sıfır testten kategorik olarak farklıdır.

Mevcut ABAP kod tabanınız için test stratejisi ve refactoring desteği alın.

Ön Görüşme Talep Et
← Blog'a Dön

İletişim

Projeleriniz için iletişime geçin.