abapgit-agent 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,589 @@
1
+ # ABAP Unit Testable Code Guidelines
2
+
3
+ This document provides guidelines for creating ABAP OO classes/interfaces that can be easily unit tested with test doubles. These guidelines help AI coding tools understand how to design classes that are testable without requiring real dependencies.
4
+
5
+ ## The Problem
6
+
7
+ When ABAP classes are not designed for testability, unit tests cannot mock dependencies. This leads to:
8
+
9
+ - Tests that depend on real external systems (databases, APIs, file systems)
10
+ - Tests that fail in different environments
11
+ - Tests that are slow and unreliable
12
+ - Impossible to test error conditions
13
+
14
+ **Example of untestable code:**
15
+
16
+ ```abap
17
+ " BAD - Hardcoded dependency, cannot be replaced in tests
18
+ CLASS zcl_abgagt_command_pull DEFINITION PUBLIC.
19
+ METHOD execute.
20
+ lo_agent = NEW zcl_abgagt_agent( ). " Hardcoded!
21
+ ls_result = lo_agent->pull( ... ). " Calls real system
22
+ ENDMETHOD.
23
+ ENDCLASS.
24
+ ```
25
+
26
+ The unit test will instantiate the REAL `zcl_abgagt_agent` which tries to connect to abapGit and a real git repository, causing test failures.
27
+
28
+ ---
29
+
30
+ ## Core Principles
31
+
32
+ ### 1. Dependency Inversion (Dependency Injection)
33
+
34
+ **Pass dependencies through constructor instead of creating them internally.**
35
+
36
+ ```abap
37
+ " GOOD - Dependency injected via constructor
38
+ CLASS zcl_abgagt_command_pull DEFINITION PUBLIC.
39
+ PUBLIC SECTION.
40
+ INTERFACES zif_abgagt_command.
41
+
42
+ " Constructor injection
43
+ METHODS constructor
44
+ IMPORTING
45
+ io_agent TYPE REF TO zif_abgagt_agent.
46
+
47
+ PRIVATE SECTION.
48
+ DATA mo_agent TYPE REF TO zif_abgagt_agent.
49
+
50
+ ENDCLASS.
51
+
52
+ CLASS zcl_abgagt_command_pull IMPLEMENTATION.
53
+
54
+ METHOD constructor.
55
+ super->constructor( ).
56
+ mo_agent = io_agent.
57
+ ENDMETHOD.
58
+
59
+ METHOD execute.
60
+ " Use injected dependency
61
+ ls_result = mo_agent->pull( ... ).
62
+ ENDMETHOD.
63
+
64
+ ENDCLASS.
65
+ ```
66
+
67
+ **In production code:**
68
+ ```abap
69
+ DATA(lo_command) = NEW zcl_abgagt_command_pull(
70
+ io_agent = NEW zcl_abgagt_agent( ) ).
71
+ ```
72
+
73
+ **In test code:**
74
+ ```abap
75
+ " Create test double
76
+ CLASS ltd_mock_agent DEFINITION FOR TESTING.
77
+ PUBLIC SECTION.
78
+ INTERFACES zif_abgagt_agent PARTIALLY IMPLEMENTED.
79
+ ENDCLASS.
80
+
81
+ CLASS ltd_mock_agent IMPLEMENTATION.
82
+ METHOD zif_abgagt_agent~pull.
83
+ " Return test data instead of calling real system
84
+ rs_result-success = abap_true.
85
+ rs_result-message = 'Test success'.
86
+ ENDMETHOD.
87
+ ENDCLASS.
88
+
89
+ " Test uses test double
90
+ CLASS ltcl_test DEFINITION FOR TESTING.
91
+ METHOD test_execute.
92
+ DATA(lo_mock) = NEW ltd_mock_agent( ).
93
+ DATA(lo_cut) = NEW zcl_abgagt_command_pull( io_agent = lo_mock ).
94
+
95
+ DATA(lv_result) = lo_cut->execute( ... ).
96
+
97
+ " Assert expected results
98
+ ENDMETHOD.
99
+ ENDCLASS.
100
+ ```
101
+
102
+ ### 2. Always Use Interfaces for Dependencies
103
+
104
+ **Never depend on concrete classes - depend on interfaces.**
105
+
106
+ ```abap
107
+ " GOOD - Depend on interface
108
+ DATA mo_agent TYPE REF TO zif_abgagt_agent. " Interface!
109
+
110
+ " BAD - Depends on concrete class
111
+ DATA mo_agent TYPE REF TO zcl_abgagt_agent. " Concrete class!
112
+ ```
113
+
114
+ This allows you to replace the implementation with test doubles.
115
+
116
+ ### Define Types in Interface
117
+
118
+ Define types needed by the interface directly in the interface to keep it self-contained:
119
+
120
+ ```abap
121
+ INTERFACE zif_my_handler.
122
+ " Define types needed by the interface
123
+ TYPES: BEGIN OF ty_response,
124
+ success TYPE abap_bool,
125
+ message TYPE string,
126
+ END OF ty_response.
127
+
128
+ " Use the type in method signatures
129
+ METHODS process
130
+ IMPORTING iv_data TYPE string
131
+ RETURNING VALUE(rs_response) TYPE ty_response.
132
+ ENDINTERFACE.
133
+ ```
134
+
135
+ This makes it easier for test doubles to implement the interface without needing separate type definitions.
136
+
137
+ ### 3. Make Dependencies Injectable via Constructor
138
+
139
+ **Use constructor injection, not setter injection.**
140
+
141
+ ```abap
142
+ " GOOD - Constructor injection (required dependency)
143
+ METHODS constructor
144
+ IMPORTING
145
+ io_agent TYPE REF TO zif_abgagt_agent.
146
+
147
+ " BAD - Setter injection (optional, can be forgotten)
148
+ METHODS set_agent
149
+ IMPORTING
150
+ io_agent TYPE REF TO zif_abgagt_agent.
151
+ ```
152
+
153
+ Constructor injection:
154
+ - Makes dependency explicit
155
+ - Ensures object is always in valid state
156
+ - Cannot forget to inject
157
+
158
+ ### 4. Avoid Static Calls
159
+
160
+ **Static method calls cannot be mocked/test-doubled.**
161
+
162
+ ```abap
163
+ " BAD - Static call cannot be replaced
164
+ DATA(li_repo) = zcl_abapgit_repo_srv=>get_instance( )->get_repo_from_url( ... ).
165
+
166
+ " GOOD - Instance method via injected dependency
167
+ DATA(li_repo) = mo_repo_srv->get_repo_from_url( ... ).
168
+ ```
169
+
170
+ If you must call static methods, wrap them in an instance method of an injected class.
171
+
172
+ ### 5. Keep Constructor Simple
173
+
174
+ **Constructor should only assign dependencies, not perform complex logic.**
175
+
176
+ ```abap
177
+ " GOOD - Simple constructor
178
+ METHOD constructor.
179
+ mo_agent = io_agent.
180
+ mo_logger = io_logger.
181
+ ENDMETHOD.
182
+
183
+ " BAD - Complex logic in constructor
184
+ METHOD constructor.
185
+ mo_agent = io_agent.
186
+ " Don't do this here:
187
+ mo_agent->connect( ). " Network call in constructor!
188
+ DATA(ls_config) = read_config( ). " File I/O in constructor!
189
+ ENDMETHOD.
190
+ ```
191
+
192
+ ---
193
+
194
+ ## Injection Techniques
195
+
196
+ ### Constructor Injection (Recommended)
197
+
198
+ ```abap
199
+ CLASS zcl_my_class DEFINITION PUBLIC.
200
+ PUBLIC SECTION.
201
+ METHODS constructor
202
+ IMPORTING
203
+ io_dependency TYPE REF TO zif_my_interface.
204
+ PRIVATE SECTION.
205
+ DATA mo_dependency TYPE REF TO zif_my_interface.
206
+ ENDCLASS.
207
+ ```
208
+
209
+ ### Back Door Injection (for existing code)
210
+
211
+ When you cannot modify the constructor, use friendship:
212
+
213
+ ```abap
214
+ " In test class
215
+ CLASS zcl_my_class DEFINITION LOCAL FRIENDS ltcl_test.
216
+
217
+ CLASS ltcl_test IMPLEMENTATION.
218
+ METHOD test_with_mock.
219
+ " Directly set private attribute via friendship
220
+ CREATE OBJECT mo_cut.
221
+ mo_cut->mo_dependency = lo_mock. " Access private attribute
222
+ ENDMETHOD.
223
+ ENDCLASS.
224
+ ```
225
+
226
+ ### Test Seams (last resort)
227
+
228
+ For legacy code that cannot be refactored:
229
+
230
+ ```abap
231
+ " In production code
232
+ METHOD get_data.
233
+ TEST-SEAM db_select.
234
+ SELECT * FROM dbtab INTO TABLE @DATA(lt_data).
235
+ END-TEST-SEAM.
236
+ ENDMETHOD.
237
+
238
+ " In test class
239
+ METHOD test_get_data.
240
+ TEST-INJECTION db_select.
241
+ lt_data = VALUE #( ( id = '1' ) ( id = '2' ) ).
242
+ END-TEST-INJECTION.
243
+
244
+ DATA(lt_result) = mo_cut->get_data( ).
245
+ ENDMETHOD.
246
+ ```
247
+
248
+ ---
249
+
250
+ ## Test Double Patterns
251
+
252
+ ### Manual Test Double (Local Class)
253
+
254
+ ```abap
255
+ " Create test double class
256
+ CLASS ltd_mock_reader DEFINITION FOR TESTING.
257
+ PUBLIC SECTION.
258
+ INTERFACES zif_data_reader PARTIALLY IMPLEMENTED.
259
+ METHODS set_result_data
260
+ IMPORTING it_data TYPE ANY TABLE.
261
+ PRIVATE SECTION.
262
+ DATA mt_data TYPE ANY TABLE.
263
+ ENDCLASS.
264
+
265
+ CLASS ltd_mock_reader IMPLEMENTATION.
266
+ METHOD set_result_data.
267
+ mt_data = it_data.
268
+ ENDMETHOD.
269
+
270
+ METHOD zif_data_reader~read_all.
271
+ rt_data = mt_data.
272
+ ENDMETHOD.
273
+ ENDCLASS.
274
+ ```
275
+
276
+ ### Using ABAP Test Double Framework
277
+
278
+ ```abap
279
+ " Step 1: Declare with correct interface type, then assign
280
+ DATA lo_mock TYPE REF TO zif_my_interface.
281
+ lo_mock ?= cl_abap_testdouble=>create( 'ZIF_MY_INTERFACE' ).
282
+
283
+ " Step 2: Configure return value - use returning() not IMPORTING
284
+ cl_abap_testdouble=>configure_call( lo_mock )->returning( lo_mock_result ).
285
+
286
+ " Step 3: Call method to register configuration (MUST use same params in test)
287
+ lo_mock->my_method(
288
+ EXPORTING
289
+ iv_param1 = 'value1'
290
+ iv_param2 = 'value2' ).
291
+
292
+ " Step 4: In test, call with SAME parameters as registered above
293
+ DATA(ls_result) = lo_mock->my_method(
294
+ EXPORTING
295
+ iv_param1 = 'value1'
296
+ iv_param2 = 'value2' ).
297
+
298
+ " To raise exception:
299
+ DATA(lx_error) = NEW zcx_my_exception( ).
300
+ cl_abap_testdouble=>configure_call( lo_mock )->raise_exception( lx_error ).
301
+ lo_mock->my_method( ... ).
302
+ ```
303
+
304
+ **Important Notes:**
305
+ - Parameters in configure_call registration MUST match parameters in test execution
306
+ - Always declare variable with interface type first: `DATA lo_mock TYPE REF TO zif_xxx`
307
+ - Use `returning(value = ...)` not `IMPORTING`
308
+ - Call method after configure_call to register the configuration
309
+
310
+ ### Mocking EXPORT Parameters
311
+
312
+ Some methods use EXPORT parameters instead of returning values. Use `set_parameter`:
313
+
314
+ ```abap
315
+ " Mock EXPORT parameter EI_REPO
316
+ cl_abap_testdouble=>configure_call( lo_repo_srv )->set_parameter(
317
+ EXPORTING
318
+ name = 'EI_REPO'
319
+ value = lo_repo_double ).
320
+
321
+ " Register the method call
322
+ lo_repo_srv->get_repo_from_url(
323
+ EXPORTING iv_url = 'https://github.com/test/repo.git' ).
324
+ ```
325
+
326
+ ### Mocking Inherited Methods
327
+
328
+ When an interface extends another interface, use the parent interface prefix:
329
+
330
+ ```abap
331
+ " zif_abapgit_repo_online extends zif_abapgit_repo
332
+ " Call inherited method with prefix
333
+ lo_repo->zif_abapgit_repo~get_package( ).
334
+ lo_repo->zif_abapgit_repo~refresh( ).
335
+ lo_repo->zif_abapgit_repo~get_files_local( ).
336
+ ```
337
+
338
+ ### Mocking Methods with No Parameters
339
+
340
+ When source code calls a method with no parameters:
341
+
342
+ ```abap
343
+ " Configure returning (no method name)
344
+ cl_abap_testdouble=>configure_call( lo_mock )->returning( lt_data ).
345
+
346
+ " Register with no parameters (matches source code)
347
+ lo_mock->get_files_local( ).
348
+ ```
349
+
350
+ ### Common Mistakes
351
+
352
+ | Mistake | Correction |
353
+ |---------|------------|
354
+ | Using `IMPORTING` in configure_call | Use `returning()` or `set_parameter()` |
355
+ | Calling method inside configure_call | Call method separately after configure_call |
356
+ | Wrong parameter count | Match exactly what source code calls |
357
+ | Forgot to mock a method | Mock ALL methods the code under test calls |
358
+ | Interface prefix not used | Use `zif_parent~method()` for inherited methods |
359
+ | Didn't check source code first | ALWAYS read source code to see how method is called |
360
+ | Cannot add RAISING to interface method | Use TRY..CATCH to handle exceptions in implementation |
361
+
362
+ ### Handling Exceptions in Interface Implementation
363
+
364
+ When implementing an interface method that calls other methods raising exceptions:
365
+
366
+ - **DO NOT** add RAISING to the interface method - you cannot change the interface
367
+ - **USE** TRY..CATCH to catch and handle exceptions within the implementation
368
+
369
+ ```abap
370
+ " Interface method does NOT declare RAISING
371
+ METHOD zif_abgagt_command~execute.
372
+
373
+ " Method being called can raise exception
374
+ TRY.
375
+ get_user( )->set_repo_git_user_name( ... ).
376
+ CATCH zcx_abapgit_exception INTO DATA(lx_error).
377
+ rv_result = '{"error":"' && lx_error->get_text( ) && '"}'.
378
+ RETURN.
379
+ ENDTRY.
380
+
381
+ ENDMETHOD.
382
+ ```
383
+
384
+ ### Important: Read Source Code First
385
+
386
+ **ALWAYS check the source code to see HOW a method is called before writing tests:**
387
+
388
+ 1. Check what parameters are passed (none, some, or all)
389
+ 2. Check if optional parameters are used
390
+ 3. Check if parameters have default values
391
+ 4. Check for type casts (e.g., `li_repo_online ?= li_repo`)
392
+
393
+ ```abap
394
+ " Source code line 122:
395
+ lt_files = li_repo->get_files_local( ).
396
+
397
+ " Test MUST match - no parameters!
398
+ cl_abap_testdouble=>configure_call( lo_repo )->returning( lt_empty_files ).
399
+ lo_repo->get_files_local( ). " No parameters!
400
+ ```
401
+
402
+ If the source code calls `get_files_local( )` with no parameters, your test registration must also have no parameters. Even if the method signature has optional parameters, if the source doesn't pass them, your mock registration must not pass them either.
403
+
404
+ ---
405
+
406
+ ## Guidelines for AI Coding Tools
407
+
408
+ When creating new ABAP classes, ALWAYS follow these rules:
409
+
410
+ ### DO:
411
+
412
+ 1. **Use interfaces for ALL external dependencies**
413
+ - Database access → interface
414
+ - External API calls → interface
415
+ - File I/O → interface
416
+ - Other services → interface
417
+
418
+ 2. **Pass dependencies via constructor**
419
+ ```abap
420
+ METHODS constructor
421
+ IMPORTING
422
+ io_agent TYPE REF TO zif_abgagt_agent
423
+ io_logger TYPE REF TO zif_logger.
424
+ ```
425
+
426
+ 3. **Define interfaces for all service classes**
427
+ ```abap
428
+ " Instead of using class directly
429
+ DATA mo_agent TYPE REF TO zcl_abgagt_agent. " BAD
430
+
431
+ " Use interface
432
+ DATA mo_agent TYPE REF TO zif_abgagt_agent. " GOOD
433
+ ```
434
+
435
+ 4. **Keep classes FINAL if they don't need mocking**
436
+ - If a class has no dependencies and doesn't need test doubles, make it FINAL
437
+ - If a class needs to be mocked in tests, don't make it FINAL
438
+
439
+ 5. **Use dependency injection in command classes**
440
+ ```abap
441
+ " Good pattern for command classes
442
+ CLASS zcl_abgagt_command_pull DEFINITION PUBLIC.
443
+ PUBLIC SECTION.
444
+ INTERFACES zif_abgagt_command.
445
+ METHODS constructor
446
+ IMPORTING io_agent TYPE REF TO zif_abgagt_agent.
447
+ ENDCLASS.
448
+ ```
449
+
450
+ ### DON'T:
451
+
452
+ 1. **Never create dependencies inside methods**
453
+ ```abap
454
+ " BAD
455
+ METHOD execute.
456
+ lo_agent = NEW zcl_abgagt_agent( ). " Hardcoded!
457
+ ENDMETHOD.
458
+
459
+ " GOOD
460
+ METHOD execute.
461
+ ls_result = mo_agent->pull( ... ). " Use injected
462
+ ENDMETHOD.
463
+ ```
464
+
465
+ 2. **Don't use static method calls for testable code**
466
+ ```abap
467
+ " BAD
468
+ DATA(lo_srv) = zcl_some_srv=>get_instance( ).
469
+
470
+ " GOOD - inject the service
471
+ DATA(lo_srv) = mo_service_provider.
472
+ ```
473
+
474
+ 3. **Don't make classes FINAL if they need test doubles**
475
+ - If you need to mock a class in tests, don't declare it FINAL
476
+
477
+ ---
478
+
479
+ ## Example: Refactoring for Testability
480
+
481
+ ### Before (Not Testable)
482
+
483
+ ```abap
484
+ CLASS zcl_abgagt_command_pull DEFINITION PUBLIC.
485
+ METHOD execute.
486
+ DATA lo_agent TYPE REF TO zcl_abgagt_agent.
487
+ lo_agent = NEW zcl_abgagt_agent( ). " Hardcoded!
488
+
489
+ ls_result = lo_agent->pull(
490
+ iv_url = ls_params-url
491
+ iv_branch = ls_params-branch ).
492
+ ENDMETHOD.
493
+ ENDCLASS.
494
+ ```
495
+
496
+ ### After (Testable)
497
+
498
+ ```abap
499
+ " Interface for agent
500
+ INTERFACE zif_abgagt_agent PUBLIC.
501
+ METHODS pull ... RAISING zcx_abapgit_exception.
502
+ ENDINTERFACE.
503
+
504
+ " Command class with constructor injection
505
+ CLASS zcl_abgagt_command_pull DEFINITION PUBLIC.
506
+ PUBLIC SECTION.
507
+ INTERFACES zif_abgagt_command.
508
+
509
+ METHODS constructor
510
+ IMPORTING
511
+ io_agent TYPE REF TO zif_abgagt_agent OPTIONAL. " Optional for backward compat
512
+
513
+ PRIVATE SECTION.
514
+ DATA mo_agent TYPE REF TO zif_abgagt_agent.
515
+
516
+ METHODS get_agent
517
+ RETURNING VALUE(ro_agent) TYPE REF TO zif_abgagt_agent.
518
+ ENDCLASS.
519
+
520
+ CLASS zcl_abgagt_command_pull IMPLEMENTATION.
521
+
522
+ METHOD constructor.
523
+ mo_agent = io_agent.
524
+ ENDMETHOD.
525
+
526
+ METHOD get_agent.
527
+ " Lazy creation if not injected (for production)
528
+ IF mo_agent IS NOT BOUND.
529
+ mo_agent = NEW zcl_abgagt_agent( ).
530
+ ENDIF.
531
+ ro_agent = mo_agent.
532
+ ENDMETHOD.
533
+
534
+ METHOD execute.
535
+ DATA(lo_agent) = get_agent( ).
536
+ ls_result = lo_agent->pull( ... ).
537
+ ENDMETHOD.
538
+
539
+ ENDCLASS.
540
+ ```
541
+
542
+ **Production usage:**
543
+ ```abap
544
+ DATA(lo_command) = NEW zcl_abgagt_command_pull(
545
+ io_agent = NEW zcl_abgagt_agent( ) ).
546
+ ```
547
+
548
+ **Test usage:**
549
+ ```abap
550
+ CLASS ltd_mock_agent DEFINITION FOR TESTING.
551
+ PUBLIC SECTION.
552
+ INTERFACES zif_abgagt_agent PARTIALLY IMPLEMENTED.
553
+ ENDCLASS.
554
+
555
+ CLASS ltd_mock_agent IMPLEMENTATION.
556
+ METHOD zif_abgagt_agent~pull.
557
+ rs_result-success = abap_true.
558
+ rs_result-message = 'Mocked success'.
559
+ ENDMETHOD.
560
+ ENDCLASS.
561
+
562
+ CLASS ltcl_test DEFINITION FOR TESTING.
563
+ METHOD test_pull_success.
564
+ DATA(lo_mock) = NEW ltd_mock_agent( ).
565
+ DATA(lo_cut) = NEW zcl_abgagt_command_pull( io_agent = lo_mock ).
566
+
567
+ DATA(lv_result) = lo_cut->execute( ... ).
568
+
569
+ " Assert mocked behavior
570
+ ENDMETHOD.
571
+ ENDCLASS.
572
+ ```
573
+
574
+ ---
575
+
576
+ ## Key Takeaways
577
+
578
+ 1. **Always use interfaces** for dependencies
579
+ 2. **Use constructor injection** to pass dependencies
580
+ 3. **Never hardcode `NEW` for dependencies** - pass them in
581
+ 4. **Avoid static calls** - use instance methods with injected dependencies
582
+ 5. **Keep constructors simple** - only assign dependencies
583
+
584
+ Following these guidelines ensures that:
585
+ - Unit tests can mock all dependencies
586
+ - Tests run fast without external systems
587
+ - Tests are reliable and repeatable
588
+ - Error conditions can be tested easily
589
+ - Code is modular and loosely coupled