19 min read

Software that processes billions of dollars in banking transactions, insurance claims, and government benefits every day cannot afford defects. A single misplaced decimal point in a batch interest calculation can cascade through millions of accounts...

Chapter 40: Testing, Quality Assurance, and Deployment

Part VIII - Modern COBOL and System Evolution

Software that processes billions of dollars in banking transactions, insurance claims, and government benefits every day cannot afford defects. A single misplaced decimal point in a batch interest calculation can cascade through millions of accounts overnight, creating errors that take weeks to unwind and may trigger regulatory action. This chapter provides a comprehensive treatment of testing, quality assurance, and deployment practices for COBOL systems, from unit testing individual paragraphs to managing production deployments on z/OS.

The testing discipline for COBOL systems has matured significantly in recent years. Modern frameworks like COBOL-Check bring unit testing practices familiar from Java and Python into the COBOL ecosystem. CI/CD pipelines that were once exclusive to distributed systems are now standard practice on the mainframe. Yet the fundamentals remain unchanged: every line of code must be verified, every deployment must be controlled, and every production incident must be resolved quickly and completely.


40.1 Testing Fundamentals for COBOL

The Testing Pyramid

COBOL testing follows the same layered approach used in all software engineering, but with important differences in emphasis and tooling.

Unit testing verifies that individual paragraphs, sections, or programs produce correct results for specific inputs. In COBOL, the unit of test is typically a paragraph or a called subprogram, since COBOL does not have functions or methods in the traditional sense (until OO COBOL, which is rarely used for unit decomposition). Unit tests should be fast, isolated, and numerous. They form the base of the testing pyramid.

Integration testing verifies that programs work correctly together. In a COBOL system, integration testing typically exercises a chain of programs: a CICS transaction that calls multiple subprograms, or a batch job stream where the output of one program becomes the input to the next. Integration tests verify data flows, copybook compatibility, and inter-program communication.

System testing verifies end-to-end business processes. A system test for a banking application might simulate a complete day of operations: opening accounts, processing deposits and withdrawals, running end-of-day batch processing, and verifying that all account balances, audit trails, and reports are correct. System tests are the slowest to run and the most expensive to maintain, but they provide the highest confidence.

Acceptance testing verifies that the system meets business requirements. In regulated industries like banking and insurance, acceptance testing often includes regulatory compliance verification. Acceptance tests are typically written and reviewed by business analysts, not programmers.

COBOL-Specific Testing Challenges

Several characteristics of COBOL systems make testing more challenging than in modern distributed applications:

Batch orientation. Many COBOL programs process files sequentially from beginning to end. Testing a specific code path may require constructing an input file with records designed to trigger that path, which may require specific record ordering or data combinations that only occur deep in the processing sequence.

Shared state through WORKING-STORAGE. COBOL programs use WORKING-STORAGE as a shared mutable state space. A paragraph that sets a flag in WORKING-STORAGE affects all subsequent paragraphs that check that flag. Testing one paragraph in isolation requires initializing all relevant WORKING-STORAGE fields to known values.

File I/O coupling. Most COBOL programs are tightly coupled to their input and output files. Testing a calculation paragraph requires providing file records, even if the calculation itself does not depend on file I/O. This coupling makes true unit testing difficult without a framework that can intercept file operations.

CICS coupling. Online COBOL programs contain EXEC CICS commands that only work within a CICS environment. Testing these programs outside of CICS requires either a CICS test region or a mocking framework that simulates CICS services.

DB2 coupling. Programs containing embedded SQL can only be tested with access to a DB2 database, or with a framework that intercepts and mocks SQL calls.


40.2 COBOL Unit Testing Frameworks

COBOL-Check

COBOL-Check is an open-source unit testing framework specifically designed for COBOL. It takes a unique approach: rather than running tests in a separate test runner, COBOL-Check inserts test code directly into the COBOL source at the points where the paragraphs under test are defined. The merged program is then compiled and executed normally, with test results written to a report.

COBOL-Check's approach eliminates the need for mocking frameworks or test harnesses for basic unit testing. Tests execute within the same WORKING-STORAGE context as the production code, ensuring complete fidelity.

Setting Up COBOL-Check

COBOL-Check requires a test suite file for each program being tested. The test suite uses a human-readable syntax that maps directly to COBOL concepts.

Here is a complete example testing a banking transaction processing program:

The program under test (BANKTRAN.cbl):

       IDENTIFICATION DIVISION.
       PROGRAM-ID. BANKTRAN.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-TRANSACTION.
           05  WS-TRAN-TYPE         PIC X(1).
               88  TRAN-DEPOSIT      VALUE 'D'.
               88  TRAN-WITHDRAWAL   VALUE 'W'.
               88  TRAN-TRANSFER     VALUE 'T'.
               88  TRAN-INQUIRY      VALUE 'I'.
           05  WS-TRAN-AMOUNT       PIC S9(9)V99 COMP-3.
           05  WS-TRAN-ACCT-FROM    PIC X(10).
           05  WS-TRAN-ACCT-TO      PIC X(10).

       01  WS-ACCOUNT.
           05  WS-ACCT-NUMBER       PIC X(10).
           05  WS-ACCT-BALANCE      PIC S9(11)V99 COMP-3.
           05  WS-ACCT-STATUS       PIC X(1).
               88  ACCT-ACTIVE       VALUE 'A'.
               88  ACCT-FROZEN       VALUE 'F'.
               88  ACCT-CLOSED       VALUE 'C'.
           05  WS-ACCT-TYPE         PIC X(2).
               88  ACCT-CHECKING     VALUE 'CH'.
               88  ACCT-SAVINGS      VALUE 'SV'.
               88  ACCT-MONEY-MKT    VALUE 'MM'.
           05  WS-ACCT-DAILY-WD     PIC S9(9)V99 COMP-3.
           05  WS-ACCT-WD-LIMIT     PIC S9(9)V99 COMP-3.

       01  WS-RESULT.
           05  WS-RESULT-CODE       PIC X(2).
               88  RESULT-SUCCESS    VALUE '00'.
               88  RESULT-INSUFF     VALUE '01'.
               88  RESULT-OVERLIMIT  VALUE '02'.
               88  RESULT-FROZEN     VALUE '03'.
               88  RESULT-CLOSED     VALUE '04'.
               88  RESULT-INVALID    VALUE '99'.
           05  WS-RESULT-MSG        PIC X(50).
           05  WS-NEW-BALANCE       PIC S9(11)V99 COMP-3.

       01  WS-OVERDRAFT-FEE        PIC S9(5)V99 COMP-3
                                    VALUE 35.00.
       01  WS-MIN-SAVINGS-BAL      PIC S9(9)V99 COMP-3
                                    VALUE 100.00.

       PROCEDURE DIVISION.
       0000-MAIN-PROCESS.
           PERFORM 1000-VALIDATE-TRANSACTION
           IF RESULT-SUCCESS
               PERFORM 2000-PROCESS-TRANSACTION
           END-IF
           .

       1000-VALIDATE-TRANSACTION.
           MOVE '00' TO WS-RESULT-CODE
           MOVE SPACES TO WS-RESULT-MSG

           IF ACCT-CLOSED
               MOVE '04' TO WS-RESULT-CODE
               MOVE 'ACCOUNT IS CLOSED' TO WS-RESULT-MSG
               EXIT PARAGRAPH
           END-IF

           IF ACCT-FROZEN
               MOVE '03' TO WS-RESULT-CODE
               MOVE 'ACCOUNT IS FROZEN' TO WS-RESULT-MSG
               EXIT PARAGRAPH
           END-IF

           IF WS-TRAN-AMOUNT NOT > ZERO
               AND NOT TRAN-INQUIRY
               MOVE '99' TO WS-RESULT-CODE
               MOVE 'INVALID TRANSACTION AMOUNT' TO WS-RESULT-MSG
               EXIT PARAGRAPH
           END-IF
           .

       2000-PROCESS-TRANSACTION.
           EVALUATE TRUE
               WHEN TRAN-DEPOSIT
                   PERFORM 2100-PROCESS-DEPOSIT
               WHEN TRAN-WITHDRAWAL
                   PERFORM 2200-PROCESS-WITHDRAWAL
               WHEN TRAN-TRANSFER
                   PERFORM 2300-PROCESS-TRANSFER
               WHEN TRAN-INQUIRY
                   PERFORM 2400-PROCESS-INQUIRY
               WHEN OTHER
                   MOVE '99' TO WS-RESULT-CODE
                   MOVE 'UNKNOWN TRANSACTION TYPE'
                       TO WS-RESULT-MSG
           END-EVALUATE
           .

       2100-PROCESS-DEPOSIT.
           ADD WS-TRAN-AMOUNT TO WS-ACCT-BALANCE
           MOVE WS-ACCT-BALANCE TO WS-NEW-BALANCE
           MOVE '00' TO WS-RESULT-CODE
           MOVE 'DEPOSIT SUCCESSFUL' TO WS-RESULT-MSG
           .

       2200-PROCESS-WITHDRAWAL.
      *    Check daily withdrawal limit
           ADD WS-TRAN-AMOUNT TO WS-ACCT-DAILY-WD
           IF WS-ACCT-DAILY-WD > WS-ACCT-WD-LIMIT
               MOVE '02' TO WS-RESULT-CODE
               MOVE 'DAILY WITHDRAWAL LIMIT EXCEEDED'
                   TO WS-RESULT-MSG
               SUBTRACT WS-TRAN-AMOUNT FROM WS-ACCT-DAILY-WD
               EXIT PARAGRAPH
           END-IF

      *    Check for savings minimum balance
           IF ACCT-SAVINGS
               COMPUTE WS-NEW-BALANCE =
                   WS-ACCT-BALANCE - WS-TRAN-AMOUNT
               IF WS-NEW-BALANCE < WS-MIN-SAVINGS-BAL
                   MOVE '01' TO WS-RESULT-CODE
                   MOVE 'BELOW MINIMUM SAVINGS BALANCE'
                       TO WS-RESULT-MSG
                   SUBTRACT WS-TRAN-AMOUNT FROM WS-ACCT-DAILY-WD
                   EXIT PARAGRAPH
               END-IF
           END-IF

      *    Process the withdrawal
           SUBTRACT WS-TRAN-AMOUNT FROM WS-ACCT-BALANCE

      *    Check for overdraft (checking accounts only)
           IF ACCT-CHECKING AND WS-ACCT-BALANCE < ZERO
               SUBTRACT WS-OVERDRAFT-FEE FROM WS-ACCT-BALANCE
               MOVE WS-ACCT-BALANCE TO WS-NEW-BALANCE
               MOVE '00' TO WS-RESULT-CODE
               STRING 'WITHDRAWAL OK - OVERDRAFT FEE '
                      '$35.00 APPLIED'
                   DELIMITED SIZE INTO WS-RESULT-MSG
               END-STRING
           ELSE
               MOVE WS-ACCT-BALANCE TO WS-NEW-BALANCE
               MOVE '00' TO WS-RESULT-CODE
               MOVE 'WITHDRAWAL SUCCESSFUL' TO WS-RESULT-MSG
           END-IF
           .

       2300-PROCESS-TRANSFER.
      *    Transfer is withdrawal from one account plus
      *    deposit to another - simplified here
           PERFORM 2200-PROCESS-WITHDRAWAL
           IF RESULT-SUCCESS
               MOVE 'TRANSFER SUCCESSFUL' TO WS-RESULT-MSG
           END-IF
           .

       2400-PROCESS-INQUIRY.
           MOVE WS-ACCT-BALANCE TO WS-NEW-BALANCE
           MOVE '00' TO WS-RESULT-CODE
           MOVE 'INQUIRY SUCCESSFUL' TO WS-RESULT-MSG
           .

