Application Development and Automation Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
cancel
Showing results for 
Search instead for 
Did you mean: 
Read only

How to write Unit Test for Function Modules using Test DoubleFramework?

14,053

Hi,

How to write ABAP Unit Test for Function Modules in a Function Group.

Is ABAP test Double Framework can be implemented for the above use case or we need to only TEST_SEAMs to create local Unit Test classes.

Kindly help me with some examples so that i can understand and write the Unit Tests in my scenario.

Thank you very much in advance.

Regards,

Ramana.

14 REPLIES 14
Read only

Sandra_Rossi
Active Contributor
0 Likes
8,409

I don't understand your difficulty specifically for function modules:

  • initialize input data
  • call function module with input data
  • verify expectations of result returned by function module

What is the difference with calling a method?

  • initialize input data
  • call method with input data
  • verify expectations of result returned by method
Read only

matt
Active Contributor
8,409

I'd avoid using test seams - they make a mockery of clean coding, and mix productive code with test code.

You can mock function modules and indeed any access to resources outside of your code under test by abstracting the functionality. So, if you have a FM "Z_MYFUNC" in your code under test that you want to mock/test double, you just create a local interface LIF_MYFUNC with the same signature as the FM, a local class LCL_MYFUNC implementing LIF_MYFUNC that calls that FM and local test double class LTD_MYFUNC FOR TESTING implementing the LIF_MYFUNC.

Writing ABAP unit tests to test function modules/groups is the same as writing them for classes, reports etc.

Read only

gasparerdelyi
Product and Topic Expert
Product and Topic Expert
0 Likes
8,409

I disagree here -- as test seams ensure that the test code will never run in production, which is an extra safety belt.
In case you go for the interfaces, you can make a mistake and then the mock can run in production, too.

Bundling test code with productive code is also desirable in certian situations -- especially when the code is to be transported among several system landscapes.

(Let me also mention that test seams are far better than the good old practice for classes, when a local test class declared friendship and wrote the private attribute that a unit test was running...)

Read only

matt
Active Contributor
8,409

See what bfeeb8ed7fa64a7d95efc21f74a8c135 says here: https://blogs.sap.com/2018/04/21/sap-open-course-unit-testing-week-6-working-with-existing-code/ He says, and I agree, that it's exactly the same as the practice of "If unit_test is running" logic. Just encoded into the language.

You say In case you go for the interfaces, you can make a mistake and then the mock can run in production, too.

I think that's not a valid argument - it's no more convincing than "If you use test seams, you can make a mistake and then the code you meant to put in test seams can run in production too". Any mistake made writing any kind of test code can result in test code running in production. TEST SEAM and using an interface are exactly as likely to encounter this problem. (I.e. extremely unlikely).

Your test doubles should always be marked as FOR TESTING.

Finally, use of TEST SEAM indicates to me that the code under test is not terribly well written. It has been argued this is exactly where TEST SEAMs should be used. Here, the prolofic and famous author "Former Member" comments "...TEST SEAMS do a great job to get totally untested legacy code under test initially. After the initial tests have been established one has the option to apply further reworks / refactorings.".

I'd agree with this. If TEST SEAM is available, they're a stepping stone to refactoring safely. But should only ever be used in that context.

In that blog horst.keller says "If you cannot redesign and rewrite the whole application, as a workaround you make the code test dependent. This is regarded as bad style, but it helps." (My emphasis)

TL;DR TEST SEAMs mix productive and test code. TEST SEAMS reduce the ease of reading productive code. Therefore TEST SEAMs are bad (outside of certain special situations)..

Read only

8,409

In regard to "good old practice for classes, when a local test class declared friendship and wrote the private attribute that a unit test was running..." implies that people used to write classes which had a private variable called something like "mf_unit_test_running" and the production code used that variable.

That's horrific. That's just as bad as TEST SEAMS. I've never seen a class written like that, and I hope I never do.

I jumped for joy when SAP added the ATC check recently which flags the usage of TEST-SEAMS as a red error. That is exactly what it is I am glad that SAP have officially acknowledged it as such.

Read only

gasparerdelyi
Product and Topic Expert
Product and Topic Expert
8,409

I mostly agree with latest comment. Most likely the difference is in the average age of code we usually look at. Most of the time I work with tons of legacy code. Then the main question is what could be the context of the original question. My assumption is that there is likely lots of legacy code around.

Test seams are indicating there is a call to another component which you do not want to test any more. If you expect consistent use of interfaces for stubbing then it might be a far too big jump from legacy code.

If you ask me what do to in case of a completely green field development, then except for very exceptional purposes when the architect exactly knows the reasons, I would even not recommend usage of function modules. However life is most of the times not that simple -- many developments are extending legacy code functionality, and then you have to find a right balance.

Read only

gasparerdelyi
Product and Topic Expert
Product and Topic Expert
8,409

I wonder if you are looking for how to launch the wizard for generation of the skeleton of the local test class in case of a function module?

Read only

matt
Active Contributor
0 Likes
8,409

gasparerdelyi Yes, it could be that. Working with FM in ADT isn't exactly easy (unless I've missed something).

Read only

gasparerdelyi
Product and Topic Expert
Product and Topic Expert
0 Likes
8,409

