ABAP OData Test
A source-verified project description is not available yet. Open the detail page or wait for a manual refresh.
Source facts need a manual refresh
ABAP unit testing framework, prepare in Excel, reuse in abap code
ABAP unit testing framework, prepare in Excel, reuse in abap code
abaplint Version Abap package version
logo
history of changes
Mockup loader is a tool to simplify data preparation for SAP ABAP unit tests. Create unit test data in Excel, easily convert it into MIME object that travels with ABAP package, easily consume the data from your unit test code. The tool was created with the following high level goals in mind:
Features:
The tool is created to simplify data preparation/loading for SAP ABAP unit tests. In one of our projects we had to prepare a lot of table data for unit tests. For example, a set of content from BKPF, BSEG, BSET tables (FI document). The output of the methods under test is also often a table or a complex structure.
Hard-coding all of that data was not an option - too much to code, difficult to maintain and terrible code readability. So we decided to write a tool which would get the data from TAB delimited .txt files, which, in turn, would be prepared in Excel in a convenient way. Certain objectives were set:
" Test class (o_ml is mockup_loader instance)
...
o_ml->load_data( " Load test data (structure) from mockup
exporting
i_obj = 'TEST1/bkpf'
i_strict = abap_true
importing
e_container = ls_bkpf ).
o_ml->load_data( " Load test data (table) from mockup
exporting
i_obj = 'TEST1/bseg'
i_strict = abap_false
importing
e_container = lt_bseg ).
...
" Call to the code-under-test
o_test_object->some_processing(
exporting
i_bkpf = ls_bkpf
it_bseg = lt_bseg ).
assert_equals(...).The first part of the code takes TAB delimited text file bkpf.txt in TEST1 directory (file names are case-insensitive) of ZIP file uploaded as binary object via SMW0 transaction...
BUKRS BELNR GJAHR BUZEI BSCHL KOART ...
1000 10 2015 1 40 S ...
1000 10 2015 2 50 S ...... and puts it (with proper ALPHA exits and etc) to an internal table with BSEG line type.
On-the-fly data filtering is supported. For more information see REFERENCE.md.
Calls with importing/exporting are less readable, especially when stack together. In search of more readable options there are several experimental forms to achieve the same result. Use with care yet, they might be changed. Feedback via github issues is appreaciated.
ml->to( lr )->load_data( 'test-mock-path' ).
" where lr is a `ref to data`
" works nicely in newer releases, but requires extra `get reference of` in older
" so also:
ml->load( 'test-mock-path' )->into( changing data = lt_data ).
" which works in 7.02 and reads well enoughSince 2.0.0 mockup loader supports generating of interface stubs. :tada:
It creates an instance object which implements the given interface where one or more methods retrieve the data from the mockup.
data lo_factory type ref to zcl_mockup_loader_stub_factory.
data lo_ml type ref to zcl_mockup_loader.
lo_ml = zcl_mockup_loader=>create(
i_type = 'MIME'
i_path = 'ZMOCKUP_LOADER_EXAMPLE' ). " <INIT YOUR MOCKUP>
create object lo_factory
exporting
io_ml_instance = lo_ml
i_interface_name = 'ZIF_MOCKUP_LOADER_STUB_DUMMY'. " <YOUR INTERFACE TO STUB>
" Connect one or MANY methods to respective mockups
lo_factory->connect_method(
i_method_name = 'TAB_RETURN' " <METHOD TO STUB>
i_mock_name = 'EXAMPLE/sflight' ). " <MOCK PATH>
data li_ifstub type ref to ZIF_MOCKUP_LOADER_STUB_DUMMY.
li_ifstub ?= lo_factory->generate_stub( ).
" Pass the stub to code-under-test, the effect is:
...
data lt_res type flighttab.
lt_res = li_ifstub->tab_return( i_connid = '1000' ).
" lt_res contains the mock data ...... and with filtering
...
lo_factory->connect_method(
i_method_name = 'TAB_RETURN' " <METHOD TO STUB>
i_sift_param = 'I_CONNID' " <FILTERING PARAM>
i_mock_tab_key = 'CONNID' " <MOCK HEADER FIELD>
i_mock_name = 'EXAMPLE/sflight' ). " <MOCK PATH>
...This will result in the data set where key field CONNID will be equal to ICONNID parameter actually passed to interface call.
Returning, exporting and changing parameters are supported. For more information see REFERENCE.md.
In addition, forwarding calls to another object (implementing same interface) is supported. For example if some of accessor methods must be connected to mocks and some others were implemented manually in a supporting test (or real production) class. See REFERENCE.md.
It is possible to return just one field of the first matching record e.g. Document type of a document selected by number. For this specify the field to return in IFIELDONLY param. See REFERENCE.md.
And finally a feature that is not related to mocks - passing ICONSTVALUE would return this value instead of loading data from text mock.
accessor pattern
Available since v2.1.6.
The above connectmethod/proxy configuration can be also done with a single string. See REFERENCE.md for details.
...
lo_factory->connect( 'tab_return -> EXAMPLE/sflight' ).
lo_factory->connect( 'tab_return -> EXAMPLE/sflight [connid = i_connid]' ).
lo_factory->connect( 'tab_return -> EXAMPLE/sflight [connid = "XYZ_ID"]' ).
lo_factory->connect( 'tab_return -> EXAMPLE/sflight(this_field_only) [connid = i_connid]' ).
lo_factory->connect( 'tab_return -> EXAMPLE/sflight(?) [connid = i_connid]' ). " boolc("record exists")
lo_factory->connect( 'tab_export(e_tab) -> EXAMPLE/sflight' ). " write output to e_tab param (exports)
lo_factory->connect( 'tab_return -> ~EXAMPLE/sflight [connid = i_connid]' ). " corresponding only
lo_factory->connect( 'tab_return -> =exact_value' ).
lo_factory->connect( 'tab_return -> *' ). " proxy
" and also a shorter version, using the set_default_mock()
lo_factory->set_default_mock( 'EXAMPLE' ).
lo_factory->connect( 'tab_return -> ./sflight [connid = i_connid]' ).
" Multi condition also supported
lo_factory->connect( 'tab_return -> ./sflight [connid = i_connid, fldate = i_fldate]' ). " AND
lo_factory->connect( 'tab_return -> ./sflight [connid = i_connid & fldate = i_fldate]' ). " AND
lo_factory->connect( 'tab_return -> ./sflight [connid = i_connid | fldate = i_fldate]' ). " OR
lo_factory->connect( 'tab_return_2conn -> ./sflight [connid = i_connid | connid = i_connid2 ]' ). " Same field multi-filter
" Multiple connection for exporting params of the same method is supported
lo_factory->connect( 'read_sales_invoice(e_vbrk) -> ./vbrk [vbeln = i_vbeln]' ).
lo_factory->connect( 'read_sales_invoice(e_vbrp) -> ./vbrp [vbeln = i_vbeln]' ). " Same method, another exporting paramGenerated stub instance implements ZIFMOCKUPLOADERSTUBCONTROL interface, which allows:
Available since v2.1.0.
If you have a target data with deep fields - tables or structures - it is possible to fill them in one run. Let's consider a simple example: assume you have 2 linked tables - header and lines - the tables are represented by separate files in zip.
DOCUMENT
========
ID DATE ...
1 ...
2 ...
LINES
========
DOCID LINEID AMOUNT ...
1 1 100.00 ...
1 2 123.00 ...
2 1 990.00 ...The target structure is:
types:
begin of ty_line,
docid type numc10,
lineid type numc3,
" ...
end of ty_line,
tt_line type table of ty_line,
begin of ty_document,
id type numc10,
" ...
lines type tt_line, " <<< DEEP FIELD, supposed to be filled with lines of the document
end of ty_document.
tt_documents type table of ty_document.The following code will load this kind of structure
o_ml->load_data(
exporting
i_obj = 'path_to_head_file'
i_deep = abap_true " <<< ENABLE DEEP LOADING
importing
e_container = lt_docs ). " <<< type tt_documentsTo instruct mockup loader how to find the data for deep components you have to fill these components in the text in special format: [ = ] which means "go find sourcepath file, parse it, extract the lines, filter those where sourceidfield = value or referencefield value of the current header record". For example:
DOCUMENT
========
ID DATE ... LINES
1 ... path_to_lines_file[docid=@id]
2 ... path_to_lines_file[docid=12345]For the first record the mockup loader will find file pathtolinesfile.txt and load the lines with docid = 1 (value of id field of the first record). For the second record the explicit value 12345 will be used as the filter.
Disclaimer: There is an opinion that adding test-related code to the production code is a 'code smell'. I sincerely agree in general. If the code was designed to use e.g. accessor interfaces from the beginning this is good. Still 'store' functionality can be useful for older pieces of code to be tested without much refactoring.
data flow
Some code is quite difficult to test when it has a db select in the middle. Of course, good code design would assume isolation of DB operations from business logic code, but it is not always possible (or was not done in proper time). So we needed to create a way to substitute selects in code to a simple call, which would take the prepared test data instead if test environment was identified. We came up with the solution we called store.
" Test class (o_mls is mockup_loader_STORE instance)
...
o_mls->store( " Store some data with 'BKPF' label
i_name = 'BKPF'
i_data = ls_bkpf ). " One line structure
...
" Working class method
...
if is_test_env = abap_false. " Production environment detected
select ... from db ...
else. " Test environment detected
zcl_mockup_loader_store=>retrieve(
exporting i_name = 'BKPF'
importing e_data = ls_fi_doc_header
exceptions others = 4 ).To keep this page readable, only the first part of the README is shown. Open the original README for the full document.