動機
現在大家都知道單元測試對我們代碼的好處。並且我們都承認它是開發過程中不可或缺的一部分。但是在把代碼切換到數據庫的模式下的時候,我們被粗暴地打回了軟件測試的黑暗年代...我們現在面臨着邏輯下推到ABAP CDS entities后,代碼要如何測試的難題。
CDS Test Double Framework允許開發者們通過眾所周知的ABAP Unit Test Framework自動化地測試CDS entities。
本文鏈接:http://www.cnblogs.com/hhelibeb/p/7376232.html
英文原文:Introduction to CDS Test Double Framework – How to write unit tests for ABAP CDS Entities?
挑戰
因為CDS entity中的邏輯運行在下層的數據庫中(獨立於abap runtime),使用傳統的ABAP依賴注入解決方案以實現測試成為了不可能的事情。entity的依賴組件需要在數據庫中加上測試替身,並且我們必須確保CDS entity測試的時候數據庫引擎調用/執行這些測試替身(double)。
為了可以在CDS entity under test (CUT)中可控地測試邏輯,我們需要通過測試替身注射測試專用數據。這意味着必須將測試數據插入到測試替身中,這樣數據可以在CUT執行時被測試替身們返回。對於在ABAP CDS上下文中有着固有的只讀屬性的依賴組件(比如數據庫視圖和數據庫函數),這是一項特別的挑戰。
本文中重要的縮寫:
CUT = CDS entity Under Test
DOC = Depended-On Component
CDS Test Double Framework
CDS Test Double Framework處理了以上的挑戰,並且可以實現CDS entities測試的自動化:
- 在相同的Database Schema中為每個依賴組件創建臨時的可更新測試替身:
- 復制依賴組件表,但不復制任何數據。不復制依賴數據庫表的主鍵約束。這允許你輕松地插入測試數據,而不用擔心數據的完整性。相似的,數據庫索引也不會被復制。
- 為依賴數據庫視圖創建數據庫表。這些表有着和依賴數據庫視圖相同的結構。
- 依賴數據庫functions(由帶有參數的依賴CDS視圖和依賴表function產生)會被復制,並且function的測試替身實現會被修改為允許插入需要的測試數據。
- 在相同的Database Schema創建一個CDS entity under test(CUT)的臨時副本。從各種意義上來看,這個副本是為CUT服務的。在原始的CDS entity中實現的邏輯在副本中同樣存在,但是依賴組件被替換為了相應的測試替身。測試替身是由我們的CDS Test Double Framework所創建的。
測試什么?
單元測試應當專注於由一個給定視圖實現的有價值的功能定義。不是所有的CDS視圖都需要一個單元測試。在實現單元測試之前,建議辨別出entity中與測試有關的方面。
通常,需要為某些包含了代碼下推的方法的entity進行單元測試。潛在的測試候選者包括:
Calculations and/or filters, conversions, conditional expressions 比如 CASE…THEN…ELSE or COALESCE, type changing CAST operations, cardinality changes or checks against NULL values, JOIN behavior, complex where conditions 等.
單元測試不應用於測試那些更適用於靜態檢查、集成測試等技術的CDS entities屬性。如果不能從單元測試中獲取任何價值的話,也不應進行它,比如對於那些簡單的CDS投影視圖。
怎樣使用CDS Test Double Framework寫單元測試?
在下一部分,我們會通過被廣泛應用的ABAP Unit Test Framework為以下的CDS視圖創建單元測試。
@AbapCatalog.sqlViewName: 'zSo_Items_By_1' @EndUserText.label: 'Aggregations/functions in SELECT list' @AbapCatalog.compiler.compareFilter: true define view Salesorder_Items_By_TaxRate as select from CdsFrwk_Sales_Order_Item association [1] to snwd_so as _sales_order on so_guid = _sales_order.node_key { so_guid, coalesce ( _sales_order.so_id, '9999999999' ) as so_id, currency_code, sum( gross_amount ) as sum_gross_amount, tax_rate, _sales_order } group by so_guid, _sales_order.so_id, currency_code, tax_rate
創建ABAP測試類
創建一個ABAP測試類以對CDS視圖進行單元測試。有一個好的實踐方法:為測試類起一個和CUT相同/相似的名字,並且加上TEST的后綴。比如,對於CDS視圖Salesorder_Items_By_TaxRate,測試類的名字可以是:Salesorder_Items_By_TaxRate_Test.
因為單元測試和CDS是不同的東西,相同/相似的名字可以幫助我們輕松的尋找相關的測試。
CLASS Salesorder_Items_By_TaxRate_Test DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PRIVATE SECTION. ... ... ENDCLASS. CLASS SO_ITEMS_BY_TAXRATE_TEST IMPLEMENTATION. ... ... ENDCLASS.
定義固定方法
定義以下的安裝拆卸方法。
運行方法cl_cds_test_environment=>create( i_for_entity = ‘<CDS under test>’ ),隱式地在數據庫中創建所有依賴組件測試替身。這個方法在測試類中只應被調用一次。
"Fixture method class_setup is executed only once in the beginning of the execution of test class METHOD class_setup. "For parameter i_for_entity, specify the CDS view to be unit tested. This will create all the depended-on component Test doubles in the database. environment = cl_cds_test_environment=>create( i_for_entity = 'Salesorder_Items_By_TaxRate' ). ENDMETHOD. METHOD class_teardown. environment->destroy( ). ENDMETHOD. "Fixture method setup is executed once before each test method execution <cod METHOD setup. environment->clear_doubles( ). ENDMETHOD.
定義單元測試方法
METHOD cuco_1_taxrate_1_item_1_ok. ENDMETHOD.
准備輸入——在測試替身中插入測試數據
METHOD cuco_1_taxrate_1_item_1_ok. "Step 1 : Insert testdata into the doubles "Step 1.1 : create an instance of type snwd_so. Note : CDS view Salesorder_Items_By_TaxRate depends on snwd_so. sales_orders = VALUE #( ( client = sy-mandt node_key = '01' so_id = 'ID' ) ). "Step 1.2 : Use the framework method CL_CDS_TEST_DATA=>create(..) to create the test_data object test_data = cl_cds_test_data=>create( i_data = sales_orders ). "Step 1.3 : Use the framework method environment->get_double(..) to create the instance of the double 'SNWD_SO' DATA(sales_orders_double) = environment->get_double( i_name = 'SNWD_SO' ). "Step 1.4 : Insert the testdata into the double depended-on component object sales_orders_double->insert( test_data ). "Repeat Step 1 for all the depended-on component doubles sales_order_items = VALUE #( ( mandt = sy-mandt so_guid = '01' currency_code = 'EUR' gross_amount = '1' tax_rate = '19.00' ) ). test_data = cl_cds_test_data=>create( i_data = sales_order_items ). DATA(sales_order_items_double) = environment->get_double( i_name = 'CdsFrwk_DEMO_1' ). sales_order_items_double->insert( test_data ). ... ENDMETHOD.
執行CDS
SELECT * FROM cdsfrwk_so_items_by_taxrate INTO TABLE @act_results.
驗證輸出——使用ABAP單元測試的斷言
exp_results = VALUE #( ( so_id = 'ID' currency_code = 'EUR' sum_gross_amount = '1' tax_rate = '19.00' ) ). cl_abap_unit_assert=>assert_equals( act = lines( act_results ) exp = lines( exp_results ) ). "The method looks as follows: METHOD cuco_1_taxrate_1_item_1_ok. "Step 1 : Insert testdata into the doubles "Step 1.1 : create an instance of type snwd_so sales_orders = VALUE #( ( client = sy-mandt node_key = '01' so_id = 'ID' ) ). "Step 1.2 : Use the framework method CL_CDS_TEST_DATA=>create to create the test_data object test_data = cl_cds_test_data=>create( i_data = sales_orders ). "Step 1.3 : Use the framework method environment->get_double to the instance of the DOC double 'SNWD_SO' DATA(sales_orders_double) = environment->get_double( i_name = 'SNWD_SO' ). "Step 1.4 : Insert the testdata into the DOC double object sales_orders_double->insert( test_data ). "Repeat Step 1 for all the DOC doubles sales_order_items = VALUE #( ( mandt = sy-mandt so_guid = '01' currency_code = 'EUR' gross_amount = '1' tax_rate = '19.00' ) ). test_data = cl_cds_test_data=>create( i_data = sales_order_items ). DATA(sales_order_items_double) = environment->get_double( i_name = 'CdsFrwk_DEMO_1' ). sales_order_items_double->insert( test_data ). "Step 2 : Execute the CDS SELECT * FROM cdsfrwk_so_items_by_taxrate INTO TABLE @act_results. "Step 3 : Verify Expected Output exp_results = VALUE #( ( so_id = 'ID' currency_code = 'EUR' sum_gross_amount = '1' tax_rate = '19.00' ) ). assert_so_items_by_taxrate( exp_results = exp_results ). ENDMETHOD.
運行CDS的單元測試
在ADT當中,打開包含所有CDS單元測試的ABAP測試類。右鍵選擇Run As->ABAP Unit Test,或者使用ctrl+shift+f10組合鍵來運行單元測試。結果會在eclipse中的ABAP Unit Runner視圖中顯示。
注意:至今為止,還不能在DDL源代碼編輯器中直接運行單元測試。
受支持的測試場景
CDS Test Double framework支持為給定的CUT的以下DOC創建測試替身:
- DDIC tables
- DDIC views
- CDS views
- CDS views with Parameters
- External Views
- Table Functions
- CDS special functions. CURRENCY_CONVERSION and UNIT_CONVERSION
你可以打開/關閉給定CDS的DCL,更多細節會在本文的后面提供。
依賴組件是Table Function
Tables Function的測試替身的操縱方式和其它的CDS視圖一樣。
依賴組件是帶有參數的CDS視圖
CDS Test Double Framework提供了
cl_cds_test_data=>create( .. )->for_parameters( .. )
來為帶有參數的類型的測試替身插入數據。
METHOD eur_tax_rate_19_found. "Step 1 : Insert testdata into the doubles open_items = VALUE #( ( mandt = sy-mandt so_guid = '0F' tax_rate = '19.00' so_id = '1' ) ). i_param_vals = VALUE #( ( parm_name = `pCuCo` parm_value = `EUR` ) ). "CdsFrwk_demo_3 is a CDS view with parameters. Use framework method ->for_parameters( ) to insert test data test_data = cl_cds_test_data=>create( i_data = open_items )->for_parameters( i_param_vals ). DATA(open_items_double) = environment->get_double( 'CdsFrwk_demo_3' ). open_items_double->insert( test_data ). ... ... ENDMETHOD.
DCL對CUT的影響
你也可以打開/關閉給定的CDS的DCL。但是,在目前,如果你的CDS DDL在測試時受到DCL影響的話,我們建議在運行測試時總是關閉DCL。在未來,使用DCL時會有很多選項可以玩,並且可以使用角色權限測試替身等。但是在目前的版本中,你需要注意在測試CDS DDL時完全關閉DCL(如果有的話)。在打開DCL時些測試可能導致測試間斷性失敗,因為實際訪問控制角色權限會被應用。因此,建議在你的生產測試中總是有一個:
DISABLE_DCL=ABAP_TRUE in the cl_cds_test_environment=>create(…)
對特殊function的支持:CURRENCY_CONVERSION和UNIT_CONVERSION
CDS Test Double framework中可以為兩個特殊的CDS function提供支持:
"Step 1 : Create testdata using the special framework method create_currency_conv_data test_data = cl_cds_test_data=>create_currency_conv_data( output = '399.21' )->for_parameters( amount = '558.14' source_currency = 'USD' target_currency = 'EUR' exchange_rate_date = '20150218' ). "Step 2 : Get the double instance using the framework method get_double DATA(curr_conv_data_double) = environment->get_double( cl_cds_test_environment=>currency_conversion ). "Step 3 : Insert test_data into the double curr_conv_data_double->insert( test_data ).
帶有NULL值的測試
為了在測試替身中插入null值,CDS Test Double Framework提供了方法:
cl_cds_test_data=>create( .. )->set_null_values( .. )
該方法可以顯式地設定null值。
partners = VALUE #( ( client = sy-mandt bp_id = '1' ) ). "Step 1 : define the list of columns into which NULL is inserted i_null_vals = VALUE #( ( `address_guid` ) ). "Step 2 : Create testdata and set the NULL value object test_data = cl_cds_test_data=>create( i_data = partners )->set_null_values( i_null_vals ). "Step 3 : Get test Double instance DATA(partners_double) = environment->get_double( i_name = 'SNWD_BPA' ). "Step 4 : Insert test data into test double partners_double->insert( test_data ).
Demo examples
你可以在這個包里找到許多單元測試的例子:
SABP_UNIT_DOUBLE_CDS_DEMO
可用性
CDS Test Double Framework從NetWeaver AS ABAP 7.51 release開始可用。
更多信息
報告bug的話,請為CSS組件BC-DWB-TOO-UT-CDS創建tickets。
總結:通過本文,你現在可以使用CDS Test Double Framework高效地為你在CDS中實現的代碼下推來寫自動化的測試!
擴展閱讀:深入探討 Test Double、Dummy、Fake、Stub 、Mock 與 Spy