The COBOL-Check test suite (BANKTRAN.cut):

       TESTSUITE 'BANKING TRANSACTION PROCESSING TESTS'

      *============================================================*
      * Tests for 1000-VALIDATE-TRANSACTION                        *
      *============================================================*
       TESTCASE 'Closed account rejects all transactions'
           MOVE 'C' TO WS-ACCT-STATUS
           MOVE 'D' TO WS-TRAN-TYPE
           MOVE 500.00 TO WS-TRAN-AMOUNT
           PERFORM 1000-VALIDATE-TRANSACTION
           EXPECT WS-RESULT-CODE TO BE '04'
           EXPECT WS-RESULT-MSG TO BE 'ACCOUNT IS CLOSED'

       TESTCASE 'Frozen account rejects all transactions'
           MOVE 'F' TO WS-ACCT-STATUS
           MOVE 'W' TO WS-TRAN-TYPE
           MOVE 200.00 TO WS-TRAN-AMOUNT
           PERFORM 1000-VALIDATE-TRANSACTION
           EXPECT WS-RESULT-CODE TO BE '03'
           EXPECT WS-RESULT-MSG TO BE 'ACCOUNT IS FROZEN'

       TESTCASE 'Zero amount rejects non-inquiry transactions'
           MOVE 'A' TO WS-ACCT-STATUS
           MOVE 'D' TO WS-TRAN-TYPE
           MOVE ZERO TO WS-TRAN-AMOUNT
           PERFORM 1000-VALIDATE-TRANSACTION
           EXPECT WS-RESULT-CODE TO BE '99'

       TESTCASE 'Zero amount allowed for inquiry'
           MOVE 'A' TO WS-ACCT-STATUS
           MOVE 'I' TO WS-TRAN-TYPE
           MOVE ZERO TO WS-TRAN-AMOUNT
           PERFORM 1000-VALIDATE-TRANSACTION
           EXPECT WS-RESULT-CODE TO BE '00'

       TESTCASE 'Active account with valid amount passes'
           MOVE 'A' TO WS-ACCT-STATUS
           MOVE 'D' TO WS-TRAN-TYPE
           MOVE 1000.00 TO WS-TRAN-AMOUNT
           PERFORM 1000-VALIDATE-TRANSACTION
           EXPECT WS-RESULT-CODE TO BE '00'

      *============================================================*
      * Tests for 2100-PROCESS-DEPOSIT                             *
      *============================================================*
       TESTCASE 'Deposit adds to account balance'
           MOVE 5000.00 TO WS-ACCT-BALANCE
           MOVE 1500.00 TO WS-TRAN-AMOUNT
           PERFORM 2100-PROCESS-DEPOSIT
           EXPECT WS-ACCT-BALANCE TO BE 6500.00
           EXPECT WS-NEW-BALANCE TO BE 6500.00
           EXPECT WS-RESULT-CODE TO BE '00'

       TESTCASE 'Deposit to zero balance account'
           MOVE ZERO TO WS-ACCT-BALANCE
           MOVE 250.00 TO WS-TRAN-AMOUNT
           PERFORM 2100-PROCESS-DEPOSIT
           EXPECT WS-ACCT-BALANCE TO BE 250.00
           EXPECT WS-RESULT-MSG TO BE 'DEPOSIT SUCCESSFUL'

       TESTCASE 'Large deposit handles correctly'
           MOVE 10000.00 TO WS-ACCT-BALANCE
           MOVE 99999999.99 TO WS-TRAN-AMOUNT
           PERFORM 2100-PROCESS-DEPOSIT
           EXPECT WS-ACCT-BALANCE TO BE 100009999.99

      *============================================================*
      * Tests for 2200-PROCESS-WITHDRAWAL                          *
      *============================================================*
       TESTCASE 'Simple withdrawal from checking account'
           MOVE 'CH' TO WS-ACCT-TYPE
           MOVE 5000.00 TO WS-ACCT-BALANCE
           MOVE 1000.00 TO WS-TRAN-AMOUNT
           MOVE ZERO TO WS-ACCT-DAILY-WD
           MOVE 5000.00 TO WS-ACCT-WD-LIMIT
           PERFORM 2200-PROCESS-WITHDRAWAL
           EXPECT WS-ACCT-BALANCE TO BE 4000.00
           EXPECT WS-RESULT-CODE TO BE '00'
           EXPECT WS-RESULT-MSG TO BE 'WITHDRAWAL SUCCESSFUL'

       TESTCASE 'Withdrawal exceeding daily limit is rejected'
           MOVE 'CH' TO WS-ACCT-TYPE
           MOVE 50000.00 TO WS-ACCT-BALANCE
           MOVE 3000.00 TO WS-TRAN-AMOUNT
           MOVE 2500.00 TO WS-ACCT-DAILY-WD
           MOVE 5000.00 TO WS-ACCT-WD-LIMIT
           PERFORM 2200-PROCESS-WITHDRAWAL
           EXPECT WS-RESULT-CODE TO BE '02'
           EXPECT WS-ACCT-BALANCE TO BE 50000.00
           EXPECT WS-ACCT-DAILY-WD TO BE 2500.00

       TESTCASE 'Overdraft applies fee on checking account'
           MOVE 'CH' TO WS-ACCT-TYPE
           MOVE 100.00 TO WS-ACCT-BALANCE
           MOVE 200.00 TO WS-TRAN-AMOUNT
           MOVE ZERO TO WS-ACCT-DAILY-WD
           MOVE 5000.00 TO WS-ACCT-WD-LIMIT
           PERFORM 2200-PROCESS-WITHDRAWAL
           EXPECT WS-ACCT-BALANCE TO BE -135.00
           EXPECT WS-RESULT-CODE TO BE '00'

       TESTCASE 'Savings withdrawal below minimum is rejected'
           MOVE 'SV' TO WS-ACCT-TYPE
           MOVE 500.00 TO WS-ACCT-BALANCE
           MOVE 450.00 TO WS-TRAN-AMOUNT
           MOVE ZERO TO WS-ACCT-DAILY-WD
           MOVE 5000.00 TO WS-ACCT-WD-LIMIT
           PERFORM 2200-PROCESS-WITHDRAWAL
           EXPECT WS-RESULT-CODE TO BE '01'
           EXPECT WS-ACCT-BALANCE TO BE 500.00

       TESTCASE 'Savings withdrawal leaving exactly minimum'
           MOVE 'SV' TO WS-ACCT-TYPE
           MOVE 500.00 TO WS-ACCT-BALANCE
           MOVE 400.00 TO WS-TRAN-AMOUNT
           MOVE ZERO TO WS-ACCT-DAILY-WD
           MOVE 5000.00 TO WS-ACCT-WD-LIMIT
           PERFORM 2200-PROCESS-WITHDRAWAL
           EXPECT WS-RESULT-CODE TO BE '00'
           EXPECT WS-ACCT-BALANCE TO BE 100.00

      *============================================================*
      * Tests for 2400-PROCESS-INQUIRY                             *
      *============================================================*
       TESTCASE 'Inquiry returns current balance'
           MOVE 12345.67 TO WS-ACCT-BALANCE
           PERFORM 2400-PROCESS-INQUIRY
           EXPECT WS-NEW-BALANCE TO BE 12345.67
           EXPECT WS-RESULT-CODE TO BE '00'
           EXPECT WS-RESULT-MSG TO BE 'INQUIRY SUCCESSFUL'

Running COBOL-Check

COBOL-Check processes the test suite through these steps:

  1. Reads the COBOL source program and the test suite file.
  2. Inserts test code at the appropriate locations within each tested paragraph.
  3. Generates a merged COBOL program that includes both production and test logic.
  4. Compiles and executes the merged program.
  5. Reports test results, including pass/fail status, expected versus actual values for failed tests, and summary counts.

The output from running the above test suite would look like:

TESTSUITE: BANKING TRANSACTION PROCESSING TESTS

  PASS: Closed account rejects all transactions
  PASS: Frozen account rejects all transactions
  PASS: Zero amount rejects non-inquiry transactions
  PASS: Zero amount allowed for inquiry
  PASS: Active account with valid amount passes
  PASS: Deposit adds to account balance
  PASS: Deposit to zero balance account
  PASS: Large deposit handles correctly
  PASS: Simple withdrawal from checking account
  PASS: Withdrawal exceeding daily limit is rejected
  PASS: Overdraft applies fee on checking account
  PASS: Savings withdrawal below minimum is rejected
  PASS: Savings withdrawal leaving exactly minimum
  PASS: Inquiry returns current balance

  14 TESTS, 14 PASSED, 0 FAILED

IBM zUnit

IBM zUnit (part of IBM Developer for z/OS) provides a more enterprise-grade unit testing framework that integrates with the z/OS development environment. zUnit test cases are written as COBOL programs themselves, following a specific structure:

      *================================================================*
      * zUnit Test Case for BANKTRAN withdrawal processing             *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. TBNKWD01.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       COPY AZUNIT.

       01  WS-TEST-TRANSACTION.
           05  WS-TRAN-TYPE         PIC X(1).
           05  WS-TRAN-AMOUNT       PIC S9(9)V99 COMP-3.
           05  WS-TRAN-ACCT-FROM    PIC X(10).
           05  WS-TRAN-ACCT-TO      PIC X(10).

       01  WS-TEST-ACCOUNT.
           05  WS-ACCT-NUMBER       PIC X(10).
           05  WS-ACCT-BALANCE      PIC S9(11)V99 COMP-3.
           05  WS-ACCT-STATUS       PIC X(1).
           05  WS-ACCT-TYPE         PIC X(2).
           05  WS-ACCT-DAILY-WD     PIC S9(9)V99 COMP-3.
           05  WS-ACCT-WD-LIMIT     PIC S9(9)V99 COMP-3.

       01  WS-TEST-RESULT.
           05  WS-RESULT-CODE       PIC X(2).
           05  WS-RESULT-MSG        PIC X(50).
           05  WS-NEW-BALANCE       PIC S9(11)V99 COMP-3.

       PROCEDURE DIVISION.

       TEST-OVERDRAFT-FEE-APPLICATION.
      *    ARRANGE: Set up checking account with low balance
           MOVE 'W'     TO WS-TRAN-TYPE
           MOVE 500.00  TO WS-TRAN-AMOUNT
           MOVE 'CH'    TO WS-ACCT-TYPE
           MOVE 'A'     TO WS-ACCT-STATUS
           MOVE 200.00  TO WS-ACCT-BALANCE
           MOVE ZERO    TO WS-ACCT-DAILY-WD
           MOVE 5000.00 TO WS-ACCT-WD-LIMIT

      *    ACT: Call the program under test
           CALL 'BANKTRAN' USING WS-TEST-TRANSACTION
                                  WS-TEST-ACCOUNT
                                  WS-TEST-RESULT

      *    ASSERT: Verify overdraft fee was applied
           CALL 'AZUASRT' USING AZU-ASSERT-EQUAL
               WS-RESULT-CODE
               '00'
               'Result code should be 00 for overdraft'

           COMPUTE WS-EXPECTED-BALANCE = -335.00
           CALL 'AZUASRT' USING AZU-ASSERT-EQUAL
               WS-NEW-BALANCE
               WS-EXPECTED-BALANCE
               'Balance should be -335.00 after overdraft fee'

           STOP RUN.

