天天看點

ABAP單元測試最佳實踐

 

  本文包含了我在開發項目中經曆過的實用的ABAP單元測試指導方針。我把它們安排成為問答的風格,歡迎任何人添加更多的Q&A's,以完成這個清單。

  • 在我的項目中,隻使用傳統的ABAP report。是以很不幸我不能使用ABAP單元測試了,是嗎?

    有個好消息:無論你正在使用哪一種ABAP代碼對象進行開發,都可以通過添加單元測試使得它更加穩定和更易于擴充。對于reports,子產品池(module pools)和函數組(function groups),可以通過添加手寫本地類的方式添加單元測試。假設一個簡單的情形,在一個report中你想要測試子程式xyz的最直接調用,下面的代碼骨架就可以做到,這段代碼可以定義為代碼模闆,以便于插入到report。

    class lcl_test definition for testing  "#AU Duration Short
      inheriting from cl_aunit_assert. "#AU Risk_Level Harmless
      private section.
        methods test_xyz_simple_call for testing.
    endclass.
     
    class lcl_test implementation.
      method test_xyz_simple_call.
    * Setup parameters for the call...
    * Perform the call
        perform xyz using ...
    * Check returned values
        assert_equals( act = ... exp = ... ).
      endmethod.
    endclass.      
    當然,使用ABAP面向對象有很多好處,比如,會有ABAP類的單元測試模闆的自動生成功能。同樣地,生産代碼和測試代碼的分界會更清晰。測試類聲生成在一個用于單元測試的“包含(incluede)”部分,會同其它内容隔離。如果出于某些原因不需要使用這些類,你依然擁有單元測試支援。
  • 不幸的是,我的客戶的開發系統中的主資料品質太差了,以至于我用不了單元測試。

    又有好的消息:盡管客戶的開發系統有着糟糕的資料品質,你還是可以做單元測試!單元測試的最大優勢之一,就是可以獨立地測試單一的代碼單元——測試不依賴任何資料庫條目,不依賴其它中途調用的函數子產品。如果測試在500用戶端沒問題,那它在000用戶端同樣可以運作得很好。

  

  • 不是真的想讓我為所有開發過的代碼對象寫單元測試吧?

    不,并沒有。為某些代碼寫單元測試會導緻時間的浪費。它們是:

    1. 自動生成的代碼,比如視圖維護函數組,靜态系統資訊報表,BSP擴充基礎類,以及其它相似的東西。
    2. 大多數資料庫查詢。在多數情況下,資料庫查詢不應該在單元測試内執行(關于該點請看下文)。有一些例外,比如DAOs(Data Access Object)。這些是單個資料庫的專家(?)。在某些特殊情況下,為了測試功能性而建立測試條目(并且在teardown階段移除)是行得通的。
    3. 連接配接dynpro和abap代碼的代碼。有一些需要重定向dynpro的膠水代碼,就像PAI(Process After Input)中一個特定的對ABAP代碼塊的鍊式請求所起的作用那樣。通常是不值得花費努力為這樣的膠水代碼進行單元測試的。
  • 某個類做的事情是不重要的,不值得為它做測試。

    也許你是對的,但通常,你錯了。隻是你認為你的代碼不重要,因為你隻是完成了它的編寫。經驗表明,一年後,先前不重要的代碼,看起來再也不會對你不重要了。你的同僚也同樣不會認為它不重要。如果你隻是實作了一個擴充卡類,将一個資料格式映射為另一個,接着調用一個API,也許你是對的:對這樣的類進行單元測試也許是過度工程。但是随着源代碼體積的上升,看起來不重要的代碼也許包含某些bug,這種bug隻有在被調用的時候才會顯現。為什麼不實作一個可以自動檢查期望結果的調用呢?這樣做可以保證該類在任何時候都工作正常。

  • 單元測試需要同測試驅動開發(TDD)共同進行嗎?

    基本上,TDD是一種意為“首先實作測試,之後添加可以使得測試通過的生産代碼”的程式設計實踐。這是一個“乒乓球”過程,你将總是在新的測試代碼和新的生産代碼間轉向。你不需要實踐TDD,但是如果你習慣了它,會在很大程度上幫助你避免bug,并是以變得更有效率。即使不使用測試驅動,你依然會受益于單元測試:可以向已存在的代碼對象添加事後比較檢驗(post-hoc test)。 

  • 單元的外部測試怎麼樣?使用單獨的測試對象。

    可以在一個類的屬性标簽中指定其作為單元測試類。但是這應當用于通過繼承來提取幾個相似的本地單元測試類的測試代碼的情況,而不應用在單個單元的測試上面。通常,在外部測試一個單元是不建議的做法,因為這使得單元測試在工作台菜單路徑中的“子產品測試”裡成為不可用的狀态。如果你的類被某人改動了,他也許沒有意識到代碼應當通過外部程式的測試。是以更好的做法是把它包含在生産代碼所在的同一個對象當中。

  • 如果我不測試所有的代碼,測試覆寫中會出現斷層!

    雖然單元測試是個很有用的工具,但并不能回應所有的需求。我在上面已經提到,這種斷層不建議使用單元測試處理,而是應該使用被稱為內建測試的其它技術進行覆寫,比如eCATT, QTP或者其它。

  • 我該怎樣設計單元測試?

    要點在于:應該将它們設計的盡可能簡單。單元測試同樣起着單元功能文檔的作用。同樣地,如果執行修改後,單元測試失敗,會很容易從代碼中看出哪個功能失敗了。嘗試避免測試方法中的多餘代碼。将重複代碼包裝到方法中甚至宏之中,以保證在測試下功能的實質更加可讀。直率地命名變量、方法、類和宏,使得代碼在測試時盡可能的具有表達力。單元的每個特性,都需要可以按照以下三步測試:

    1. 建立測試資料——填充接口參數的内表或屬性,以及/或者樁。
    2. 調用測試方法——通常正好是對公共方法的調用。
    3. 檢測方法輸出的異常。

      這三步應當被包含在一個測試方法中。在每個測試方法附近,通常是測試對象建構的地方,會有一個建立步驟(對于類的每一個方法都是相同的),如果有需要的話,會提供樁。同樣的,每個測試方法的調用後伴随着一個teardown調用。

  • 我怎樣識别自己的方法其實在被單元測試調用,而不是真的使用者?我想在這種情形下做點不同的事情。

    别這樣!不要将生産代碼和測試代碼混在一起,如果想要為了測試而消除生産代碼中的一部分,應當使用樁和依賴注入來代替。但是,在生産代碼使用一個“測試模式”的辨別,會破壞單元測試的概念,并且導緻的代碼變得更糟糕。

  • 我要怎樣組織自己的代碼?

    沒有用于組織單元測試的通行方案。有時讓每個方法有一個單元測試類、每個輸入資料的等價類有一個測試方法是好的做法。但這不是一般的規則。一般來說,測試方法在正交時會變得有用:理想情況下,每個方法測試測試一個不依賴其它存在的單一功能。不要讓測試方法負擔過多的斷言。

  • 如何測試一個将資料庫查詢和它自己的業務邏輯混合到一起并且調用了其它函數子產品的程式(方法/函數子產品)?

    The redefined helper classes like lcl_api_test and lcl_db_test is what the test people call stubs.首先讓代碼成為可測試的,例如使用樁:将資料庫查詢和函數子產品調用包裝到本地幫助類中(資料庫方面我使用LCL_DB,調用其它代碼單元方面我使用LCL_API ),提取這些代碼到自己的方法裡。為這些方法使用具有表達性的名字,使用擴充卡模式為它們設計一個良好的接口。之後你的LCL_API和LCL_DB将隻包含外部函數子產品調用和資料庫操作(select, insert, update, enqueue, ...),也許會有幾行映射代碼,用于将你設計的好的接口映射到你調用的子產品的傳統接口。

    在你的對象中應有像go_api和godb這樣的全局的幫助類執行個體可用。在測試方法中重定義其方法,控制他們的行為。像lcl_api_test和lcl_db_test這樣的重定義過的幫助類就是測試人員所說的的“樁”。

  • 聽起來是複雜的。

    你說得對,它不是直接的。為了保持測試代碼簡單可了解,你應當嘗試在任何時候盡可能避免樁的使用。可以通過在業務邏輯、API調用和資料庫操作之間提供更好的分隔,來避免樁。例如,不在相同的方法裡面查詢資料、對資料執行檢查。可以首先查詢資料,接着将資料條目作為導入參數在自己的方法裡進行檢查。通過這種方法讓代碼變得可測試,通常——作為副作用——會提高其可讀性。

  • 我應該測試受保護方法或者私有方法麼?

    通常不用。通常,你會關注一個類的公共界面。私有屬性或方法也許會在重構期間消失,或者被其它元件代替。如果它對公共方法調用沒有任何影響,就算删除它,也許都是安全的;如果它對公共方法調用有影響,那就測試公共方法——保持未來重構的自由。如果測試私有方法,接着,想要改變這些元件的時候,就不得不改變它們的單元測試,這導緻代碼的可變性很差。

  • 好的——但是,我在某種特别的狀況下(blabla...)真的很需要測試私有方法和受保護的方法。我要怎樣提供這個?

    因為,像任何其它類一樣,本地類是獨立于它們的包含工作台類的,你需要聲明本地測試類為包含類的友元。如果zcl_testee是包含類,lcl_test是單元測試類,需要在本地測試類中添加如下代碼:

    class lcl_test definition deferred.
    class zcl_testee definition local friends lcl_test.
    ...
    class lcl_test definition for testing ...
    ...      
  • 我的單元測試包含文法錯誤,但是它對生産類沒影響,因為單元測試隻在開發系統中進行。對嗎?
    ABAP單元測試最佳實踐
     不是的。單元測試不可以在生産系統中執行,但是類中的單元部分裡面的文法錯誤會破壞完整的類,導緻通路類的任何屬性或方法時會出現SYNTAX_ERROR的short dump。
  • 我的測試對象是一個單例。為了避免副作用,我想至少對每個方法的測試得建立一個新的執行個體。

    如果你的單例包含全局資料,它們也許會被測試改變,在測試調用之間生成醜陋的依賴。你可以在測試期間通過屬性“create public”建立一個對象的子類,按照如下方法進行。

    如果你隻是需要類行為的這種改變,你甚至不需要子類的“class...implementation”部分。

    class lcl_testee definition inheriting from zcl_someclass create public.
    endclass.
    ...
    class lcl_test implementation.
      method setup.
        create object go_testee type lcl_testee.
      endmethod.
    endclass.      
    記着,無論如何,問題不會由單元測試而是全局資料引起。單元測試隻是發現問題,而不是導緻問題。是以最佳的解決方式是排除類中的全局資料。
  • 我要怎樣做能讓我的測試代碼變得更加可讀?
    • 無論在任何時候,盡可能地使用隐式的“函數”表示法進行方法調用,特别是像assert( ), assert_initial(), assert_subrc()等等這種調用。
    • 如果你不需要測試類的繼承層次(為什麼需要?),你也許會讓測試類繼承自cl_aunit_assert。可以像這樣寫:
      assert_subrc( sy-subrc ).      
      而不是
      call method cl_aunit_assert=>assert_subrc
        exporting
          act = sy-subrc.      
      • 如果調用是複雜的(比如含有很多參數),使用宏填充内表和調用測試方法。省去調用自身的重複代碼,也省去了用于填充内表的本地變量比如工作區。我們使用一種宏包含和子程式池的結合來填充内表,減少了用于工作區的輔助本地變量的需要。

        如果你需要一個例子:這裡是一個用于解析器的測試方法,可以将指定的包裝規則轉換為自由文本并将其放入内表中,内表中包含以預定義格式存在的相關資訊。建立自由文本、調用解析器方法、檢查結果内表的特定元件,這三種行為,在約20個不同方法中是重複的,隻有自由文本的内容和修改的内表中的預期結果會改變。

        宏_assert_n_fields_in_row檢查指定内表的指定行的指定的元件含有指定的值!

    • method test_2_lief_2_pal.
       
      * Test assignment of deliveries to handling units
       
          _set_code:
            `1. Palette  (                 `,
            `  1. Lieferung, 1. Pos, 50%   `,
            `  )                           `,
            `2. Palette  (                 `,
            `  2. Lieferung, 2. Pos, Rest  `,
            ` )                            `.
       
          _call_parser.
       
          _assert_rows 'Pack data' gt_packdata_template 2.
       
          _assert_n_fields_in_row 'Pack data' gt_packdata_template 'exidv;vepos;vbeln;posnr;vemng;vemeh;unvel' :
             1 'E1;1;1;1;50;!%;',
             2 'E2;1;2;2;REST;;'.
       
        endmethod.      
      用這種方式提取重複的代碼,減少上面提到過的用于“建立——測試調用——校驗”三個步驟的方法,以明确受測試的功能。
    • 讀一些好書,比如Martin Fowler的《重構》,或者Robert C. Martin的《代碼整潔之道》以擷取更多關于代碼如何變得更加可讀的思想。
  • 使用宏對調試不利嗎?

     視情況而定,如果隻是使用宏來“去掉噪音”,比如,用來提取總是一樣并且頻繁使用到的代碼序列,那麼在調試器裡面使用F6跳過它的執行就不是問題。如果你有一個隐藏了像上面例子中的_call_parser一樣的方法調用的宏,你可以使用F5進入該方法,即使調用隐藏在宏裡面。此外,在這種情形下,你隻是失去了代碼中無趣的部分。

  • 在一個作業中周期性地運作單元測試是有意義的嗎?

    通常,單元測試和新代碼的開發相關聯。與內建測試相反,在夜間作業運作它們并不讓人意外,因為結果隻在代碼改變的時候改變,是以代碼的最後修改者應該知道結果——如果他測試了他的單元!如果你的團隊中有不使用單元測試的開發者,或者代碼的最後一個修改者僅僅是忘記了運作單元測試,有個作業來通知失敗,會很不錯(比如通過發送郵件給TADIR的擁有者)。你可以使用代碼檢查器(code inspector)運作單元測試。别忘記在單元測試類定義中的有關風險等級的僞代碼注釋和期間,因為,否則的話,代碼檢查其也許會不執行測試:

    class lcl_test definition for testing  "#AU Duration Short
      inheriting from cl_aunit_assert. "#AU Risk_Level Harmless
     ...      
  • 在傳輸請求将要釋出的時候檢查單元測試是可行的嗎?

    可以,而且我認為它很有用。最簡單的達成方式是打開傳輸釋出的代碼檢查器檢查,并在檢查變量中選擇“單元測試”。

    在我們的實踐中,我選擇了一個更

    複雜的方式,使用傳輸組織器的BAdI和一個函數子產品調用單元測試。雖然這個功能沒有得到SAP的保障(短文本中包含危險修訂“for SAP

    only”),它還是看起來工作的相當好。我們從兩年前開始使用它,到現在也沒出問題。方法cl_aunit_prog_info=>

    contain_programs_testcode(

    )也許可以用于找出特定的程式(根據指定主程式的報表源的名字)是否包含單元測試。如果僅僅是程式、類或者函數子產品的一部分改變了,你也許不得不找出LIMU的父對象。為實作這點,可以使用函數子產品TR_CHECK_TYPE。

本文連結:http://www.cnblogs.com/hhelibeb/p/6038202.html

原文連結:ABAP Unit Best Practices 

2018.04.22更新:現有一個Open SAP的Writing Testable Code for ABAP 視訊教程,推薦觀看

繼續閱讀