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...
In This Chapter
- Part VIII - Modern COBOL and System Evolution
- 40.1 Testing Fundamentals for COBOL
- 40.2 COBOL Unit Testing Frameworks
- 40.3 Writing Testable COBOL
- 40.4 Test Data Management
- 40.5 JCL-Based Test Automation
- 40.6 Regression Testing Strategies
- 40.7 Code Coverage Analysis
- 40.8 Static Analysis
- 40.9 Deployment on z/OS
- 40.10 Change Management
- 40.11 CI/CD for COBOL
- 40.12 Production Monitoring and Incident Response
- Summary
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:
- Reads the COBOL source program and the test suite file.
- Inserts test code at the appropriate locations within each tested paragraph.
- Generates a merged COBOL program that includes both production and test logic.
- Compiles and executes the merged program.
- 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.
Compilation and Link-Edit
COBOL programs are compiled and link-edited into load modules that reside in partitioned datasets (PDSEs). The compilation process includes:
- 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.
- Compilation. The COBOL compiler translates the source code into object code.
- 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:
- Development (DEV). Programmers compile and test their changes.
- Unit Test (UT). Unit tests are executed against the compiled program.
- Integration Test (IT). The program is tested with other programs in the application.
- Quality Assurance (QA). Full regression testing and user acceptance testing.
- Pre-Production (PPROD). Final verification in a production-like environment.
- 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:
- Clone the repository to a z/OS Unix System Services (USS) directory or a developer workstation.
- Branch for the feature or fix.
- Edit the COBOL source using an IDE (IBM IDz, Micro Focus Enterprise Developer, or VS Code with Zowe plugin).
- Commit changes to the local branch.
- Push to the remote repository.
- Pull request for code review.
- Merge after approval.
- 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:
-
Detection. The abend or error is detected through monitoring alerts, user reports, or automated output validation.
-
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.
-
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.
-
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.
-
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.
-
Verification. Run targeted tests to confirm the fix resolves the issue without introducing new problems.
-
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.