zUnit provides integration with IBM's IDz (IBM Developer for z/OS), enabling test execution from the IDE with results displayed in a graphical test runner similar to JUnit in Eclipse. It supports test fixtures, setup and teardown operations, and test suites that group related test cases.

Micro Focus Unit Test Framework

Micro Focus provides a unit test framework integrated with Visual COBOL and Enterprise Developer. It follows conventions familiar from xUnit-style frameworks:

      *================================================================*
      * Micro Focus Unit Test for interest calculation                 *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. TEST-INTEREST-CALC.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-PRINCIPAL         PIC S9(11)V99 COMP-3.
       01  WS-RATE              PIC S9(3)V9(6) COMP-3.
       01  WS-DAYS              PIC S9(5) COMP.
       01  WS-INTEREST          PIC S9(11)V99 COMP-3.
       01  WS-EXPECTED          PIC S9(11)V99 COMP-3.

       PROCEDURE DIVISION.

       ENTRY 'MFURUN'.

       TEST-DAILY-INTEREST-STANDARD.
      *    Test: $10,000 at 5% APR for 30 days
           MOVE 10000.00 TO WS-PRINCIPAL
           MOVE 0.050000 TO WS-RATE
           MOVE 30 TO WS-DAYS

           CALL 'INTCALC' USING WS-PRINCIPAL
                                 WS-RATE
                                 WS-DAYS
                                 WS-INTEREST

      *    Expected: 10000 * 0.05 * 30/365 = 41.10
           MOVE 41.10 TO WS-EXPECTED
           CALL 'MFUASSRT' USING WS-INTEREST
                                  WS-EXPECTED
                                  'Daily interest for 30 days'
           .

       TEST-LEAP-YEAR-INTEREST.
      *    Test: Interest calculation uses 366 days in leap year
           MOVE 10000.00 TO WS-PRINCIPAL
           MOVE 0.050000 TO WS-RATE
           MOVE 366 TO WS-DAYS

           CALL 'INTCALC' USING WS-PRINCIPAL
                                 WS-RATE
                                 WS-DAYS
                                 WS-INTEREST

      *    Expected: 10000 * 0.05 * 366/366 = 500.00
           MOVE 500.00 TO WS-EXPECTED
           CALL 'MFUASSRT' USING WS-INTEREST
                                  WS-EXPECTED
                                  'Full leap year interest'
           .

       TEST-ZERO-PRINCIPAL.
      *    Test: Zero principal produces zero interest
           MOVE ZERO TO WS-PRINCIPAL
           MOVE 0.050000 TO WS-RATE
           MOVE 30 TO WS-DAYS

           CALL 'INTCALC' USING WS-PRINCIPAL
                                 WS-RATE
                                 WS-DAYS
                                 WS-INTEREST

           MOVE ZERO TO WS-EXPECTED
           CALL 'MFUASSRT' USING WS-INTEREST
                                  WS-EXPECTED
                                  'Zero principal zero interest'
           .

           STOP RUN.

40.3 Writing Testable COBOL

Not all COBOL code is equally testable. Programs written with testing in mind are significantly easier to verify than those that are not. This section presents design principles that improve testability.

Designing for Testability

Separate I/O from logic. The most important design principle for testable COBOL is separating input/output operations from business logic. A paragraph that reads a file, processes the data, and writes results is difficult to test because you must set up files. A paragraph that takes data in WORKING-STORAGE, processes it, and puts results in WORKING-STORAGE is trivially testable.

      *================================================================*
      * POORLY TESTABLE: I/O mixed with business logic                 *
      *================================================================*
       PROCESS-ACCOUNT-FEES.
           READ ACCOUNT-FILE INTO WS-ACCOUNT-REC
           IF WS-ACCT-BALANCE < WS-MIN-BALANCE
               COMPUTE WS-FEE = WS-LOW-BAL-FEE
               SUBTRACT WS-FEE FROM WS-ACCT-BALANCE
               WRITE FEE-RECORD FROM WS-FEE-LINE
           END-IF
           REWRITE ACCOUNT-RECORD FROM WS-ACCOUNT-REC
           .

      *================================================================*
      * TESTABLE: I/O separated from business logic                    *
      *================================================================*
       PROCESS-ACCOUNT-FEES.
           PERFORM 3100-READ-ACCOUNT
           PERFORM 3200-CALCULATE-FEES
           PERFORM 3300-WRITE-RESULTS
           .

       3200-CALCULATE-FEES.
      *    Pure logic - no I/O - easily testable
           MOVE ZERO TO WS-FEE
           MOVE 'N' TO WS-FEE-APPLIED-FLAG
           IF WS-ACCT-BALANCE < WS-MIN-BALANCE
               COMPUTE WS-FEE = WS-LOW-BAL-FEE
               SUBTRACT WS-FEE FROM WS-ACCT-BALANCE
               MOVE 'Y' TO WS-FEE-APPLIED-FLAG
           END-IF
           .

The 3200-CALCULATE-FEES paragraph can be tested by simply setting WS-ACCT-BALANCE and WS-MIN-BALANCE to desired values and verifying WS-FEE and WS-FEE-APPLIED-FLAG after execution. No file setup is required.

Use CALL interfaces for reusable logic. Subprograms accessed via CALL with a defined LINKAGE SECTION are the most testable units in COBOL. They have explicit inputs and outputs defined by the USING clause, making them easy to invoke from test harnesses.

      *================================================================*
      * TESTABLE SUBPROGRAM: Explicit interface via LINKAGE            *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. FEECALC.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-WORK-FIELDS.
           05  WS-DAYS-BELOW       PIC S9(5) COMP.
           05  WS-FEE-RATE         PIC S9(3)V9(4) COMP-3.

       LINKAGE SECTION.
       01  LS-ACCOUNT-INFO.
           05  LS-ACCT-BALANCE     PIC S9(11)V99 COMP-3.
           05  LS-ACCT-TYPE        PIC X(2).
           05  LS-ACCT-MIN-BAL     PIC S9(9)V99 COMP-3.
           05  LS-DAYS-IN-PERIOD   PIC S9(5) COMP.

       01  LS-FEE-RESULT.
           05  LS-FEE-AMOUNT       PIC S9(7)V99 COMP-3.
           05  LS-FEE-CODE         PIC X(3).
           05  LS-FEE-DESC         PIC X(30).

       PROCEDURE DIVISION USING LS-ACCOUNT-INFO
                                 LS-FEE-RESULT.
       0000-CALCULATE-FEE.
           INITIALIZE LS-FEE-RESULT
           EVALUATE TRUE
               WHEN LS-ACCT-BALANCE < ZERO
                   PERFORM 1000-CALC-OVERDRAFT-FEE
               WHEN LS-ACCT-BALANCE < LS-ACCT-MIN-BAL
                   PERFORM 2000-CALC-LOW-BALANCE-FEE
               WHEN OTHER
                   MOVE ZERO TO LS-FEE-AMOUNT
                   MOVE 'NON' TO LS-FEE-CODE
                   MOVE 'NO FEE APPLICABLE' TO LS-FEE-DESC
           END-EVALUATE
           GOBACK
           .

       1000-CALC-OVERDRAFT-FEE.
           MOVE 35.00 TO LS-FEE-AMOUNT
           MOVE 'OVR' TO LS-FEE-CODE
           MOVE 'OVERDRAFT FEE' TO LS-FEE-DESC
           .

       2000-CALC-LOW-BALANCE-FEE.
           EVALUATE LS-ACCT-TYPE
               WHEN 'CH'
                   MOVE 12.00 TO LS-FEE-AMOUNT
                   MOVE 'LBL' TO LS-FEE-CODE
                   MOVE 'LOW BALANCE - CHECKING'
                       TO LS-FEE-DESC
               WHEN 'SV'
                   MOVE 5.00 TO LS-FEE-AMOUNT
                   MOVE 'LBS' TO LS-FEE-CODE
                   MOVE 'LOW BALANCE - SAVINGS'
                       TO LS-FEE-DESC
               WHEN OTHER
                   MOVE ZERO TO LS-FEE-AMOUNT
                   MOVE 'NON' TO LS-FEE-CODE
                   MOVE 'NO FEE FOR ACCOUNT TYPE'
                       TO LS-FEE-DESC
           END-EVALUATE
           .

Test Copybooks

Test copybooks define standardized test data structures that are shared across test programs. This ensures consistency and reduces the effort to create test data.

      *================================================================*
      * COPYBOOK: TESTACCT.cpy                                         *
      * Standard test account data for unit testing                    *
      *================================================================*
       01  TEST-STANDARD-CHECKING.
           05  FILLER PIC X(10)        VALUE '1000000001'.
           05  FILLER PIC S9(11)V99 COMP-3 VALUE +5000.00.
           05  FILLER PIC X(1)         VALUE 'A'.
           05  FILLER PIC X(2)         VALUE 'CH'.
           05  FILLER PIC S9(9)V99 COMP-3 VALUE +0.00.
           05  FILLER PIC S9(9)V99 COMP-3 VALUE +5000.00.

       01  TEST-OVERDRAWN-CHECKING.
           05  FILLER PIC X(10)        VALUE '1000000002'.
           05  FILLER PIC S9(11)V99 COMP-3 VALUE -150.00.
           05  FILLER PIC X(1)         VALUE 'A'.
           05  FILLER PIC X(2)         VALUE 'CH'.
           05  FILLER PIC S9(9)V99 COMP-3 VALUE +0.00.
           05  FILLER PIC S9(9)V99 COMP-3 VALUE +2000.00.

       01  TEST-SAVINGS-AT-MINIMUM.
           05  FILLER PIC X(10)        VALUE '2000000001'.
           05  FILLER PIC S9(11)V99 COMP-3 VALUE +100.00.
           05  FILLER PIC X(1)         VALUE 'A'.
           05  FILLER PIC X(2)         VALUE 'SV'.
           05  FILLER PIC S9(9)V99 COMP-3 VALUE +0.00.
           05  FILLER PIC S9(9)V99 COMP-3 VALUE +1000.00.

       01  TEST-FROZEN-ACCOUNT.
           05  FILLER PIC X(10)        VALUE '3000000001'.
           05  FILLER PIC S9(11)V99 COMP-3 VALUE +10000.00.
           05  FILLER PIC X(1)         VALUE 'F'.
           05  FILLER PIC X(2)         VALUE 'CH'.
           05  FILLER PIC S9(9)V99 COMP-3 VALUE +0.00.
           05  FILLER PIC S9(9)V99 COMP-3 VALUE +5000.00.

       01  TEST-CLOSED-ACCOUNT.
           05  FILLER PIC X(10)        VALUE '4000000001'.
           05  FILLER PIC S9(11)V99 COMP-3 VALUE +0.00.
           05  FILLER PIC X(1)         VALUE 'C'.
           05  FILLER PIC X(2)         VALUE 'CH'.
           05  FILLER PIC S9(9)V99 COMP-3 VALUE +0.00.
           05  FILLER PIC S9(9)V99 COMP-3 VALUE +0.00.

