A Spotlight on ABAP Unit Part 2
This is part 2 of a weblog series on ABAP Unit. If you are interested, you find the other weblogs of the series here: A Spotlight on ABAP Unit Part 1, A Spotlight on ABAP Unit Part 3, A Spotlight on ABAP Unit Part 4, A Spotlight on ABAP Unit Part 5.
In the last weblog I have shown you in a simple example on how an ABAP Unit test is implemented within the production program. The trouble is only that the example is so easy that you might wonder if such a simple test is worth it at all.
In this weblog I will give you a more “real world” example (though, of course, still simplified) which will demonstrate: With ABAP Unit it is easy to discover side effects of program modifications, which might go unnoticed otherwise or might only be detected with much more effort.
Bound for Trouble – Almost a Real-World Example
A company has a flight booking system. Their employees can look for flights by inputting the flight-date, the location of departure and the destination into the program. As a result, the program presents a list of flights ordered by date, cityfrom and cityto. As the controlling department wants employees to book cheap flights, it asks the IT-department to sort the flights by price ascending in the list output.
As it happens, the developer of the program is on holiday. So the modifications to the program are accomplished easily by a student, who is not very experienced in business programming. He is happy that just by browsing over the program he recognizes what he considers to be changed. Running the program shows the expected result: The list is sorted by price.
So the modification is transported to the production system. Unfortunately for the employees and the programmer, the modification of the code has unexpected and unwanted effects: As soon as the new code is activated the program starts to behave in a strange way. Some new flights cannot be added, while adding already existing flights, which should be prevented by the program logic, is now sometimes possible.
What Has Gone Wrong – an Analysis
Let us look a bit deeper into the code to understand what has happened: The flights available are kept in an internal table IT_FLIGHTS which is part of a global service class CL_TECHED04_AU_FLIGHTS. This class offers all the methods needed to add, delete or list flights, while the report zbgtest_flights handles the parameter input of the user and the output of the table in a list.
The answer to the question why the internal table is not sorted by price at first is obvious when going into the code a bit: Its type is defined by the type
which in turn is defined by:
types:Ty_sflight_it type sorted table of ty_my_sflight with unique key carrid connid fldate .
So our unhappy student felt seduced to change simply the table key in this definition and supposed that this way he would get the desired output:
types: Ty_sflight_it type sorted table of ty_my_sflight with unique key price.
Of course, more experience with the pitfalls of internal table programming might have prevented a developer from modifying the program in this way. What has gone wrong? The original table had a unique composite key consisting of carrid, connid and fldate because these attributes identify a flight in a unique way. This reflects the fact that it makes no sense to keep the same flight in the table twice.
Of course, you may want to add to the table a new flight that incidentally has the same price as some flight which is already in the table. But since the price is the table key in the modified version, this adding is no longer possible. The situation gets worse by the fact that you can now add an already existing flight to the table if it has a different price, which might easily happen, if a digit is changed by accident.
Preventing the Trouble With ABAP Unit
With ABAP Unit, this trouble would not have happened. Using ABAP Unit would have prevented that the problematic code was transported to the production system. Covering all the methods with unit tests and running these tests after every modification on a regular base would have shown that something has gone wrong. And this is all achieved without further analysis. The proper usage of ABAP Unit automatically detects mistakes like the one just shown.
As this is no more than a promise to you by now, l will show in some detail, in which way a pretty simple ABAP Unit test would have forestalled this unhappy course of events.
Some Test Code
Of course, we test a whole bunch of methods in the global class as only complete test coverage enables you to detect side effects with a decent probability:
Let us have a look at the implementation of the first test method:
CLASS test_flights IMPLEMENTATION. METHOD add_flight. DATA: l_act TYPE i, l_exp TYPE i. cut TYPE REF TO cl_te4h_aunit_flights. * " some predefined flights: flight1 TYPE cl_te4h_aunit_flights=>ty_my_sflight. … * predefine some flights: flight1-carrid = 'AA'. flight1-connid = '0017'. flight1-fldate = '20040101'. flight1-price = '1000'. flight2-carrid = 'UA'. flight2-connid = '0400'. flight2-fldate = '20040301'. flight2-price = '2000'. flight3-carrid = 'LH'. flight3-connid = '0017'. flight3-fldate = '20040101'. flight3-price = '300'. *** extra flights for test flight4-carrid = 'AL'. flight4-connid = '0017'. flight4-fldate = '20040501'. flight4-price = '300'. * setup class under test: CREATE OBJECT cut. * add first 3 flight for our tests cut->add_flight( flight1 ). cut->add_flight( flight2 ). cut->add_flight( flight3 ). n_setup_flights = 3.
So much for the preparations for our test. Now the real test:
*** add not existing flight 4 to the initial 3 flights cut->add_flight( flight4 ). l_act = cut->get_count( ). l_exp = n_setup_flights + 1. cl_aunit_assert=>assert_equals( act = l_act exp = l_exp msg = 'flight not added.' ).
Surely, you have no trouble understanding what is happening in our test method: First, we define two variables for the expected and the actual number of flights in the internal table IT_Flights, one variable for an instance of the class we want to test and four structures of the line type of IT_Flights. We fill these four structures, create an instance of the production class we test and add three structures to the internal table by using the method add_flight of this class.
In the same manner as in our first example we perform the test in the assert_equals method. In our example we add a fourth structure to the internal table and test if the number of lines in the table has increased by one as it should be if the portion of code under test worked properly. Remember that after the modification of the program it is no longer possible to add a flight with the same price as an already existing flight to the internal table. Flight number 4 has the same price as flight number 3, and so this flight will not be added to the internal table. If the assert condition is violated, an error is raised which is shown by the ABAP Unit Tool after the test is run.
Still Skeptic about ABAP Unit?
Again, the more skeptic-minded among you might not be really convinced. They might suppose a trick: Why did we test by sheer accident just the method which was affected by the side effect with just the right test data? Is this not a very improbable coincidence?
As I will show you in the next part of this flash light on ABAP Unit, it is of course not the mere writing of ABAP Unit tests that does the job all by itself. Obviously, tests have to be cleverly devised according to certain principles. This is what I will tell you next time: a little bit of theory on which tests with ABAP Unit should be based.
I'm searching for more examples of how to use and implement test classes for ABAP Unit tests.
In the documentation I found the following recommendation:
It is recommended that you place all test classes in a program below the production code as this will provide the test classes with access to all data and procedures of the program.
Actually it would be very helpful how to do this for the following use cases:
1. Production code is a function module: where should I put the test class? Would the TOP include of the function group be the right place?
2. Production code is a class: where to put the test class and how does it work?
3. Production code is a method: where to put the test class?
Is there any good tutorial to answer these questions, if possible under NW2004s?
Thanks & regards,
Consider this simple scnerio:
Extract from database table
Display the data in report
Now I want to use Unit to find out the contents of the db table extract and the ret code. And then I want to know the contents coming out of each of my routine. Would I have to replicate all this code in the test class that will reside after all the production code? And then call methods in CL_AUNIT* classes after each subroutine to determine the results?