In this blog series, I will share my understanding and knowledge about ABAP Unit Testing and TDD approach. As this itself is a vast topic, I will first begin with Unit testing fundamentals, then delve into testing framework. Later I will write about TDD, its necessity and illustrate how to achieve it practically. The blog series is structured in such a way that it goes from scratch, and later goes in-depth into the concepts.
In this blog post, I will give an introduction about unit testing fundamentals and the basic concepts of Unit Testing. I will also give an example of a very simple unit test in ABAP, covering positive and negative scenario. This blog is particularly for absolute beginners who would like to start off in a systematic way to Unit Testing in ABAP.
5 WH Analysis of Unit Testing
To begin with a brief introduction, let me first answer the 5 WH questions for Unit Testing.
Table 1: 5 WH Analysis of Unit Testing
|What||Approach for testing the behavior of a unit of production code|
To verify whether the independent behavior of every single unit of the code is as per the expectations.
To cover all the positive and negative scenarios, covering all those which cannot be tested otherwise.
|When||During the development stage, preferably as part of Test-Driven Development (TDD)|
By restricting the communication of the unit from all other artifacts, which it is dependent upon, for its successful execution.
Following FIRST Principles.
Using ABAP Unit Test Framework, Mocking the dependencies, Test Doubles, Wrappers, Test Seams, Code Refactoring
“Unit” in Unit Testing
Before going into the concepts of unit testing, we need to have a clear idea about what a “Unit” means, in the term “Unit Testing”.
Unit is basically the smallest piece of the code, that can be logically isolated and tested in such an isolation.
In ABAP, a unit can be a method, function module, subroutine etc.
Each unit does its intended tasks, by means of the logic written within it. This will result in certain effects, depending upon the given conditions. These effects can broadly be any of the two below:
Table 2: What to verify in Unit Test Results
|Effect||What to verify in the unit test results|
|Change the state of the system||Changes made to the concerned variables|
|Communication with other units||Whether the call / invocation is made or not|
Fundamental concepts and terminologies in Unit Testing
- CUT – Code Under Test / Class Under Test
As the name suggests, this is the actual production code which is to be tested. In Object-Oriented Programming, this CUT is usually the class.
- LTC – Local Test Class
We are supposed to define and implement a unit testing class to implement unit testing test our CUT. This test class, which is local to the CUT is called as the Local Test Class (LTC). It is very important to mark this class for testing, by using the “FOR TESTING” keyword in the definition, so that at the runtime it is executed as test class.
- UTM – Unit Test Methods
These are the methods of the LTC. They can be either public or private. It is very important to mark these methods for testing, by using the “FOR TESTING” keyword in the definition, so that at the runtime these are executed as test methods.
As mentioned in the 5 WH analysis, we test the behaviors of our production code.
It is evident that any given unit will exhibit multiple behaviors depending on the scenario. Each such scenario is a “Test Case”. These are classified as positive and negative test cases.
Unit test methods will invoke the corresponding unit of the CUT. As a good practice, it is suggested to write one unit test method for one scenario for one unit.
Figure 1: Interaction of LTC with CUT
- FIRST Principles
FIRST is an acronym for – Fast, Independent, Repeatable, Self-validating, Timely.
Unit tests should comply with the below mentioned FIRST Principles:
Table 3: FIRST Principles of Unit Testing
|Fast||The test cases should be very quick in execution, including setup, actual test and teardown time. Setup and teardown will be explained a bit later in this blog|
|Independent||One test case should not depend upon other test case for execution. Thus there is no interference amongst the test cases. This also ensures that there is no dependency of order of execution of test cases|
|Repeatable||The test cases should return the same result each time they are executed|
|Self-validating||The unit test itself should convey whether it has passed or failed. In case of failure, the test should point out the location of failure|
|Timely||This is related to Test Driven Development (TDD) approach where we write the unit tests before the actual production code is written|
- DOC – Depended-On-Component
The CUT will possibly interact with various artifacts (which are not defined in the unit) like DB Tables, DDIC Views, CDS Views, File system, Web Services, trigger output, invoke other units etc. These are the components, on which the CUT is dependent on, for its successful execution. These are termed as the DOCs.
However, according to the FIRST principles, the unit tests should be independent and fast.
Also, there are some difficulties if the LTC access the DOCs while execution, the details of which will be addressed in the next blog.
We prefer to mock all these dependencies by test doubles. These mocks / doubles of the DOC will replace the actual DOC during the unit test runtime. OSQL Test Double Framework handles this very well in the test environment when the DOC is a database object or a CDS view. In order to keep things simple in this blog, I have not mentioned the details here. I will provide a deeper look into this in the next blog.
By its meaning, the word “assertion” means “a statement that you strongly believe to be true”.
As seen in the 5 WH analysis, we verify whether the unit test result is as per the expectations.
In ABAP, we use the static methods of class cl_abap_unit_assert. We pass the actual and expected values as parameters to these methods. So, we make our assertions for the behavior (result of the unit test) and pass them as parameters.
- Test Fixtures
A test fixture is something which provides a test environment to the entity which is to be tested, so that the test executes consistently each time it is run on the entity. Test fixtures are used in multiple areas, be it software, electronics, manufacturing etc.
Test fixture methods play a vital role in the structure of Unit Test modules.
In unit testing, we have to setup the test environment and the testing frameworks. In ABAP, we use setup() and class_setup() methods.
At the end of the test we destroy (teardown) this setup. This is done using the teardown() and class_teardown() methods.
Test fixtures are defined and implemented in the private section of the ABAP LTC. Let us see a short description of each of these fixture methods:
Table 4: Test Fixtures for ABAP Unit Testing
- The 4-Phase test approach
Consider a real-life scenario that you are giving an online examination, where your scores will be displayed immediately after the exam:
- The first thing you do is complete the necessary revisions, setup your system for the test, arrange the required stationery, peace out yourself etc. This is the “Setup” phase.
- Then, the test begins. Within the specified time frame, you solve the questions, mark the answers and submit the test. This is the “Exercise” phase.
- Now, your submitted answers are evaluated. Your answers are verified with the expected answers and grades are given accordingly. The system displays your scorecard. This is the “Assert” phase.
- After getting your scores you close the system, leave the exam center, go home and keep aside all the study material you referred for this exam. This is the “Teardown” phase.
Unit tests are structured according to the 4-Phase approach – Setup, Exercise, Assert, Teardown:
Setup: Setup the test fixture
Exercise: Interact with CUT
Assert: Do necessary things to verify whether the expected outcome has been obtained
Teardown: Teardown the test fixture
Example for practical demonstration
I will now show you a very simple example to get a clearer understanding of the theory mentioned above.
Here, I have created a class ZCL_DEMO_UNIT_TEST_SESSION with one method GET_YRS_OF_SRVC.
Figure 2: Class Under Test for demo example
This method takes in date of hire of an employee and provides the years of service. It has one importing parameter and two exporting parameters. The method signature and implementation are shown below.
Figure 3: Production method definition
Let us now find out positive and negative scenarios (test cases) for this method.
The importing parameter here is the date of hire. So, a possible negative test scenario will occur if the date of hire is a future date. Positive test case is thus when the date of hire is a date in the past, or even the current date. As clearly seen in the code, in the positive case, the method returns the years of service. And in the negative case it will return a message. Once this is clear, we can now start writing the LTC.
As far as the naming for the LTC goes, as a suggested good practice, we name it as ltc_<name of CUT, dropping the “cl_” prefix>
In this case, the LTC is – ltc_demo_unit_test_session
Figure 4: LTC Definition
Let me walk you through this definition of LTC. As we all know, any type of class has two parts – definition and implementation.
Similarly, we first write the class definition for our LTC. However, do you notice some extra key-words in Line 2?
These are the keywords which distinguish a ABAP test class from a usual ABAP class. Let us have a look at them one-by-one.
FOR TESTING : This addition is used to define a class as a test class
The below two are the test attributes.
RISK LEVEL HARMLESS : This indicates the effect which the test will have on the database. In short, the amount of risk that the test poses on the underlying database.
There are 3 risk levels defined – Critical, Dangerous, Harmless.
DURATION SHORT : The maximum time limit for execution of the entire LTC. This is to ensure that the unit tests don’t run for too long and thus comply with the FIRST principles.
There are 3 duration types defined – Short, Medium, Long
At the end of the blog I will mention about the transaction code SAUNIT_CLIENT_SETUP, where the customizing for these test attributes is maintained. For now, let us continue with our LTC.
As we have identified one positive and one negative test case, so in the public section there will be a total of 2 UTMs. Again, we add the keyword “FOR TESTING” here. As a suggested good practice, we should name these methods in a systematic way as mentioned in FIGURE. This will improve the readability and maintenance efforts for your LTC.
In the private section we define our test fixtures. I had mentioned earlier that we instantiate our CUT in the setup() method. For this, we define an object here in the private section and later use this object to create and destroy the instance of CUT.
Now coming to the implementation of this LTC.
Figure 5: LTC Implementation – Test Fixtures
Since there are no DOCs in this simple example, the static methods class_setup() and class_teardown() are empty.
In the setup() method we are instantiating the CUT and subsequently clearing this instance in teardown().
Below is the implementation of both the test methods.
Figure 6: LTC Implementation – Positive test case
Figure 7: LTC Implementation – Negative test case
As you can see here, there are 3 sections for any test case – Given, When, Then.
Table 5: Sections of ABAP Unit Test
Here we provide the value for the importing variables of our production method, according to the test case.
Also, we do the necessary data declaration to create variables which are necessary to handle the data returned by the production method.
Here we invoke the production method of our CUT using the instance of CUT.
We also provide the necessary importing parameters and receive the returned data (actual values) back from the method.
Now that we have executed the method, we make the necessary assertions to compare the actual and expected behavior.
This will indicate whether the test case has executed successfully or not.
To execute the unit test, follow the menu options as shown in Figure 8 .
On executing the unit tests with coverage, we get the various coverage metrics as shown in Figure 9.
Figure 8: How to execute Unit Test with coverage
Figure 9: Coverage metrics
This was a practical illustration of a very simple unit testing scenario, just to get familiar with ABAP unit testing and keep things simple for beginners. In reality we have much more complex scenarios where we use approaches like test doubles, wrappers and test seams.
In my next blog blog post, I will cover test doubles and Open SQL Test Double Framework. Before closing the blog I will mention about the transaction SAUNIT_CLIENT_SETUP as I had mentioned earlier.
Figure 10: Transaction SAUNIT_CLIENT_SETUP
Limit of risk level: Indicates what is the maximum risk level of test classes that can be executed in this particular client. As shown in Figure 10, for this client the limit is set to “Critical”. Thus, tests with all the 3 risk levels are parmitted to run in this client.
Long, Medium, Short Duration: Indicate the maximum time in seconds for which the unit tests should preferrably run. If the LTC takes longer than this time to execute, system issues a warning message after the execution.
In the above example, the risk level of our test class is “harmless” and duration is “short”. This means that this test can run in this client given its risk level, and the test duration is not supposed to extend beyond 60 seconds, thus complying with FIRST principles.
Link to my next blog post in this series. : “Test Doubles” and using OSQL Test Double Framework for ABAP Unit Tests. This blog will explain about Test Doubles and unit testing when database tables are queried in the code.