abapgit-agent 1.5.0 → 1.6.1

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