Technical Articles
Short examples of CL_ABAP_TESTDOUBLE
There are only a few posts about CL_ABAP_TESTDOUBLE. I didn’t find it so trivial to use, even tricky sometimes. Even Google returns only 14 results for “CL_ABAP_TESTDOUBLE”. The most interesting information I could find is Prajul Meyana’s ABAP Test Double Framework – An Introduction, including a nice presentation video. This post is a very good starting point, but it would benefit from more examples.
So, I decided to create simple examples, for mocking methods with IMPORTING, EXPORTING, CHANGING and RETURNING parameters, class-based exceptions and events, and also the method TIMES.
Those examples are provided as a standalone demo program, containing an ABAP Unit class (all tests are passing, of course), in which you can check how test double call configurations are to be written, in case you have some doubts or issues with your own test doubles. I hope that those methods are enough self-documented. I tested this program in an ABAP 7.52 system.
Note that I don’t talk about “answers”, “matchers” and “expectations”, which allow dynamic decisions at runtime. They are explained in Prajul Meyana’s blog post.
Before showing the code, I tried to summarize a few key concepts that were not clear to me.
But, before going forward, please first read Prajul Meyana’s blog post, as it’s important that you first understand what the Test Double Framework is all about.
I hope this post will be useful.
Have fun with the ABAP Test Double Framework!
===> LATEST NEWS, March 16th, 2019: hey, what is the real point of using the ATD Framework, if you can simply create your own local class implementing the interface and code the rules you want… (discussion here: https://blogs.sap.com/2015/01/05/abap-test-double-framework-an-introduction/#comment-452240). By the way I moved the code to Github.
Reminder: limits of CL_ABAP_TESTDOUBLE
- Can mock interfaces only, not classes, and only global interfaces, not local ones.
- Can output exceptions of type “class-based” only (cannot mock those original old exceptions).
- A maximum of 36 interfaces can be test-doubled per test class, because the method CREATE of CL_ABAP_TESTDOUBLE generates one class at runtime by using a GENERATE SUBROUTINE POOL statement (generation of maximum 36 subroutine pools).
- Demonstrated in the test method “MAX_TEST_DOUBLES”
- Can’t mock static methods and events.
- Available from 7.40 SP 9. It seems that there’s no downport planned.
- Cannot mock parameters declared EXPORTING instead of CHANGING
- See below chapter “Parameters declared EXPORTING instead of CHANGING”
- Demonstrated in the test method “EXPORTING_BAD”
Reminder: call configuration = always 2 calls
What I didn’t clearly understand from Prajul Meyana’s post, was the obligatory sequence and presence of both the method CONFIGURE_CALL to define the output values, and the next method call on the test double to define the method name and input values, shortly said.
So, any call configuration must always (there’s no exception to this rule, never!) be made of these 2 consecutive calls, in this exact sequence:
- First call: define the output values or raise an exception, ignore some input parameters (to say any input value), and additionally raise events(s), etc.:
- CL_ABAP_TESTDOUBLE=>CONFIGURE_CALL( TEST_DOUBLE )-> … here chained methods …
- Example which uses almost all possibilities presented in this post:
cl_abap_testdouble=>configure_call( test_double
)->ignore_parameter( 'IN1'
)->ignore_parameter( 'IN2'
)->returning( 40
)->set_parameter( name = 'OUT1' value = 38
)->set_parameter( name = 'OUT2' value = 'X'
)->times( 999999999
)->raise_event( name = 'ACTION_DONE' parameters = VALUE #(
( name = 'PARAM_NAME' value = REF i( 20 ) ) )
)->raise_event( name = 'ACTION_DONE' parameters = VALUE #(
( name = 'PARAM_NAME' value = REF i( 40 ) ) ) ).
- Second call: choose the method and its input values (including the input values of changing parameters):
- TEST_DOUBLE->METHOD( input and changing parameters )
- The input values are only tested for equality by the framework, all other test operators are to be manually coded using “matchers” (not detailed in this post).
- There’s also another possibility, which is to define “any value” for one or more parameters. This must be done by:
- First, using the methods IGNORE_PARAMETER and IGNORE_ALL_PARAMETERS in the CONFIGURE_CALL above.
- Second, enter dummy values for all those “ignored” parameters in the second call. You may not enter those dummy values for those parameters which are optional).
- Don’t indicate parameters from the IMPORTING category, otherwise you’ll get an exception at runtime. Those parameter values must be passed via the SET_PARAMETER method in the first call.
If you want to mock more combinations of input and output values, like defining a decision table, or mocking several methods, you may define several call configurations, as many times as you want, on the same test double.
So, again, don’t forget, a call configuration is always composed of 2 calls: a call to CONFIGURE_CALL, followed by a call to the method to be configured.
Summary of the methods of the call configuration
Those methods belong to a so-called “fluent interface”, meaning that each instance method returns their own instance, so that you can chain the methods easily (cf the CONFIGURE_CALL example above).
- SET_PARAMETER:
- It sets the value of one parameter to be returned by the configured method; it’s valid only for exporting and changing parameters. For the returning parameter, you must use the RETURNING method. You must not mix SET_PARAMETER with RAISE_EXCEPTION in the call configuration of course.
- cf code of the test methods EXPORTING_GOOD and EXPORTING_BAD below.
- RETURNING:
- It sets the value for the returning parameter. You must not mix RETURNING with RAISE_EXCEPTION in the call configuration of course.
- cf code of the test method RETURNING_GOOD below.
- RAISE_EXCEPTION:
- It triggers a class-based exception. You must not mix it with SET_PARAMETER and RETURNING in the call configuration, and only one RAISE_EXCEPTION can be used of course.
- cf code of the test methods RAISE_EXCEPTION_BAD and RAISE_EXCEPTION_GOOD below.
- RAISE_EVENT:
- It triggers an event (eventually with parameters) when the configured method is called; you may raise several events if you want to.
- cf code of the test method RAISE_EVENT_GOOD below.
- IGNORE_PARAMETER:
- This method is used to implement a wild card “whatever the parameter value is” for a given parameter. You may call it several times, once per each parameter you want to “ignore”. It can be used for a Changing parameter.
- cf code of the test methods IGNORE_PARAMETER_BAD and IGNORE_PARAMETER_GOOD below.
- IGNORE_ALL_PARAMETERS
- Same as IGNORE_PARAMETER, but it ignores all the importing and changing parameters.
- cf code of the test methods IGNORE_ALL_PARAMETERS_BAD and IGNORE_ALL_PARAMETERS_GOOD below.
- TIMES:
- If not specified, the default is TIMES( 1 ), i.e. this call configuration is applied once.
- Important: if the configured method is called more times than the number indicated, the framework applies the last created call configuration for the method. This is demonstrated in the test methods EXPORTING_TWICE_GOOD and RETURNING_TWICE_GOOD below.
- This is very important to understand, especially when you define generic input values, as this kind of call configuration would probably be consumed many times, so you should use the method TIMES with a high-enough value. This is demonstrated in the test methods DECISION_TABLE_BAD* and DECISION_TABLE_GOOD below.
- For simple usages, cf code of the test methods TIMES_BAD and TIMES_GOOD below.
The following methods are not explained in this blog post, for more information cf Prajul Meyana’s blog post:
- SET_ANSWER:
- In short, first create an “answer” class which returns some output values (or exceptions) based on the input values, of any method, and you pass an instance of it to SET_ANSWER.
- SET_MATCHER: (out of scope)
- In short, first create a “matcher” class which determines whether the call configuration applies or not based on the method and its input values, and you pass an instance of it to SET_MATCHER.
- AND_EXPECT: (out of scope)
- In short, it defines the expected number of calls of the configured method. The expectation is to be checked by calling the method VERIFY_EXPECTATIONS of CL_ABAP_TESTDOUBLE. Note: this method must always be used at the end of the chain, because it returns an instance which is not the call configuration.
Case study of an “else if” decision table with generic values, and why TIMES is so important
While doing the test methods DECISION_TABLE_BAD* and DECISION_TABLE_GOOD, I discovered how important the method TIMES is.
Those test methods test the method DECISION_TABLE, which is defined as follows:
METHODS decision_table IMPORTING p1 TYPE i p2 TYPE i RETURNING VALUE(result) TYPE i.
The call configurations defined in the test method DECISION_TABLE_GOOD corresponds to this decision table:
Input P1 | Input P2 | Output RESULT |
5 | 5 | 10 |
Any value | 5 | 20 |
5 | Any value | 30 |
Any value | Any value | 40 |
Note that the sequence of the lines is important, as there is to be an “ELSE IF” rule, i.e. if P1 and P2 parameters have both values 5, all four lines of the decision table could apply, but using the ELSE IF rule, only the first line will be applied for those values and the output will be 10.
The call configurations corresponding to the decision table were written as follows, the order is as much important as in the decision table because the Test Double Framework chooses the “first” matching configuration (in the chronological order of their creation):
cl_abap_testdouble=>configure_call( test_double )->set_returning( 10 )->times( 999999 ).
cut->decision_table( p1 = 5 p2 = 5 ).
cl_abap_testdouble=>configure_call( test_double )->ignore_parameter( 'P1' )->set_returning( 20 )->times( 999999 ).
cut->decision_table( p1 = 0 p2 = 5 ).
cl_abap_testdouble=>configure_call( test_double )->ignore_parameter( 'P2' )->set_returning( 30 )->times( 999999 ).
cut->decision_table( p1 = 5 p2 = 0 ).
cl_abap_testdouble=>configure_call( test_double )->ignore_all_parameters( )->set_returning( 40 )->times( 999999 ).
cut->decision_table( p1 = 0 p2 = 0 ).
Note that the convention I use here is to pass the generic parameter values (“ignored parameters”) with the value 0, although any value could be passed. Another value could be chosen if 0 was used as a real input value.
The method call TIMES( 999999 ) is very important:
If you don’t use TIMES or you use TIMES( 1 ) (that’s the same thing), you would get 30 instead of 10 if the method is called twice with the same input values, because the first call configuration would be invalid since it already occurred once. That’s demonstrated in the test methods DECISION_TABLE_*_BAD.
Input P1 | Input P2 | Expected RESULT | Actual RESULT with TIMES( 1 ) |
5 | 5 | 10 | 10 |
5 | 5 | 10 | 30 (WRONG !) |
With TIMES( 999999 ), the results are correct (cf the test method DECISION_TABLE_GOOD):
Input P1 | Input P2 | Expected RESULT | Actual RESULT with TIMES( 999999 ) |
66 | 66 | 40 | 40 |
66 | 66 | 40 | 40 |
5 | 66 | 30 | 30 |
5 | 66 | 30 | 30 |
66 | 5 | 20 | 20 |
66 | 5 | 20 | 20 |
5 | 5 | 10 | 10 |
5 | 5 | 10 | 10 |
Parameters declared EXPORTING instead of CHANGING
Some incorrectly-defined methods use a parameter in the category EXPORTING instead of CHANGING. This design error usually remains unnoticed as, if this parameter is passed by reference, both categories work identically.
But for test doubles, with an EXPORTING parameter, if you want to return an output value depending on a given input value, then it’s impossible because an IMPORTING is forbidden by the framework.
Example:
METHODS cutmethod EXPORTING output TYPE i.
METHOD cutmethod.
output = output + 1.
ENDMETHOD.
You’d like to mock it, but in fact you can’t, as it triggers an ATD exception:
cl_abap_testdouble=>configure_call( test_double )->set_parameter( name = 'OUTPUT' value = 11 ).
data(output) = value i( 10 ).
cut->cutmethod( IMPORTING output = output ). " <=== ATD exception at runtime
The only solutions I can think of, is to refactor the parameter to the category CHANGING, or if you can’t, to configure an “answer” instance (I don’t know whether it works, and I don’t discuss this topic in this post).
Code with the short examples
Finally, here is the code containing the Test Double Framework examples, wrapped into ABAP Unit classes.
https://github.com/sandraros/Demo-ATD
It consists of 2 objects:
- Interface pool (SE24) ZIF_ATD_DEMO
- Executable program (SE38) Z_ATD_DEMO
Nice one! This will be a good reference. Do you think you can provide the coding as a Github repository?
Best regards, Tapio
Thanks. I'll try 🙂
Tapio, for information, the code is moved to Github.
Nice! Thank you!
Hello Sandra,
Nice blog!
I faced the same problem when i used the ATD Framework the first time 🙂
This sounds a bit ambiguous to me! You can configure them using the SET_PARAMETER( ) as you have mentioned in the blog.
BR,
Suhas
Hello Suhas, thank you! I was afraid that this chapter would not be clear enough, as this case is very rarely encountered (but I know this case and I wanted to test it). I was talking about the Exporting parameters, those which are output by the methods, not the Importing parameters (input of methods). Or are you really saying that SET_PARAMETER is also able to define the "input value of an exporting parameter"? That would surprise so much that I didn't even try it.
Hi Suhas, one year later, I've just read your comment again, and I think I didn't reply correctly. I had dedicated the chapter "Parameters declared EXPORTING instead of CHANGING" to clarify the point, and I had also created the method "EXPORTING_BAD" to show it's impossible to use SET_PARAMETER to pass a value to the Exporting parameter in the configured call.
Really nice! I appreciate the time you put in and thanks for sharing this!
Thanks a lot. Yes, it took me a lot of time to do it, and probably more than necessary as it was my first blog post in SCN (stress to (try to) do a good job) 🙂
Nice one. I like the format with examples between. Do you use the test framework in your daily work as well? Would be nice to have also a "real world" example.
~florian
Thank you. I often use ABAP Unit, but not the ABAP Test Double framework (not yet). This post is a result of my discoveries. I'd like more real world examples too, anybody there? 🙂
First you have to find more than a handful of people that even do this level of testing. haha
Excellent blog - I love code examples. It reminds me again that really have to start writing tests.
Michelle
Thanks for the code.
I could not find interface IF_ADT_DP_DBG_AMDP on Netwaver 7.51, so the test failed. I used IF_ADT_FQL_FUNCTION instead.
JNN
Thanks. My code was tested on 7.52. I'll try to find out a list of classes which used to exist in 7.40.
Hi Jacques, I have updated the code so that the method MAX_TEST_DOUBLES works on any system (it takes randomly a list of 37 interfaces from the table of class/interface pools SEOCLASS). And it's on Github now.
I get this error in Interface ZIF_ATD_DEMO. Do I need to change anything?
Interface ZIF_ATD_DEMO
Method EXPORTING_RETURNING may not have a RETURNING parameter and an EXPORTING or CHANGING parameter at the same time.
My code ran on 7.52. ABAP doesn’t support this syntax before 7.40 SP 2
Hi Sandra,
I could imagine how big the effort you made to prepare all the stuffs for this Test Double Framework.
It's fascinating! I already downloaded your sample codes and tried to learn the details.
Really appreciate your contribution.