天天看點

CDS測試架構介紹:如何為ABAP CDS Entities寫測試

動機

現在大家都知道單元測試對我們代碼的好處。并且我們都承認它是開發過程中不可或缺的一部分。但是在把代碼切換到資料庫的模式下的時候,我們被粗暴地打回了軟體測試的黑暗年代...我們現在面臨着邏輯下推到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測試的自動化:

  1. 在相同的Database Schema中為每個依賴元件建立臨時的可更新測試替身:
    1. 複制依賴元件表,但不複制任何資料。不複制依賴資料庫表的主鍵限制。這允許你輕松地插入測試資料,而不用擔心資料的完整性。相似的,資料庫索引也不會被複制。
    2. 為依賴資料庫視圖建立資料庫表。這些表有着和依賴資料庫視圖相同的結構。
    3. 依賴資料庫functions(由帶有參數的依賴CDS視圖和依賴表function産生)會被複制,并且function的測試替身實作會被修改為允許插入需要的測試資料。
  2. 在相同的Database Schema建立一個CDS entity under test(CUT)的臨時副本。從各種意義上來看,這個副本是為CUT服務的。在原始的CDS entity中實作的邏輯在副本中同樣存在,但是依賴元件被替換為了相應的測試替身。測試替身是由我們的CDS Test Double Framework所建立的。
CDS測試架構介紹:如何為ABAP CDS Entities寫測試

測試什麼?

單元測試應當專注于由一個給定視圖實作的有價值的功能定義。不是所有的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

繼續閱讀