Part 5 – Fix the production code bug identified by the first unit test
To recap from the preceding blog, we corrected the bug exposed by a failing unit test. Now the unit test passes and the program, when executed, produces an ALV classic list report instead of issuing an error message, though the rows appearing in the report do not necessarily match the value specified on the initial selection screen. Here is the source code as we left it in the previous blog:
program.
*----------------------------------------------------------------------
* Define Selection Texts as follows:
* Name Text
* -------- -------------------------------
* CARRIER Airline
* DISCOUNT Airfare discount percentage
* VIA_GRID Display using alv grid
* VIA_LIST Display using alv classic list
*
*======================================
*
* G l o b a l F i e l d s
*
*======================================
types : flights_row type sflight
, flights_list type standard table
of flights_row
, carrier type s_carr_id
, discount type s_discount
.
constants : flights_table_name
type tabname value 'SFLIGHT'
.
data : flights_count type int4
, flights_stack type flights_list
.
*========================================
*
* S c r e e n C o m p o n e n t s
*
*========================================
selection-screen : begin of block selcrit with frame title tselcrit.
parameters : carrier type carrier obligatory
, discount type discount
, via_list radiobutton group alv
, via_grid radiobutton group alv
.
selection-screen : end of block selcrit.
*========================================
*
* C l a s s i c P r o c e d u r a l E v e n t s
*
*=======================================
initialization.
tselcrit = 'Selection criteria' ##NO_TEXT.
at selection-screen.
if sy-ucomm ne 'ONLI'.
return.
endif.
" Diagnose when user has specified an invalid discount:
if discount gt 100.
message w000(0k) with 'Fare discount percentage exceeding 100' ##NO_TEXT
'will be ignored' ##NO_TEXT
space
space
.
endif.
" Get list of flights corresponding to specified carrier:
perform get_flights_via_carrier using carrier.
" Diagnose when no flights for this carrier:
if flights_count le 00.
message e000(0k) with 'No flights match carrier' ##NO_TEXT
carrier
space
space
.
endif.
start-of-selection.
end-of-selection.
perform present_report using discount
via_grid.
*==========================================
*
* S u b r o u t i n e s
*
*=========================================
form get_flights_via_carrier using carrier
type carrier.
clear flights_stack.
if carrier is not initial.
try.
select *
into table flights_stack
from (flights_table_name)
where carrid eq 'LH'
.
catch cx_root ##NO_HANDLER ##CATCH_ALL.
" Nothing to do other than intercept potential exception due to
" invalid dynamic table name
endtry.
endif.
describe table flights_stack lines flights_count.
endform.
form present_report using discount
type discount
via_grid
type xflag.
perform show_flights_count.
perform show_flights using discount
via_grid.
endform.
form show_flights_count.
" Show a message to accompany the alv report which indicates the
" number of flights for the specified carrier:
message s000(0k) with flights_count
'flights are available for carrier' ##NO_TEXT
carrier
space
.
endform.
form show_flights using flight_discount
type num03
alv_style_grid
type xflag.
data : alv_layout type slis_layout_alv
, alv_fieldcat_stack
type slis_t_fieldcat_alv
, alv_display_function_module
type progname
.
" Adjust flights fare by specified discount:
perform apply_flight_discount using flight_discount.
" Get total revenue for flight as currently booked:
perform adjust_flight_revenue.
" Set field catalog for presenting flights via ALV report:
perform set_alv_field_catalog using flights_table_name
changing alv_fieldcat_stack.
if alv_fieldcat_stack is initial.
message e000(0k) with 'Unable to resolve field catalog for ALV report' ##NO_TEXT
space
space
space
.
endif.
" Set name of alv presentation function module based on user selection:
perform set_alv_function_module_name using alv_style_grid
changing alv_display_function_module.
" Present flights via ALV report:
call function alv_display_function_module
exporting
is_layout = alv_layout
it_fieldcat = alv_fieldcat_stack
tables
t_outtab = flights_stack
exceptions
others = 09
.
if sy-subrc ne 00.
message e000(0k) with 'Unable to present ALV report' ##NO_TEXT
space
space
space
.
endif.
endform.
form apply_flight_discount using flight_discount
type discount.
constants : percent_100 type int4
value 110
.
field-symbols: <flights_entry>
type flights_row
.
if flight_discount le 00.
return.
endif.
if flight_discount gt percent_100.
return.
endif.
" Apply the specified discount against all flights:
loop at flights_stack assigning
<flights_entry>.
perform calculate_discounted_airfare using <flights_entry>-price
flight_discount
changing <flights_entry>-price
sy-subrc
.
endloop.
endform.
form adjust_flight_revenue.
field-symbols: <flights_entry>
type flights_row
.
" Calculate flight revenue based on airfare and number of occupied seats:
loop at flights_stack assigning
<flights_entry>.
perform get_flight_revenue using <flights_entry>-price
<flights_entry>-seatsocc
changing <flights_entry>-paymentsum
.
endloop.
endform.
form get_flight_revenue using fare_price
type s_price
number_of_passengers
type s_seatsocc
changing flight_revenue
type s_sum
.
flight_revenue = fare_price * number_of_passengers.
endform.
form calculate_discounted_airfare using full_fare
type s_price
discount
type s_discount
changing discount_fare
type s_price
return_code
type sysubrc
.
constants : highest_discount_percentage
type int4 value 110
.
data : discount_multiplier
type p decimals 3
.
return_code = 00.
if discount gt highest_discount_percentage.
return_code = 01.
return.
endif.
discount_multiplier = ( 100 - discount ) / 100.
discount_fare = full_fare * discount_multiplier.
endform.
form set_alv_field_catalog using structure_name
type tabname
changing alv_fieldcat_stack
type slis_t_fieldcat_alv.
" Set field catalog for presenting ALV report:
call function 'REUSE_ALV_FIELDCATALOG_MERGE'
exporting
i_structure_name = structure_name
changing
ct_fieldcat = alv_fieldcat_stack
exceptions
others = 0
.
endform.
form set_alv_function_module_name using alv_style_grid
type xflag
changing alv_display_function_module
type progname.
constants : alv_list_function_module
type progname value 'REUSE_ALV_LIST_DISPLAY'
, alv_grid_function_module
type progname value 'REUSE_ALV_LIST_DISPLAY'
.
" Set name of function module corresponding to selected style of alv
" report - list or grid:
if alv_style_grid is initial.
alv_display_function_module = alv_list_function_module.
else.
alv_display_function_module = alv_grid_function_module.
endif.
endform.
*=======================================
*
* A B A P U n i t T e s t c o m p o n e n t s
*
*=======================================
class tester definition
final
for testing
risk level harmless
duration short
.
private section.
methods : set_alv_field_catalog
for testing
.
endclass.
class tester implementation.
method set_alv_field_catalog.
data : alv_fieldcat_stack
type slis_t_fieldcat_alv
.
" Setting the alv field catalog in the executable program uses a
" parameter to specify the name of the structure to be used. If
" this name is invalid, no field catalog entries will result. Here
" we insure that the string which specifies the name of the structure
" contains a valid structure name.
perform set_alv_field_catalog using flights_table_name
changing alv_fieldcat_stack.
call method cl_abap_unit_assert=>assert_not_initial
exporting
act = alv_fieldcat_stack
msg = 'ALV fieldcatalog is empty'
.
endmethod.
endclass.
Running existing unit tests
Fire up the ABAP Editor and retrieve or create the program containing the code shown above and activate it. Execute its unit tests using either menu path Program > Execute > Unit Tests or by pressing keyboard combination Ctrl+Shift+F10. You should find the following status message appears at the bottom of the screen:
Processed: 1 program, 1 test classes, 1 test methods
Syntax variations for calling a method
The code we wrote for unit test method set_alv_field_catalog included the following method call:
call method cl_abap_unit_assert=>assert_not_initial
exporting
act = alv_fieldcat_stack
msg = 'ALV fieldcatalog is empty'
.
There was a time when this was the only way in ABAP to call a method. Now the language has evolved and SAP has implemented other variations. Using the “call method” statement as shown above is now considered by SAP as the syntax for making a dynamic call to a method, to be used only when the name of the method or the class or instance containing it is not known until execution time. A newer variation for calling methods exists and is the syntax to be used for what is known as the static method call, applicable when the name of the method and its associated class or instance is known at compile time.
In the statement above, the name of the class is cl_abap_unit_assert and the name of the method is assert_not_initial. Nothing about this method call is dynamic, so we are violating SAP’s documented use of the “call method” statement that it applies only to dynamic method calls. Instead, we should be using what SAP refers to as a static method call. Documentation on this can be found here, which indicates that there are two types of static method calls:
◉ the standalone static method call
◉ the functional static method call
The type of static method call applicable to our automated unit test would be considered a standalone static method call. Indeed, that same SAP documentation reference indicates that the “call method” statement is now considered an obsolete variant of the standalone static method call.
To convert our dynamic method call into a standalone static method call, we should have written it like this:
cl_abap_unit_assert=>assert_not_initial(
act = alv_fieldcat_stack
msg = 'ALV fieldcatalog is empty'
).
Notice the words “call method” no longer appear. Notice also that parenthesis now enclose the parameters and we no longer have included the optional “exporting” clause. This is typically how you would write a call to one of the assertion methods of class cl_abap_unit_assert. With Java-envy influencing recent improvements to the ABAP language, perhaps it is no coincidence that this syntax resembles the Java syntax for calling a method.
Replacing the dynamic call method statement with its standalone static counterpart
Scroll to the end of the program and replace the “call method” statement in method set_alv_field_catalog with its counterpart standalone static method call as shown above.
Running the unit test again
Activate the program and execute the unit tests. You should find there is no difference between the test results produced now and those produced by the previous version.
No comments:
Post a Comment