40.4 Test Data Management

Creating Representative Test Datasets

Effective testing requires test data that represents the full range of conditions the program will encounter in production. For banking systems, this includes:

  • Normal accounts with typical balances
  • Accounts at or near zero balance
  • Overdrawn accounts
  • Accounts at exactly the maximum balance the data structure can hold
  • Accounts with special characters in name fields
  • Accounts in every possible status (active, frozen, closed, dormant)
  • Transactions of every type, including boundary amounts
  • Date-sensitive test cases (month-end, quarter-end, year-end, leap year)
      *================================================================*
      * PROGRAM: TESTGEN1                                              *
      * PURPOSE: Generate test data for banking transaction tests      *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. TESTGEN1.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-TEST-ACCOUNT-REC.
           05  WS-TA-ACCT-NUM      PIC X(10).
           05  WS-TA-ACCT-NAME     PIC X(30).
           05  WS-TA-BALANCE       PIC S9(11)V99 COMP-3.
           05  WS-TA-STATUS        PIC X(1).
           05  WS-TA-TYPE          PIC X(2).
           05  WS-TA-OPEN-DATE     PIC 9(8).
           05  WS-TA-LAST-ACTIVITY PIC 9(8).

       01  WS-REC-COUNT            PIC 9(6) VALUE ZERO.

       PROCEDURE DIVISION.
       0000-GENERATE-TEST-DATA.
           OPEN OUTPUT TEST-ACCOUNT-FILE

      *    Category 1: Normal checking accounts
           PERFORM 1000-GEN-NORMAL-CHECKING
      *    Category 2: Boundary condition accounts
           PERFORM 2000-GEN-BOUNDARY-ACCOUNTS
      *    Category 3: Special status accounts
           PERFORM 3000-GEN-SPECIAL-STATUS
      *    Category 4: Edge cases
           PERFORM 4000-GEN-EDGE-CASES

           CLOSE TEST-ACCOUNT-FILE
           DISPLAY 'TEST RECORDS GENERATED: ' WS-REC-COUNT
           STOP RUN
           .

       1000-GEN-NORMAL-CHECKING.
      *    Active checking, typical balance
           MOVE '1000000001' TO WS-TA-ACCT-NUM
           MOVE 'JOHNSON, ROBERT M'  TO WS-TA-ACCT-NAME
           MOVE 5432.10     TO WS-TA-BALANCE
           MOVE 'A'         TO WS-TA-STATUS
           MOVE 'CH'        TO WS-TA-TYPE
           MOVE 20200115    TO WS-TA-OPEN-DATE
           MOVE 20260205    TO WS-TA-LAST-ACTIVITY
           PERFORM 8000-WRITE-TEST-RECORD

      *    Active checking, high balance
           MOVE '1000000002' TO WS-TA-ACCT-NUM
           MOVE 'WILLIAMS, SARAH J'  TO WS-TA-ACCT-NAME
           MOVE 250000.00   TO WS-TA-BALANCE
           MOVE 'A'         TO WS-TA-STATUS
           MOVE 'CH'        TO WS-TA-TYPE
           MOVE 20180301    TO WS-TA-OPEN-DATE
           MOVE 20260210    TO WS-TA-LAST-ACTIVITY
           PERFORM 8000-WRITE-TEST-RECORD
           .

       2000-GEN-BOUNDARY-ACCOUNTS.
      *    Zero balance
           MOVE '2000000001' TO WS-TA-ACCT-NUM
           MOVE 'CHEN, DAVID W'      TO WS-TA-ACCT-NAME
           MOVE ZERO         TO WS-TA-BALANCE
           MOVE 'A'          TO WS-TA-STATUS
           MOVE 'CH'         TO WS-TA-TYPE
           MOVE 20210601     TO WS-TA-OPEN-DATE
           MOVE 20260101     TO WS-TA-LAST-ACTIVITY
           PERFORM 8000-WRITE-TEST-RECORD

      *    Maximum balance for field size
           MOVE '2000000002' TO WS-TA-ACCT-NUM
           MOVE 'TRUST FUND ACCT ONE' TO WS-TA-ACCT-NAME
           MOVE 99999999999.99 TO WS-TA-BALANCE
           MOVE 'A'          TO WS-TA-STATUS
           MOVE 'MM'         TO WS-TA-TYPE
           MOVE 20100101     TO WS-TA-OPEN-DATE
           MOVE 20260209     TO WS-TA-LAST-ACTIVITY
           PERFORM 8000-WRITE-TEST-RECORD

      *    Negative balance (overdrawn)
           MOVE '2000000003' TO WS-TA-ACCT-NUM
           MOVE 'MARTINEZ, ELENA R'  TO WS-TA-ACCT-NAME
           MOVE -247.50      TO WS-TA-BALANCE
           MOVE 'A'          TO WS-TA-STATUS
           MOVE 'CH'         TO WS-TA-TYPE
           MOVE 20190815     TO WS-TA-OPEN-DATE
           MOVE 20260208     TO WS-TA-LAST-ACTIVITY
           PERFORM 8000-WRITE-TEST-RECORD
           .

       3000-GEN-SPECIAL-STATUS.
      *    Frozen account
           MOVE '3000000001' TO WS-TA-ACCT-NUM
           MOVE 'RESTRICTED ACCT'    TO WS-TA-ACCT-NAME
           MOVE 15000.00    TO WS-TA-BALANCE
           MOVE 'F'         TO WS-TA-STATUS
           MOVE 'SV'        TO WS-TA-TYPE
           MOVE 20170401    TO WS-TA-OPEN-DATE
           MOVE 20251115    TO WS-TA-LAST-ACTIVITY
           PERFORM 8000-WRITE-TEST-RECORD

      *    Closed account
           MOVE '3000000002' TO WS-TA-ACCT-NUM
           MOVE 'THOMPSON, MARK A'   TO WS-TA-ACCT-NAME
           MOVE ZERO        TO WS-TA-BALANCE
           MOVE 'C'         TO WS-TA-STATUS
           MOVE 'CH'        TO WS-TA-TYPE
           MOVE 20150901    TO WS-TA-OPEN-DATE
           MOVE 20240630    TO WS-TA-LAST-ACTIVITY
           PERFORM 8000-WRITE-TEST-RECORD
           .

       4000-GEN-EDGE-CASES.
      *    Savings at exact minimum balance
           MOVE '4000000001' TO WS-TA-ACCT-NUM
           MOVE 'PATEL, ANITA K'     TO WS-TA-ACCT-NAME
           MOVE 100.00      TO WS-TA-BALANCE
           MOVE 'A'         TO WS-TA-STATUS
           MOVE 'SV'        TO WS-TA-TYPE
           MOVE 20220301    TO WS-TA-OPEN-DATE
           MOVE 20260201    TO WS-TA-LAST-ACTIVITY
           PERFORM 8000-WRITE-TEST-RECORD

      *    Savings one cent above minimum
           MOVE '4000000002' TO WS-TA-ACCT-NUM
           MOVE 'GARCIA, LUIS F'     TO WS-TA-ACCT-NAME
           MOVE 100.01      TO WS-TA-BALANCE
           MOVE 'A'         TO WS-TA-STATUS
           MOVE 'SV'        TO WS-TA-TYPE
           MOVE 20220615    TO WS-TA-OPEN-DATE
           MOVE 20260203    TO WS-TA-LAST-ACTIVITY
           PERFORM 8000-WRITE-TEST-RECORD
           .

       8000-WRITE-TEST-RECORD.
           WRITE TEST-ACCOUNT-RECORD FROM WS-TEST-ACCOUNT-REC
           ADD 1 TO WS-REC-COUNT
           .

Data Masking for PII

Production data is often the best source of realistic test data, but it contains personally identifiable information (PII) that must be masked before use in non-production environments. Data masking replaces sensitive values with fictitious but structurally valid data.

      *================================================================*
      * PROGRAM: DATAMASK                                              *
      * PURPOSE: Mask PII in account data for test environments        *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. DATAMASK.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-ACCOUNT-REC.
           05  WS-ACCT-NUM          PIC X(10).
           05  WS-ACCT-NAME         PIC X(30).
           05  WS-SSN               PIC X(9).
           05  WS-PHONE             PIC X(10).
           05  WS-ADDRESS           PIC X(60).
           05  WS-BALANCE           PIC S9(11)V99 COMP-3.
           05  WS-ACCT-DATA         PIC X(100).

       01  WS-MASK-SEED             PIC 9(8).
       01  WS-RANDOM-NUM            PIC 9(9).
       01  WS-MASKED-DIGIT          PIC 9(1).

       01  WS-FAKE-NAMES.
           05  FILLER PIC X(30) VALUE 'TESTUSER, ALPHA A           '.
           05  FILLER PIC X(30) VALUE 'TESTUSER, BETA B            '.
           05  FILLER PIC X(30) VALUE 'TESTUSER, GAMMA G           '.
           05  FILLER PIC X(30) VALUE 'TESTUSER, DELTA D           '.
           05  FILLER PIC X(30) VALUE 'TESTUSER, EPSILON E         '.
       01  WS-NAME-TABLE REDEFINES WS-FAKE-NAMES.
           05  WS-FAKE-NAME PIC X(30) OCCURS 5 TIMES.
       01  WS-NAME-IDX              PIC 9(1).

       PROCEDURE DIVISION.
       0000-MASK-DATA.
           PERFORM 1000-INITIALIZE
           PERFORM 2000-PROCESS-RECORDS
               UNTIL WS-EOF-FLAG = 'Y'
           PERFORM 9000-TERMINATE
           STOP RUN
           .

       2000-PROCESS-RECORDS.
           READ PROD-INPUT INTO WS-ACCOUNT-REC
               AT END
                   MOVE 'Y' TO WS-EOF-FLAG
                   EXIT PARAGRAPH
           END-READ

      *    Mask account number - preserve first 2, mask rest
           PERFORM 2100-MASK-ACCT-NUMBER
      *    Replace name with test name
           PERFORM 2200-MASK-NAME
      *    Replace SSN completely
           PERFORM 2300-MASK-SSN
      *    Mask phone number
           PERFORM 2400-MASK-PHONE
      *    Replace address
           PERFORM 2500-MASK-ADDRESS
      *    Preserve balance - numeric data is test-critical
      *    Preserve acct-data - non-PII operational data

           WRITE MASKED-RECORD FROM WS-ACCOUNT-REC
           ADD 1 TO WS-RECORDS-PROCESSED
           .

       2100-MASK-ACCT-NUMBER.
      *    Keep first 2 digits, replace rest with hash
           MOVE WS-ACCT-NUM(3:8) TO WS-MASK-SEED
           COMPUTE WS-RANDOM-NUM =
               FUNCTION MOD(WS-MASK-SEED * 7919 + 104729,
                            100000000)
           MOVE WS-RANDOM-NUM TO WS-ACCT-NUM(3:8)
           .

       2200-MASK-NAME.
           COMPUTE WS-NAME-IDX =
               FUNCTION MOD(WS-RECORDS-PROCESSED, 5) + 1
           MOVE WS-FAKE-NAME(WS-NAME-IDX) TO WS-ACCT-NAME
           .

       2300-MASK-SSN.
      *    Generate structurally valid but fake SSN
      *    Never use real SSN patterns (000, 666, 900-999)
           MOVE '999' TO WS-SSN(1:3)
           COMPUTE WS-RANDOM-NUM =
               FUNCTION MOD(WS-RECORDS-PROCESSED * 3571, 999999)
           MOVE WS-RANDOM-NUM TO WS-SSN(4:6)
           .

       2400-MASK-PHONE.
           MOVE '5550000000' TO WS-PHONE
           COMPUTE WS-RANDOM-NUM =
               FUNCTION MOD(WS-RECORDS-PROCESSED * 1117, 10000)
           MOVE WS-RANDOM-NUM TO WS-PHONE(7:4)
           .

       2500-MASK-ADDRESS.
           STRING WS-RECORDS-PROCESSED ' TEST STREET, '
                  'TESTCITY, TS 00000'
               DELIMITED SIZE INTO WS-ADDRESS
           END-STRING
           .

