Getting acquainted with automating ABAP unit testing – Part 1
Initiating the execution of automated ABAP unit tests
This blog represents part 1 of this 10-part series Getting acquainted with automating ABAP unit testing.
Are you still manually unit testing your ABAP programs? Are you bothered by the exorbitant amount of time and effort required to complete those tests? Do you dread the drudgery associated with manually preparing those tests for execution? Is the pain and agony you endure while executing those tests about the same as it was the last time you underwent this tedious process? Does this whole experience leave you exhausted once you have succeeded in running all those manual unit tests?
If you answered “Yes” to any of those questions, then perhaps you are willing to consider an alternative, one that can simplify the process, significantly reduce the time it takes to complete it and eliminate virtually all of the associated misery: automating the unit testing of ABAP programs.
It is my guess that there is a large segment of ABAP programmers who remain unaware that automated unit testing is possible with ABAP. Even among those who are aware of it but still do not use it, many of them are under the mistaken impression that it is capable only of testing ABAP code written using the object-oriented programming model. This series of blogs is intended to acquaint you with the process of creating automated unit tests for ABAP programs, particularly for those programs containing not a shred of object-oriented code.
Prepare To Abandon Manual Testing All Ye Who Enter Here
The first step is to select an ABAP program small enough to be easily understood but large enough to actually do something useful. The following code should serve this purpose adequately:
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 'XFLIGHT' . 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.
Next, fire up the ABAP Editor (transaction SE38) and create a new program using a local package, then copy this code into that program and activate it. Notice there are comments at the top indicating how to define selection texts, so apply those changes as well and activate them. If you were to perform a search on the string “from” you will see that there is a single select statement in the code, and perhaps you’ll recognize that one of the sample flights persistence tables supplied by SAP for training purposes is being used by this program.
Take a moment to familiarize yourself with the program. It is not particularly well-written, violates some best ABAP programming practices and contains more than a few bugs. Notice especially that it contains no object-oriented code nor does it instantiate or invoke any methods of global object-oriented classes. Its purpose is to produce an ALV report, list or grid depending on what is specified on the initial selection screen, of the flights associated with a specified airline carrier abbreviation, to include a discount of a specified percentage applied to each flight price.
Executing the program
Now execute this program from within the editor (F8), specify Airline value ‘AA’ on the initial selection screen and press Execute. You should find that error message “No flights match carrier AA” appears at the bottom of the screen. This is the expected result for executing this program in its current state. If you were to go looking for the reason why the error message appeared you might find it easily enough and be tempted to make the necessary correction, but don’t do that. Instead, let us see how the process of creating automated unit tests for this program will cause that problem and a few others to be revealed when the automated unit tests are executed.
Requesting the execution of unit tests
The next step is to run the automated unit tests for this program. To do this from within the ABAP Editor, select from the menu: Program > Execute > Unit Tests (or use keyboard combination Ctrl+Shift+F10).
Yes, we have no tests written; We have no tests written at all
“Whoa, wait a moment, Jim!” some of you might exclaim, “This program has no unit tests and we did not write any new ones for it yet.” Though that is true, that fact does not prohibit us from initiating the process of running any automated unit tests defined for this program. Once you have done so, using the menu path specified or through the equivalent keyboard combination, you should see a warning message appear at the bottom of your screen indicating:
The program <program name> contains no executable unit tests
Accordingly, it is unnecessary to know ahead of time whether or not a program has any automated unit tests before attempting to run them.
So not only do you now know how to initiate the execution of automated unit tests, but you also see that a warning is issued when the program has no associated automated unit tests. You should get this same warning message when you similarly attempt to run the non-existent automated unit tests for any one of the thousands of other customized programs in your program repository.
Confirming there are applicable records to be presented
It is entirely possible that the sample flights persistence tables supplied by SAP for training purposes contains no records for airline ‘AA’ as we specified when executing the program. So at this point we cannot be certain whether the error message “No flights match carrier AA” was issued legitimately, due to a lack of such records, or was caused by a bug in the program. Let’s check that right now. Invoke transaction SE16N (or similar) for table SFLIGHT, specify Airline code ‘AA’ and press Execute. If you find there are no records for this airline code, then use test data generator program SAPBC_DATA_GENERATOR to generate bulk records or use transaction BC_GLOBAL_SFLGH_CREA (utility program SAPBC_GLOBAL_SFLIGHT_CREATE) to generate specific records individually. In the end, insure there are some records existing in this table for each of the following three airline codes: AA (American Airlines) ; LH (Lufthansa); UA (United Airlines).
So, let’s summarize what we have learned here:
- The ABAP Editor provides the capability to execute automated unit tests.
- ABAP unit tests can be written for programs that contain no object-oriented code at all.
- Initiating the execution of automated unit tests from within the ABAP Editor is performed through menu path Program > Execute > Unit Tests or by pressing keyboard combination Ctrl+Shift+F10.
- The attempt to run automated unit tests for a program having none will produce a warning message alerting us to their absence.
Since attempting to initiate the execution of automated unit tests against a program having none results in a warning message, the next step is to write an automated unit test for this program, a program which at this point contains no object-oriented code. This will be covered in the blog representing part 2 of this 10-part series Getting acquainted with automating ABAP unit testing.
I hoping the final result of this blog will be a program that's fully in a test harness and doesn't use any obsolete code like "FORMS".
I watch with interest!
You are not going to be disappointed.
Jim has to start with 100% procedural code and no tests as (sadly) that is what people are used to. All the current examples you see on the internet start with OO code, and that puts people off.
So you have to move people from A to B one little tiny baby step at a time, each time stressing why this baby step in particular makes you better off than you were before. Los of little clearly defined small changes that make you better off a tiny bit at a time work much better than "Start using OO because it is good" or "TDD is really good, look at all these OO Java examples!"
It's like that quote Eddie Murphy uses in "Coming to America" - "you must walk to fly - you cannot fly to fly"
BTW as a disclaimer myself and Jim are an ABAP double act as he does a technical review of my books and I do a technical review of his. We are also planning an actual ABAP comedy double act.
This is how we see ourselves:
James, looking forward to following this blog!
I agree even in today ABAP Unit Test or Unit Testing a whole is looked as an optional for many projects.
There are various reasons to it,
Development systems data is poor.
Lack of understanding on how to perform a unit test.
This blog should definitely help developers to understand the importance of automated unit tests.
Thanks for taking the time to share.
sorry a lot of text for (me) trivia.
What should be the difference between a unit test and a automated unit test?
Excited to see the automation...