Test classes then can be generated from SE80, too from the context menu for the navigation hierarchy for the function group entry: Create -> Other Objects -> Generate Test Class.

Otherwise, Test Double Framework is independent from what kind of object is the test class created for... my other guess would have been a general question for TDF///

Read only

felipe_dimmu23
Participant
8,409

I think the pattern you are looking for is "Facade", plus some dependency breaking.

Create an interface for the FM and two classes, a mock and a 'real'. Inject the mock during tests, look up for the dependency when not provided. Something like this:

Hope it helps,

Regards,

Felipe

Read only

felipe_dimmu23
Participant
0 Likes
8,409

And I agree with @Matthew Billingham, although test seams are a bad practice, and can always be done in a different and better way, it does not mean you should scrap from your tool set.

I use it quite often to get past authorization objects, for instance, you don't even need to have any code in it.


"productive
test-seam authority_check.
end-test-seam.

if sy-subrc <> 0.
...


"test
test-injection authority_check.
 sy-subrc = 0.
end-test-injection.

Would it really add any value putting everything behind an interface and creating a dependency to it?

Regards,

Felipe

Read only

matt
Active Contributor
0 Likes
8,409

You'll see in the later discussion I concede some use - but only while you're getting code into a test harness.

But I think in your case, you should be encapsulating the authority checks away. For example, a single interface/class for all authority checks, with a method for each check. The double then just always return abap_true (it is authorised).

See - test seam is a code smell. It indicates you should encapsulate/abstract further.

Read only

Ketki_Kulkarni1
Product and Topic Expert
Product and Topic Expert
0 Likes
8,409

Hi Ramana,
Please use following code.

METHOD mock_fi_company_code_data.
function_name = 'FI_COMPANY_CODE_DATA'.

"create function test doubles for the given list of function modules and returns an instance

"of test environment. The test environment instance would be used to access the individual test double.

DATA(function_double) = cl_function_test_environment=>create(
VALUE #( ( 'FI_COMPANY_CODE_DATA' ) ) )->get_double( 'FI_COMPANY_CODE_DATA' ).

" 1. we need to create the input configuration which would contain the expected set of input values on function call

" 2. we need to create the output configuration which would contain the output values expected on function call

" for the given input values.

" 3. configure test double with input and output test data configuration


DATA(input_config_1) = function_double->create_input_configuration( )->set_importing_parameter( name = 'I_BUKRS' value = 'ES01' ).
DATA(output_config_1) = function_double->create_output_configuration( )->set_exporting_parameter( name = 'E_T001'
value = VALUE t001( bukrs = 'ES01' land1 = 'ES' ) ).

" configure test double to return the values configured in the output configuration

" when the function module is invoked with the exact list of input arguments defined in

" input configuration


function_double->configure_call( )->when( input_config_1 )->then_set_output( output_config_1 ).

DATA(input_config_2) = function_double->create_input_configuration( )->set_importing_parameter( name = 'I_BUKRS' value = 'US01' ).
DATA(output_config_2) = function_double->create_output_configuration( )->set_exporting_parameter( name = 'E_T001'
value = VALUE t001( bukrs = 'US01' land1 = 'US' ) ).
function_double->configure_call( )->when( input_config_2 )->then_set_output( output_config_2 ).

ENDMETHOD.

Note - This feature is available from APAP 7.53 onwards. Please check your OP release if this is available.

Read only

Ketki_Kulkarni1
Product and Topic Expert
Product and Topic Expert
0 Likes
8,409

Hi Ramana,

Please use following code.

METHOD mock_fi_company_code_data.
function_name = 'FI_COMPANY_CODE_DATA'.

"create function test doubles for the given list of function modules and returns an instance

"of test environment. The test environment instance would be used to access the individual test double.

DATA(function_double) = cl_function_test_environment=>create(
VALUE #( ( 'FI_COMPANY_CODE_DATA' ) ) )->get_double( 'FI_COMPANY_CODE_DATA' ).

" 1. we need to create the input configuration which would contain the expected set of input values on function call

" 2. we need to create the output configuration which would contain the output values expected on function call

" for the given input values.

" 3. configure test double with input and output test data configuration


DATA(input_config_1) = function_double->create_input_configuration( )->set_importing_parameter( name = 'I_BUKRS' value = 'ES01' ).
DATA(output_config_1) = function_double->create_output_configuration( )->set_exporting_parameter( name = 'E_T001'
value = VALUE t001( bukrs = 'ES01' land1 = 'ES' ) ).

" configure test double to return the values configured in the output configuration

" when the function module is invoked with the exact list of input arguments defined in

" input configuration


function_double->configure_call( )->when( input_config_1 )->then_set_output( output_config_1 ).

DATA(input_config_2) = function_double->create_input_configuration( )->set_importing_parameter( name = 'I_BUKRS' value = 'US01' ).
DATA(output_config_2) = function_double->create_output_configuration( )->set_exporting_parameter( name = 'E_T001'
value = VALUE t001( bukrs = 'US01' land1 = 'US' ) ).
function_double->configure_call( )->when( input_config_2 )->then_set_output( output_config_2 ).

ENDMETHOD.

Note - This feature is available from APAP 7.53 onwards. Please check your OP release if this is available.