Now accepting guest editorial from SAP, Oracle, Workday and ServiceNow practitioners.Pitch a guide
The Sign-Off Table

Lesson 3 of 5

Designing for testability

10 min

Listen to this lesson

0:00 / 9:03

Kenji's first test passed, then failed the next morning. Someone had changed the agreement for Great Lakes Fleet Services in CMD, and the deposit class reads the agreement straight from the custom table ZCMC_CORE_AGR. The test was really testing the contents of the development database. That is the most common reason ABAP Unit adoption stalls: the code under test reaches out to things the test cannot control. The fix is design, not more test code.

Find the dependencies

Start by listing what the method touches besides its own parameters and attributes. In ABAP the usual suspects are database reads and writes, CDS views, function modules and BAPIs, other classes created with NEW inside the method, authority checks, the system date and time, and number ranges. Each is a depended-on component, and each needs a way to be replaced in a test.

Logic you want to testDeposit per unit, rounding per line, minimum deposit, exemptions for returned cores
Seam: an interfaceZIF_CMC_AGREEMENT_READER with a method get_agreement( customer )
Production implementationReads ZCMC_CORE_AGR through a CDS view entity
Test doubleReturns a fixed agreement: 45.00 per unit, minimum 100.00
Dependency inversion: the logic depends on an interface, so the test can supply its own implementation.
The Sign-Off Table · thesignofftable.com/academy/sap-abp-205

Depend on interfaces, inject the implementation

The pattern SAP's Clean ABAP guide recommends is dependency inversion: the class depends on an interface, not on a concrete class or table, and the concrete implementation is handed to it from outside. Production code passes the real reader; the test passes a double. Kenji extracts an interface ZIF_CMC_AGREEMENT_READER, moves the database read into a class that implements it, and gives ZCL_CMC_CORE_DEPOSIT a reference to the interface instead of a SELECT.

Injection techniqueHow the double gets inWhen to use it
Constructor injectionPassed as an (optional) parameter of the constructor; production code passes nothing and gets the defaultThe default choice for required dependencies
Setter injectionPassed through a setter method after creationOptional dependencies or late configuration
Parameter injectionPassed as an optional importing parameter of the tested methodA single method needs a different collaborator
Back-door injectionThe test class is a LOCAL FRIEND and sets a private attribute directlyLegacy classes you cannot change much yet
Factory with injectorA factory returns implementations; a test-only injector class sets doubles in the factoryMany consumers create the same dependency

Injection techniques named in SAP's ABAP cheat sheets and ABAP Unit sessions.

Constructor injection with a default is the easiest to read: METHODS constructor IMPORTING reader TYPE REF TO zif_cmc_agreement_reader OPTIONAL. In the constructor, if no reader was passed, the class creates the production reader. Production callers do not change. If the constructor is private or the class has many consumers, a factory plus an injector class (declared FOR TESTING) keeps the test hook out of production code paths.

Constructor injection with a default: production callers change nothing, while the test hands in a stub through ZIF_CMC_AGREEMENT_READER.
The Sign-Off Table · thesignofftable.com/academy/sap-abp-205

Test seams: a temporary workaround

Sometimes you must test legacy code before you can restructure it. ABAP offers test seams: production code wraps a block in TEST-SEAM name ... END-TEST-SEAM, and a test class replaces it with TEST-INJECTION name ... END-TEST-INJECTION. Without an injection, the original code runs. Seams cannot be nested, and injections must sit in test classes in the test include, so in practice they work in class pools and function groups. Clean ABAP recommends seams only as a temporary workaround because they are invasive and tangle production and test code. Check SAP's list of language elements per ABAP language version before relying on them in code written for ABAP Cloud.

A test seam swaps a block of legacy code only when a test injects into it, and is meant to be removed once the class can be refactored.
The Sign-Off Table · thesignofftable.com/academy/sap-abp-205
  1. Can you change the class to depend on an interface?Yes → constructor injection with a default · No → next question
  2. Do many callers create the dependency?Yes → factory plus test injector · No → next question
  3. Is the dependency a table, CDS view or function module?Yes → a framework double (next lesson) may need no design change · No → next question
  4. Is this legacy code you must pin down before refactoring?Yes → test seam or LOCAL FRIENDS back door, with a ticket to remove it · No → refactor first
Choosing how to make a dependency replaceable.
The Sign-Off Table · thesignofftable.com/academy/sap-abp-205

Other things that make tests flaky

  • System date and time: read them through an injectable clock or pass them in, so a test about expiry dates does not break at month end.
  • Number ranges: let the test supply the number or use a double for the number provider.
  • Authority checks: isolate them, or use SAP's authority check test helper to run the test with a defined set of authorizations.
  • Shared static state: reset buffers and singletons in setup, or avoid them in the code under test.

What would you do?

Kenji's deposit class has a private method that reads ZCMC_CORE_AGR with a SELECT, called from the public method calculate. A teammate proposes making the private method public so the test can call it directly. What should Kenji do?

Key points
  • Tests stall when code reaches out to dependencies the test cannot control.
  • Depend on interfaces and inject implementations; constructor injection with a default is the usual choice.
  • Test public behavior; a need to test private methods hints at a missing class.
  • Test seams and LOCAL FRIENDS back doors are temporary tools for legacy code.

Check your understanding · not graded

Lesson check

  1. 1. What made Kenji's first deposit test fail the next morning?

  2. 2. Which injection technique passes the test double through an optional constructor parameter?

  3. 3. How does SAP's Clean ABAP guide position test seams?

  4. 4. A test checks an expiry rule and fails only at month end. What is the most likely design issue?