ABAP單元測試最佳實踐

 

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

 

  • 在個人項目中,只使用傳統的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)」部分,會同其它內容隔離。若是出於某些緣由不須要使用這些類,你依然擁有單元測試支持。java

 

  • 不幸的是,個人客戶的開發系統中的主數據質量太差了,以致於我用不了單元測試。
    又有好的消息:儘管客戶的開發系統有着糟糕的數據質量,你仍是能夠作單元測試!單元測試的最大優點之一,就是能夠獨立地測試單一的代碼單元——測試不依賴任何數據庫條目,不依賴其它中途調用的函數模塊。若是測試在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 ...
    ...

 

  • 個人單元測試包含語法錯誤,可是它對生產類沒影響,由於單元測試只在開發系統中進行。對嗎?
    (warning) 不是的。單元測試不能夠在生產系統中執行,可是類中的單元部分裏面的語法錯誤會破壞完整的類,致使訪問類的任何屬性或方法時會出現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.

    記着,不管如何,問題不會由單元測試而是全局數據引發。單元測試只是發現問題,而不是致使問題。所以最佳的解決方式是排除類中的全局數據。api

 

  • 我要怎樣作能讓個人測試代碼變得更加可讀?
    • 不管在任什麼時候候,儘量地使用隱式的「函數」表示法進行方法調用,特別是像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 視頻教程,推薦觀看less

相關文章
相關標籤/搜索