40.5 JCL-Based Test Automation

On z/OS, batch testing is orchestrated through JCL (Job Control Language). Test JCL sets up the test environment, executes the programs under test, and verifies results through condition code checking and output comparison.

Test Job Streams

A complete test job stream for a banking batch process might look like this:

//BNKTST01 JOB (TESTING),'BANK BATCH TESTS',
//         CLASS=T,MSGCLASS=X,NOTIFY=&SYSUID
//*================================================================*
//* TEST JOB: Daily transaction processing regression test          *
//* This job:                                                       *
//*   1. Copies production structure to test datasets                *
//*   2. Loads test data                                            *
//*   3. Runs the transaction processing batch                      *
//*   4. Compares output to expected results                        *
//*   5. Reports pass/fail                                          *
//*================================================================*
//*
//* STEP 1: ALLOCATE TEST DATASETS
//*
//ALLOC   EXEC PGM=IEFBR14
//TESTMSTR DD DSN=TEST.BANK.MASTER,
//            DISP=(NEW,CATLG,DELETE),
//            SPACE=(CYL,(5,1)),
//            DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)
//TESTTRAN DD DSN=TEST.BANK.TRANS,
//            DISP=(NEW,CATLG,DELETE),
//            SPACE=(CYL,(2,1)),
//            DCB=(RECFM=FB,LRECL=100,BLKSIZE=0)
//TESTOUT  DD DSN=TEST.BANK.OUTPUT,
//            DISP=(NEW,CATLG,DELETE),
//            SPACE=(CYL,(5,1)),
//            DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)
//TESTRPT  DD DSN=TEST.BANK.REPORT,
//            DISP=(NEW,CATLG,DELETE),
//            SPACE=(CYL,(1,1)),
//            DCB=(RECFM=FBA,LRECL=133,BLKSIZE=0)
//*
//* STEP 2: LOAD TEST MASTER FILE
//*
//LOADMSTR EXEC PGM=TESTGEN1
//STEPLIB  DD DSN=TEST.LOAD.LIBRARY,DISP=SHR
//TESTFILE DD DSN=TEST.BANK.MASTER,DISP=SHR
//SYSOUT   DD SYSOUT=*
//*
//* STEP 3: LOAD TEST TRANSACTIONS
//*
//LOADTRAN EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//INFILE   DD DSN=TEST.BANK.TRANS.TEMPLATE,DISP=SHR
//OUTFILE  DD DSN=TEST.BANK.TRANS,DISP=SHR
//SYSIN    DD *
  REPRO INFILE(INFILE) OUTFILE(OUTFILE)
/*
//*
//* STEP 4: RUN THE PROGRAM UNDER TEST
//*
//PROCESS EXEC PGM=BANKTRAN
//STEPLIB  DD DSN=TEST.LOAD.LIBRARY,DISP=SHR
//MASTERIN DD DSN=TEST.BANK.MASTER,DISP=SHR
//TRANSIN  DD DSN=TEST.BANK.TRANS,DISP=SHR
//MSTROUT  DD DSN=TEST.BANK.OUTPUT,DISP=SHR
//REPORT   DD DSN=TEST.BANK.REPORT,DISP=SHR
//SYSOUT   DD SYSOUT=*
//*
//* STEP 5: COMPARE OUTPUT TO EXPECTED RESULTS
//*
//COMPARE EXEC PGM=SUPERC,COND=(0,NE,PROCESS)
//NEWDD    DD DSN=TEST.BANK.OUTPUT,DISP=SHR
//OLDDD    DD DSN=TEST.BANK.EXPECTED.OUTPUT,DISP=SHR
//OUTDD    DD SYSOUT=*
//SYSIN    DD *
  CMPCOLM 1:200
/*
//*
//* STEP 6: COMPARE REPORT TO EXPECTED
//*
//CMPRPT  EXEC PGM=SUPERC,COND=(0,NE,PROCESS)
//NEWDD    DD DSN=TEST.BANK.REPORT,DISP=SHR
//OLDDD    DD DSN=TEST.BANK.EXPECTED.REPORT,DISP=SHR
//OUTDD    DD SYSOUT=*
//SYSIN    DD *
  CMPCOLM 1:133
/*
//*
//* STEP 7: CLEANUP TEST DATASETS
//*
//CLEANUP EXEC PGM=IEFBR14
//TESTMSTR DD DSN=TEST.BANK.MASTER,DISP=(OLD,DELETE)
//TESTTRAN DD DSN=TEST.BANK.TRANS,DISP=(OLD,DELETE)
//TESTOUT  DD DSN=TEST.BANK.OUTPUT,DISP=(OLD,DELETE)
//TESTRPT  DD DSN=TEST.BANK.REPORT,DISP=(OLD,DELETE)

Condition Code Checking

JCL condition codes (return codes) are the primary mechanism for controlling test flow. The COND parameter on EXEC statements determines whether a step executes based on the return codes of previous steps.

//*================================================================*
//* Condition code conventions for test jobs:                       *
//*   RC=0  - All tests passed                                     *
//*   RC=4  - Warnings (non-critical differences)                  *
//*   RC=8  - Test failures detected                               *
//*   RC=12 - Test infrastructure error                            *
//*   RC=16 - Abend or severe error                                *
//*================================================================*
//*
//* Only run comparison if processing completed successfully
//COMPARE EXEC PGM=SUPERC,COND=(4,LT,PROCESS)
//*
//* Only run cleanup if comparison ran
//CLEANUP EXEC PGM=IEFBR14,COND=(8,LT,COMPARE)
//*
//* Send notification only on failure
//NOTIFY  EXEC PGM=NOTIFPGM,COND=(0,EQ,COMPARE)
//SYSIN    DD *
  TEST JOB BNKTST01 COMPLETED WITH DIFFERENCES
  REVIEW COMPARISON OUTPUT FOR DETAILS
/*

The COND parameter syntax COND=(code,operator,stepname) means "skip this step if the condition is true." So COND=(0,NE,PROCESS) means "skip this step if the return code from PROCESS is not equal to 0" -- in other words, only run this step if PROCESS completed with RC=0.

Automated Test Suites in JCL

Multiple test jobs can be chained together using job scheduling systems like CA-7, TWS (Tivoli Workload Scheduler), or Control-M. A nightly test suite might include:

//BNKTEST JOB (TESTING),'NIGHTLY TEST SUITE',
//         CLASS=T,MSGCLASS=X,NOTIFY=&SYSUID
//*================================================================*
//* NIGHTLY REGRESSION TEST SUITE                                   *
//* Runs all banking transaction tests in sequence                  *
//*================================================================*
//*
//* TEST SUITE 1: Account opening
//*
//SUITE01 EXEC PROC=BNKTPROC,TEST=ACCTOPEN
//*
//* TEST SUITE 2: Deposits
//*
//SUITE02 EXEC PROC=BNKTPROC,TEST=DEPOSITS,
//         COND=(8,LT,SUITE01)
//*
//* TEST SUITE 3: Withdrawals
//*
//SUITE03 EXEC PROC=BNKTPROC,TEST=WITHDRAW,
//         COND=(8,LT,SUITE02)
//*
//* TEST SUITE 4: Transfers
//*
//SUITE04 EXEC PROC=BNKTPROC,TEST=TRANSFER,
//         COND=(8,LT,SUITE03)
//*
//* TEST SUITE 5: Interest calculation
//*
//SUITE05 EXEC PROC=BNKTPROC,TEST=INTEREST,
//         COND=(8,LT,SUITE04)
//*
//* TEST SUITE 6: Fee processing
//*
//SUITE06 EXEC PROC=BNKTPROC,TEST=FEEPROC,
//         COND=(8,LT,SUITE05)
//*
//* TEST SUITE 7: End of day batch
//*
//SUITE07 EXEC PROC=BNKTPROC,TEST=EODBATCH,
//         COND=(8,LT,SUITE06)
//*
//* GENERATE SUMMARY REPORT
//*
//SUMMARY EXEC PGM=TSTRPTG1
//RESULTS  DD DSN=TEST.SUITE.RESULTS,DISP=SHR
//REPORT   DD SYSOUT=*,DEST=TESTTEAM
//SYSIN    DD *
  SUITE BNKTEST
  DATE &LYYMMDD
  NOTIFY TESTLEAD@BANK.COM
/*

40.6 Regression Testing Strategies

Golden File Comparison

The golden file approach is the most common regression testing strategy for COBOL batch programs. A golden file is a verified correct output file produced by the current production program for a defined set of test inputs. When the program is modified, it is run against the same test inputs and its output is compared byte-for-byte to the golden file.

      *================================================================*
      * PROGRAM: GOLDFCMP                                              *
      * PURPOSE: Golden file comparison with detailed diff reporting   *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. GOLDFCMP.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-GOLDEN-REC           PIC X(200).
       01  WS-ACTUAL-REC           PIC X(200).
       01  WS-REC-NUMBER           PIC 9(8) VALUE ZERO.
       01  WS-DIFF-COUNT           PIC 9(6) VALUE ZERO.
       01  WS-MATCH-COUNT          PIC 9(8) VALUE ZERO.
       01  WS-GOLDEN-EOF           PIC X    VALUE 'N'.
       01  WS-ACTUAL-EOF           PIC X    VALUE 'N'.
       01  WS-FIELD-IDX            PIC 9(3).

       01  WS-DIFF-REPORT-LINE.
           05  FILLER              PIC X(5)  VALUE 'REC# '.
           05  WS-DR-REC-NUM       PIC Z(7)9.
           05  FILLER              PIC X(2)  VALUE '  '.
           05  WS-DR-FIELD-NAME    PIC X(20).
           05  FILLER              PIC X(2)  VALUE '  '.
           05  WS-DR-EXPECTED      PIC X(40).
           05  FILLER              PIC X(5)  VALUE ' GOT '.
           05  WS-DR-ACTUAL        PIC X(40).

       PROCEDURE DIVISION.
       0000-COMPARE-FILES.
           OPEN INPUT GOLDEN-FILE ACTUAL-FILE
           OPEN OUTPUT DIFF-REPORT
           PERFORM 1000-COMPARE-RECORDS
               UNTIL WS-GOLDEN-EOF = 'Y'
               AND   WS-ACTUAL-EOF = 'Y'
           PERFORM 2000-WRITE-SUMMARY
           CLOSE GOLDEN-FILE ACTUAL-FILE DIFF-REPORT
           IF WS-DIFF-COUNT > ZERO
               MOVE 8 TO RETURN-CODE
           ELSE
               MOVE 0 TO RETURN-CODE
           END-IF
           STOP RUN
           .

       1000-COMPARE-RECORDS.
           READ GOLDEN-FILE INTO WS-GOLDEN-REC
               AT END MOVE 'Y' TO WS-GOLDEN-EOF
           END-READ
           READ ACTUAL-FILE INTO WS-ACTUAL-REC
               AT END MOVE 'Y' TO WS-ACTUAL-EOF
           END-READ
           ADD 1 TO WS-REC-NUMBER

           EVALUATE TRUE
               WHEN WS-GOLDEN-EOF = 'Y'
                   AND WS-ACTUAL-EOF = 'N'
                   ADD 1 TO WS-DIFF-COUNT
                   MOVE 'EXTRA RECORD IN ACTUAL'
                       TO WS-DR-FIELD-NAME
                   MOVE WS-ACTUAL-REC TO WS-DR-ACTUAL
                   MOVE SPACES TO WS-DR-EXPECTED
                   MOVE WS-REC-NUMBER TO WS-DR-REC-NUM
                   WRITE DIFF-RECORD FROM WS-DIFF-REPORT-LINE
               WHEN WS-GOLDEN-EOF = 'N'
                   AND WS-ACTUAL-EOF = 'Y'
                   ADD 1 TO WS-DIFF-COUNT
                   MOVE 'MISSING RECORD IN ACTUAL'
                       TO WS-DR-FIELD-NAME
                   MOVE WS-GOLDEN-REC TO WS-DR-EXPECTED
                   MOVE SPACES TO WS-DR-ACTUAL
                   MOVE WS-REC-NUMBER TO WS-DR-REC-NUM
                   WRITE DIFF-RECORD FROM WS-DIFF-REPORT-LINE
               WHEN WS-GOLDEN-EOF = 'N'
                   AND WS-ACTUAL-EOF = 'N'
                   IF WS-GOLDEN-REC = WS-ACTUAL-REC
                       ADD 1 TO WS-MATCH-COUNT
                   ELSE
                       ADD 1 TO WS-DIFF-COUNT
                       PERFORM 1100-REPORT-DIFFERENCES
                   END-IF
           END-EVALUATE
           .

       1100-REPORT-DIFFERENCES.
           MOVE WS-REC-NUMBER TO WS-DR-REC-NUM
           MOVE 'RECORD MISMATCH' TO WS-DR-FIELD-NAME
           MOVE WS-GOLDEN-REC(1:40) TO WS-DR-EXPECTED
           MOVE WS-ACTUAL-REC(1:40) TO WS-DR-ACTUAL
           WRITE DIFF-RECORD FROM WS-DIFF-REPORT-LINE
           .

       2000-WRITE-SUMMARY.
           MOVE WS-REC-NUMBER TO WS-DR-REC-NUM
           MOVE 'TOTAL RECORDS' TO WS-DR-FIELD-NAME
           WRITE DIFF-RECORD FROM WS-DIFF-REPORT-LINE
           MOVE WS-MATCH-COUNT TO WS-DR-REC-NUM
           MOVE 'MATCHING RECORDS' TO WS-DR-FIELD-NAME
           WRITE DIFF-RECORD FROM WS-DIFF-REPORT-LINE
           MOVE WS-DIFF-COUNT TO WS-DR-REC-NUM
           MOVE 'DIFFERENCES FOUND' TO WS-DR-FIELD-NAME
           WRITE DIFF-RECORD FROM WS-DIFF-REPORT-LINE
           .

Parallel Running

Parallel running is described in detail in Chapter 39 in the context of modernization. For ongoing regression testing, a lighter-weight form of parallel running compares the output of the modified program against the output of the unmodified program for the same daily production inputs. This is sometimes called "shadow mode" testing.

The advantage over golden file comparison is that parallel running tests against real production data volumes and data patterns, catching issues that test datasets might not reveal. The disadvantage is that it requires double the computing resources and introduces a delay in detecting problems (the comparison happens after the batch run, not during it).


40.7 Code Coverage Analysis

Code coverage measures what percentage of a program's code is actually executed during testing. Low coverage indicates that significant portions of the code are untested and may contain hidden defects.

Statement Coverage

Statement coverage measures the percentage of executable statements that are executed at least once during the test suite. This is the most basic and most commonly used coverage metric.

For COBOL programs, statement coverage is typically measured by the compiler's debugging facilities or by dedicated coverage tools. IBM's Debug Tool and Micro Focus's code coverage feature both provide statement-level coverage reporting.

      *================================================================*
      * Coverage analysis example                                      *
      * Lines marked with > were executed during testing               *
      * Lines marked with ! were NOT executed (coverage gaps)          *
      *================================================================*
      *>   0000-MAIN-PROCESS.
      *>       PERFORM 1000-INITIALIZE
      *>       PERFORM 2000-PROCESS-TRANSACTIONS
      *>           UNTIL WS-EOF-FLAG = 'Y'
      *>       PERFORM 9000-TERMINATE
      *>       STOP RUN
      *>       .
      *>
      *>   2200-PROCESS-WITHDRAWAL.
      *>       ADD WS-TRAN-AMOUNT TO WS-ACCT-DAILY-WD
      *>       IF WS-ACCT-DAILY-WD > WS-ACCT-WD-LIMIT
      *>           MOVE '02' TO WS-RESULT-CODE
      *>           MOVE 'DAILY WITHDRAWAL LIMIT EXCEEDED'
      *>               TO WS-RESULT-MSG
      *>           SUBTRACT WS-TRAN-AMOUNT FROM WS-ACCT-DAILY-WD
      *>           EXIT PARAGRAPH
      *>       END-IF
      *>       IF ACCT-SAVINGS
      *!           COMPUTE WS-NEW-BALANCE =
      *!               WS-ACCT-BALANCE - WS-TRAN-AMOUNT
      *!           IF WS-NEW-BALANCE < WS-MIN-SAVINGS-BAL
      *!               MOVE '01' TO WS-RESULT-CODE
      *!               MOVE 'BELOW MINIMUM SAVINGS BALANCE'
      *!                   TO WS-RESULT-MSG
      *!               SUBTRACT WS-TRAN-AMOUNT FROM WS-ACCT-DAILY-WD
      *!               EXIT PARAGRAPH
      *!           END-IF
      *>       END-IF
      *>       SUBTRACT WS-TRAN-AMOUNT FROM WS-ACCT-BALANCE

In this example, the savings account minimum balance check was never executed during testing, indicating that the test suite lacks savings account withdrawal scenarios. This is exactly the kind of gap that coverage analysis reveals.

Branch Coverage

Branch coverage measures the percentage of decision branches (IF conditions, EVALUATE WHEN clauses) that have been taken at least once. A program can have 100% statement coverage but less than 100% branch coverage if some conditional branches always evaluate the same way during testing.

For COBOL, the important branches to cover include:

  • Both TRUE and FALSE branches of every IF statement
  • Every WHEN clause in every EVALUATE statement (including WHEN OTHER)
  • Both AT END and NOT AT END of every READ statement
  • Both INVALID KEY and NOT INVALID KEY of every keyed file operation
  • Both ON EXCEPTION and NOT ON EXCEPTION paths
  • Both ON OVERFLOW and NOT ON OVERFLOW paths for STRING/UNSTRING
  • Both ON SIZE ERROR and NOT ON SIZE ERROR for arithmetic operations

Path Coverage

Path coverage measures the percentage of distinct paths through a program that have been tested. A program with three sequential IF statements has eight possible paths (2^3). Path coverage is the most thorough but also the most expensive metric, as the number of paths grows exponentially with the number of decisions.

For complex COBOL programs, achieving 100% path coverage is usually impractical. A pragmatic approach is to focus on the most critical paths: the main success path (the most common execution path through the program), all error-handling paths, and paths through high-risk calculations (financial calculations, regulatory computations, and similar business-critical logic).

Coverage Targets

Industry standards for COBOL systems in regulated industries typically require:

  • Statement coverage: 85% minimum, 95% target for critical programs
  • Branch coverage: 80% minimum, 90% target for critical programs
  • Path coverage: Not typically mandated, but critical paths should be explicitly tested

40.8 Static Analysis

Static analysis examines source code without executing it, identifying potential defects, coding standard violations, and maintainability concerns.

Code Review Checklists

Manual code review remains valuable even with automated scanning tools. A COBOL-specific code review checklist should include:

Data Division checks: - Are all data items properly initialized (or explicitly initialized in the PROCEDURE DIVISION before first use)? - Are numeric field sizes sufficient for the maximum expected values? - Are signed fields used wherever negative values are possible? - Are COMP-3 fields used for decimal arithmetic in financial calculations? - Are copybooks used consistently (no local redefinitions of shared structures)?

Procedure Division checks: - Does every paragraph have exactly one entry point and one exit point? - Are all file operations checked for status codes? - Are all CALL statements checked for return codes? - Do all arithmetic operations have ON SIZE ERROR clauses where overflow is possible? - Are STRING and UNSTRING operations protected with ON OVERFLOW? - Is WORKING-STORAGE properly initialized before each iteration in loops?

Financial calculation checks: - Are all monetary calculations performed in COMP-3 (packed decimal)? - Are rounding rules applied consistently (ROUNDED phrase where required)? - Do accumulated totals use fields large enough to avoid overflow? - Are tax and fee calculations applied in the correct sequence?

Automated Scanning

Automated static analysis tools scan COBOL source code for known defect patterns. Common findings include:

      *================================================================*
      * Common static analysis findings in COBOL                       *
      *================================================================*

      *--- FINDING: Uninitialized variable used in calculation ---
       CALCULATE-INTEREST.
           COMPUTE WS-INTEREST =
               WS-BALANCE * WS-RATE * WS-DAYS / 365
      *    WARNING: WS-DAYS may not be initialized if
      *    3000-DETERMINE-PERIOD was not executed

      *--- FINDING: Missing SIZE ERROR on financial calculation ---
       COMPUTE-TOTAL.
           ADD WS-SUBTOTAL TO WS-GRAND-TOTAL
      *    WARNING: No ON SIZE ERROR clause; if WS-GRAND-TOTAL
      *    overflows PIC S9(9)V99, the result is truncated silently

      *--- FINDING: File status not checked after I/O ---
       READ-CUSTOMER.
           READ CUSTOMER-FILE INTO WS-CUSTOMER-REC
      *    WARNING: File status WS-CUST-FILE-STATUS not checked
      *    Possible conditions: end-of-file, record not found, I/O error

      *--- CORRECTED VERSIONS ---

       CALCULATE-INTEREST-FIXED.
           IF WS-DAYS NOT NUMERIC OR WS-DAYS = ZERO
               MOVE ZERO TO WS-INTEREST
           ELSE
               COMPUTE WS-INTEREST =
                   WS-BALANCE * WS-RATE * WS-DAYS / 365
                   ON SIZE ERROR
                       MOVE ZERO TO WS-INTEREST
                       PERFORM 9100-LOG-CALC-ERROR
               END-COMPUTE
           END-IF
           .

       COMPUTE-TOTAL-FIXED.
           ADD WS-SUBTOTAL TO WS-GRAND-TOTAL
               ON SIZE ERROR
                   PERFORM 9200-LOG-OVERFLOW-ERROR
           END-ADD
           .

       READ-CUSTOMER-FIXED.
           READ CUSTOMER-FILE INTO WS-CUSTOMER-REC
           EVALUATE WS-CUST-FILE-STATUS
               WHEN '00'
                   CONTINUE
               WHEN '10'
                   MOVE 'Y' TO WS-EOF-FLAG
               WHEN '23'
                   MOVE 'RECORD NOT FOUND' TO WS-ERROR-MSG
                   PERFORM 9100-HANDLE-NOT-FOUND
               WHEN OTHER
                   MOVE WS-CUST-FILE-STATUS TO WS-ERR-STATUS
                   PERFORM 9300-HANDLE-IO-ERROR
           END-EVALUATE
           .

40.9 Deployment on z/OS

Deploying COBOL programs on z/OS involves a multi-step process that goes far beyond simply copying an executable file.

COBOL programs are compiled and link-edited into load modules that reside in partitioned datasets (PDSEs). The compilation process includes:

  1. Precompilation (if the program contains embedded SQL, CICS commands, or DL/I calls). The precompiler processes EXEC SQL, EXEC CICS, or EXEC DLI statements and replaces them with COBOL CALL statements.
  2. Compilation. The COBOL compiler translates the source code into object code.
  3. Link-edit. The linkage editor combines the object module with any required runtime libraries, subroutines, and precompiler-generated modules to create an executable load module.
//*================================================================*
//* Compile, precompile (DB2), and link-edit a COBOL program        *
//*================================================================*
//COMPILE EXEC PROC=DSNHCOB2,
//         MEM=BANKTRAN,
//         PARM.PC='HOST(IBMCOB)',
//         PARM.COB='LIB,RENT,OPT(FULL),LIST,MAP,XREF',
//         PARM.LKED='LIST,XREF,MAP,RENT'
//PC.DBRMLIB DD DSN=BANK.DBRM.LIBRARY(BANKTRAN),DISP=SHR
//PC.SYSLIB  DD DSN=BANK.COPYLIB,DISP=SHR
//COB.SYSLIB DD DSN=BANK.COPYLIB,DISP=SHR
//LKED.SYSLMOD DD DSN=BANK.LOAD.LIBRARY(BANKTRAN),DISP=SHR
//LKED.SYSLIB  DD DSN=CEE.SCEELKED,DISP=SHR
//             DD DSN=DSN.SDSNLOAD,DISP=SHR

DB2 Bind Process

Programs that access DB2 must have their SQL statements "bound" to create access plans (packages) that the DB2 optimizer uses to determine how to execute each SQL statement. The bind process is separate from compilation and must be performed whenever SQL statements change.

//*================================================================*
//* Bind DB2 package for BANKTRAN program                          *
//*================================================================*
//BIND    EXEC PGM=IKJEFT01
//STEPLIB  DD DSN=DSN.SDSNLOAD,DISP=SHR
//DBRMLIB  DD DSN=BANK.DBRM.LIBRARY,DISP=SHR
//SYSPRINT DD SYSOUT=*
//SYSTSPRT DD SYSOUT=*
//SYSTSIN  DD *
  DSN SYSTEM(DB2P)
  BIND PACKAGE(BANKPKG) -
       MEMBER(BANKTRAN) -
       OWNER(BANKOWN) -
       QUALIFIER(BANKPROD) -
       ISOLATION(CS) -
       VALIDATE(BIND) -
       RELEASE(COMMIT) -
       ACTION(REPLACE) -
       ENCODING(EBCDIC)
  BIND PLAN(BANKPLAN) -
       PKLIST(BANKPKG.*) -
       ACTION(REPLACE) -
       ISOLATION(CS)
  END
/*

CICS Resource Definitions

Programs that run under CICS must have resource definitions installed in the CICS region. These definitions specify the program name, language, transaction ID, and other properties.

//*================================================================*
//* Define CICS resources for BANKTRAN                             *
//*================================================================*
//* Program definition
//DEFPROG EXEC PGM=DFHCSDUP
//STEPLIB  DD DSN=CICS.SDFHLOAD,DISP=SHR
//DFHCSD   DD DSN=CICS.DFHCSD,DISP=SHR
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  DEFINE PROGRAM(BANKTRAN)
         GROUP(BANKGRP)
         LANGUAGE(COBOL)
         DATALOCATION(ANY)
         EXECKEY(USER)
         STATUS(ENABLED)

  DEFINE TRANSACTION(BKTR)
         GROUP(BANKGRP)
         PROGRAM(BANKTRAN)
         TASKDATALOC(ANY)
         ISOLATE(YES)
         PRIORITY(100)

  INSTALL GROUP(BANKGRP)
/*

Promotion Workflows

Production deployment follows a controlled promotion workflow that moves code through a series of environments:

  1. Development (DEV). Programmers compile and test their changes.
  2. Unit Test (UT). Unit tests are executed against the compiled program.
  3. Integration Test (IT). The program is tested with other programs in the application.
  4. Quality Assurance (QA). Full regression testing and user acceptance testing.
  5. Pre-Production (PPROD). Final verification in a production-like environment.
  6. Production (PROD). Live deployment.

Each promotion requires approval from designated reviewers and passes through automated gates that verify compilation success, test results, and code quality metrics.


40.10 Change Management

Source Control with Endevor

CA Endevor Software Change Manager is the most widely used source control system on the mainframe. It provides a controlled environment for managing COBOL source code, copybooks, JCL, and other artifacts through a defined lifecycle.

Endevor's key concepts:

  • Elements. Individual source members (programs, copybooks, JCL).
  • Environments. The promotion stages (DEV, TEST, QA, PROD).
  • Stages. Substages within each environment.
  • Systems and Subsystems. Organizational groupings of related elements.
  • Processors. Automated actions (compile, link, bind) executed during promotion.
  • Packages. Groups of changes that are promoted together as a unit.

A typical Endevor promotion sequence:

DEV/1 -> DEV/2 -> TEST/1 -> TEST/2 -> QA/1 -> PROD/1
  ^        ^         ^         ^         ^        ^
  |        |         |         |         |        |
Edit    Compile   Integration  QA      Approval  Deploy
         Unit Test   Test      Test    Gate

Git for Mainframe

Increasingly, organizations are adopting Git for mainframe source control, either replacing or supplementing Endevor. IBM Dependency Based Build (DBB) enables Git-based workflows on z/OS, and Rocket Software's Git for z/OS provides native Git capabilities on the mainframe.

The Git-based workflow for COBOL development typically involves:

  1. Clone the repository to a z/OS Unix System Services (USS) directory or a developer workstation.
  2. Branch for the feature or fix.
  3. Edit the COBOL source using an IDE (IBM IDz, Micro Focus Enterprise Developer, or VS Code with Zowe plugin).
  4. Commit changes to the local branch.
  5. Push to the remote repository.
  6. Pull request for code review.
  7. Merge after approval.
  8. Build triggered automatically by the merge.

Migration Procedures

Migration from one source control system to another requires careful planning because mainframe source control systems often store metadata (compile options, promotion history, element dependencies) that Git does not natively support.


40.11 CI/CD for COBOL

Continuous Integration and Continuous Delivery (CI/CD) for COBOL brings the same automation benefits to mainframe development that CI/CD provides for distributed systems. The pipeline automates compilation, testing, and deployment, reducing manual errors and accelerating delivery.

Jenkins on z/OS

Jenkins can orchestrate mainframe builds and deployments through plugins that interact with z/OS:

  • Zowe CLI plugin executes z/OS commands and submits JCL from Jenkins pipelines.
  • IBM z/OS Connector provides native integration with z/OS job submission and file transfer.
  • ISPW or Endevor plugins integrate with mainframe change management systems.

A Jenkins pipeline for COBOL might look like this (Jenkinsfile):

pipeline {
    agent any
    environment {
        ZOWE_PROFILE = 'mainframe-prod'
    }
    stages {
        stage('Checkout') {
            steps {
                git branch: 'main',
                    url: 'https://github.com/bank/cobol-apps.git'
            }
        }
        stage('Upload Source') {
            steps {
                sh '''
                    zowe files upload ftds \
                        "src/BANKTRAN.cbl" \
                        "BANK.SOURCE(BANKTRAN)" \
                        --profile ${ZOWE_PROFILE}
                '''
            }
        }
        stage('Compile and Link') {
            steps {
                sh '''
                    zowe jobs submit lf \
                        "BANK.JCL(COMPILE)" \
                        --wait-for-active \
                        --wait-for-output \
                        --profile ${ZOWE_PROFILE}
                '''
            }
        }
        stage('Unit Tests') {
            steps {
                sh '''
                    zowe jobs submit lf \
                        "BANK.JCL(UNITTEST)" \
                        --wait-for-output \
                        --profile ${ZOWE_PROFILE}
                '''
            }
        }
        stage('Integration Tests') {
            steps {
                sh '''
                    zowe jobs submit lf \
                        "BANK.JCL(INTTEST)" \
                        --wait-for-output \
                        --profile ${ZOWE_PROFILE}
                '''
            }
        }
        stage('Deploy to QA') {
            when {
                branch 'main'
            }
            steps {
                sh '''
                    zowe jobs submit lf \
                        "BANK.JCL(DEPLOYQA)" \
                        --wait-for-output \
                        --profile ${ZOWE_PROFILE}
                '''
            }
        }
    }
    post {
        failure {
            emailext subject: "COBOL Build Failed: ${env.JOB_NAME}",
                     body: "Build ${env.BUILD_NUMBER} failed.",
                     to: "cobol-team@bank.com"
        }
    }
}

Zowe CLI

Zowe is an open-source framework for z/OS that provides CLI and API access to mainframe services. Zowe CLI enables developers and CI/CD pipelines to interact with z/OS from any platform:

# Upload COBOL source to mainframe
zowe files upload ftds "BANKTRAN.cbl" "BANK.SOURCE(BANKTRAN)"

# Submit a compile job and wait for completion
zowe jobs submit lf "BANK.JCL(COMPILE)" --wait-for-output

# Check the job return code
zowe jobs view jss JOB12345 --step COMPILE

# Download the compiler listing
zowe files download ds "BANK.LISTING(BANKTRAN)" -f "BANKTRAN.lst"

# Submit test job
zowe jobs submit lf "BANK.JCL(UNITTEST)" --wait-for-output

# View test results
zowe files download ds "BANK.TEST.RESULTS" -f "test-results.txt"

IBM Dependency Based Build (DBB)

IBM DBB integrates with Git and provides intelligent build capabilities for z/OS applications. Its key feature is dependency analysis: rather than recompiling every program in the application, DBB determines which programs need to be recompiled based on what changed. If a copybook is modified, DBB identifies all programs that COPY that copybook and recompiles only those programs.

DBB build scripts are written in Groovy and can be customized for each project. A typical DBB build script:

// DBB build script for COBOL banking application
@groovy.transform.BaseScript com.ibm.dbb.groovy.ScriptLoader baseScript

import com.ibm.dbb.build.*
import com.ibm.dbb.dependency.*

def properties = BuildProperties.getInstance()
def buildList = argMap.buildList

buildList.each { buildFile ->
    def member = CopyToPDS.createMemberName(buildFile)

    // Copy source to PDS
    new CopyToPDS().file(new File(buildFile))
                    .dataset(properties.cobolPDS)
                    .member(member)
                    .execute()

    // Compile, precompile for DB2, and link-edit
    def compile = new MVSExec()
        .pgm("IGYCRCTL")
        .parm("LIB,RENT,OPT(FULL)")

    compile.dd(new DDStatement().name("SYSIN")
        .dsn("${properties.cobolPDS}(${member})").options("shr"))
    compile.dd(new DDStatement().name("SYSLIB")
        .dsn(properties.copybookPDS).options("shr"))
    compile.dd(new DDStatement().name("SYSLIN")
        .dsn("${properties.objectPDS}(${member})").options("shr"))

    def rc = compile.execute()

    if (rc > 4) {
        println "Compile failed for ${member} with RC=${rc}"
        System.exit(1)
    }

    // Link-edit
    def link = new MVSExec().pgm("IEWBLINK")
        .parm("LIST,XREF,MAP,RENT")
    link.dd(new DDStatement().name("SYSLIN")
        .dsn("${properties.objectPDS}(${member})").options("shr"))
    link.dd(new DDStatement().name("SYSLMOD")
        .dsn("${properties.loadPDS}(${member})").options("shr"))

    rc = link.execute()

    if (rc > 4) {
        println "Link-edit failed for ${member} with RC=${rc}"
        System.exit(1)
    }

    println "Build successful for ${member}"
}

40.12 Production Monitoring and Incident Response

Monitoring COBOL Applications

Production COBOL systems require continuous monitoring across several dimensions:

Batch job monitoring. Batch jobs are monitored for completion status (abend vs. normal termination), return codes, elapsed time (to detect performance degradation), and output file sizes (unexpected sizes may indicate data issues). Scheduling systems like TWS and Control-M provide built-in monitoring capabilities.

CICS transaction monitoring. CICS provides SMF (System Management Facility) records that track transaction response times, resource usage, abend rates, and queue depths. IBM CICS Performance Analyzer and BMC MainView provide real-time dashboards for CICS monitoring.

DB2 monitoring. DB2 performance is monitored for query elapsed time, lock contention, buffer pool hit rates, and deadlock frequency. Slow-running SQL statements (identified through DB2 trace data) often indicate COBOL program issues such as missing host variable indexing or inefficient cursor processing.

Abend Diagnosis

When a COBOL program abends (abnormally terminates) in production, the first step is to identify the abend type from the system completion code:

      *================================================================*
      * Common COBOL abend codes and their typical causes              *
      *================================================================*
      * S0C1 - Operation exception
      *   Cause: Executing non-executable storage (bad PERFORM/GO TO)
      *   Debug: Check for table subscript out of range, corrupted
      *          program storage, or calling a program not in STEPLIB
      *
      * S0C4 - Protection exception
      *   Cause: Accessing storage outside the program's address space
      *   Debug: Check for LINKAGE SECTION field used without being
      *          passed via CALL, table index overflow, or addressing
      *          mode mismatch
      *
      * S0C7 - Data exception
      *   Cause: Non-numeric data in a numeric field during arithmetic
      *   Debug: Check for uninitialized WORKING-STORAGE fields,
      *          corrupted input data, or missing INITIALIZE statements
      *   THIS IS THE MOST COMMON COBOL ABEND
      *
      * S0CB - Division by zero
      *   Cause: DIVIDE or COMPUTE with zero divisor
      *   Debug: Add zero-check before division operations
      *
      * S222 - Job cancelled by operator
      * S322 - Time limit exceeded (runaway loop)
      * S722 - Output dataset full
      * S806 - Module not found (CALL target missing from STEPLIB)
      * S913 - Security authorization failure (RACF/ACF2)

As discussed in Chapter 20 on debugging, systematic diagnosis of COBOL abends requires examination of the CEEDUMP or SYSUDUMP, which provides the program's storage contents at the time of the abend. For S0C7 abends (the most common), the dump reveals which field contained non-numeric data and which instruction attempted to use it.

Incident Response Procedures

A structured incident response process for COBOL production issues:

  1. Detection. The abend or error is detected through monitoring alerts, user reports, or automated output validation.

  2. Triage. The on-call team assesses severity. A batch abend that affects end-of-day processing is higher severity than a CICS transaction failure that affects a single user.

  3. Containment. For CICS issues, the transaction can be disabled to prevent further failures. For batch issues, the failing job and all dependent jobs are held.

  4. Diagnosis. Examine the dump, CICS auxiliary trace, DB2 diagnostic log, and application log files. Cross-reference with recent changes to identify whether a recent deployment caused the issue.

  5. Resolution. Apply the fix. For emergency fixes, this may involve overlaying the load module directly (a "zap") rather than going through the full promotion process. Emergency fixes must be formally back-tracked into the standard promotion pipeline afterward.

  6. Verification. Run targeted tests to confirm the fix resolves the issue without introducing new problems.

  7. Post-mortem. Document the root cause, the fix, and any process improvements that could prevent recurrence. Update the test suite to include a test case that would have caught this issue.

      *================================================================*
      * PROGRAM: ABNDHNDL                                              *
      * PURPOSE: COBOL application-level abend handler                 *
      * Registered via EXEC CICS HANDLE ABEND                         *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. ABNDHNDL.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-ABEND-INFO.
           05  WS-ABEND-CODE       PIC X(4).
           05  WS-ABEND-PROGRAM    PIC X(8).
           05  WS-ABEND-TRANSID    PIC X(4).
           05  WS-ABEND-DATE       PIC X(10).
           05  WS-ABEND-TIME       PIC X(8).
           05  WS-ABEND-USERID     PIC X(8).
           05  WS-ABEND-TERMINAL   PIC X(4).

       01  WS-LOG-MESSAGE          PIC X(200).
       01  WS-USER-MESSAGE         PIC X(79).

       PROCEDURE DIVISION.
       0000-HANDLE-ABEND.
      *    Gather diagnostic information
           EXEC CICS ASSIGN
               ABCODE(WS-ABEND-CODE)
               PROGRAM(WS-ABEND-PROGRAM)
               USERID(WS-ABEND-USERID)
           END-EXEC

           EXEC CICS ASSIGN
               FACILITY(WS-ABEND-TERMINAL)
           END-EXEC

           MOVE FUNCTION CURRENT-DATE(1:10)
               TO WS-ABEND-DATE
           MOVE FUNCTION CURRENT-DATE(11:8)
               TO WS-ABEND-TIME

      *    Log the abend details to TD queue for monitoring
           STRING 'ABEND ' WS-ABEND-CODE
                  ' IN PROGRAM ' WS-ABEND-PROGRAM
                  ' TRAN ' WS-ABEND-TRANSID
                  ' USER ' WS-ABEND-USERID
                  ' TERM ' WS-ABEND-TERMINAL
                  ' AT ' WS-ABEND-DATE ' ' WS-ABEND-TIME
               DELIMITED SIZE INTO WS-LOG-MESSAGE
           END-STRING

           EXEC CICS WRITEQ TD
               QUEUE('CSML')
               FROM(WS-LOG-MESSAGE)
               LENGTH(LENGTH OF WS-LOG-MESSAGE)
           END-EXEC

      *    Display user-friendly message
           STRING 'A system error has occurred. '
                  'Reference: ' WS-ABEND-CODE '-'
                  WS-ABEND-PROGRAM '. '
                  'Please contact the help desk.'
               DELIMITED SIZE INTO WS-USER-MESSAGE
           END-STRING

           EXEC CICS SEND TEXT
               FROM(WS-USER-MESSAGE)
               LENGTH(LENGTH OF WS-USER-MESSAGE)
               ERASE
           END-EXEC

           EXEC CICS RETURN END-EXEC
           .

Operational Runbooks

For each production COBOL application, maintain a runbook that documents:

  • Normal operating procedures (job schedule, expected completion times, expected return codes)
  • Known issues and their workarounds
  • Restart and recovery procedures for each batch job
  • Escalation contacts for each application area
  • Data correction procedures for common data issues
  • Emergency fix procedures including approval chain

The runbook is a living document that is updated after every incident. It is the primary resource for on-call support staff who may not be familiar with the specific application.


Summary

Testing, quality assurance, and deployment for COBOL systems require a disciplined approach that combines modern practices with mainframe-specific knowledge. Unit testing with frameworks like COBOL-Check brings the benefits of test-driven development to COBOL programs. JCL-based test automation provides the infrastructure for repeatable regression testing. Static analysis and code coverage tools identify gaps in test coverage and potential defects before they reach production.

Deployment on z/OS involves multiple coordinated steps: compilation, link-editing, DB2 binding, CICS resource definition, and controlled promotion through environments. Change management systems like Endevor and Git provide the audit trail and approval gates required in regulated industries. CI/CD pipelines using Jenkins, Zowe CLI, and IBM DBB automate the build and deployment process, reducing manual errors and accelerating delivery.

Production monitoring and incident response close the feedback loop, ensuring that issues are detected quickly, diagnosed accurately, and resolved with minimal business impact. Every incident becomes an input to the testing process, as new test cases are created to prevent recurrence.

The techniques presented in this chapter, combined with the debugging skills covered in Chapter 20, provide a complete quality assurance framework for COBOL systems. Whether maintaining a legacy system or developing new COBOL applications, these practices ensure that the code running the world's financial infrastructure remains reliable, correct, and trustworthy.