This tutorial will guide you step-by-step through the creation of a small test suite and the implementation of a simple number-summing program. After reading this tutorial, you will know how to write and run RPGUnit tests. You will also learn the basics of TDD .
Let's assume we want to write a simple program (SUMBYCUST) that sums order amounts by customers. There will be an order file (ORDERS) and a customer report file (CUSTSUMS).
Start by creating a source file.
===> CRTSRCPF FILE(RUTUTORIAL) RCDLEN(112) TEXT('RPGUnit - Tutorial.')
PF source member called ORDERS in RUTUTORIAL, with the following description.
R ORDER ORDERID 7P 0 CUSTID 4A ORDERAMT 15P 2 K ORDERID
PF source member called CUSTSUMS, with the following description.
R CUSTSUM CUSTID 4A SUM 17P 2 K CUSTID
===> CRTPF FILE(ORDERS) SRCFILE(RUTUTORIAL) ===> CRTPF FILE(CUSTSUMS) SRCFILE(RUTUTORIAL)
Before we write any code that is going to last, we want to be sure the testing infrastructure is working correctly. For this, we will write a dummy test. First create an RPGLE source member SUMBYCUSTT in the RUTUTORIAL source file.
H NoMain
//---------------------------------------------------------
// Prototypes
//---------------------------------------------------------
/copy RPGUNIT1,TESTCASE
// Test case prototypes.
Dtest_failure pr
//---------------------------------------------------------
// Test Case Definitions
//---------------------------------------------------------
Ptest_failure b Export
Dtest_failure pi
/free
iEqual( 5 : 2+2 );
/end-free
P e
We compile the dummy test.
===> RUCRTTST TSTPGM(SUMBYCUSTT) SRCFILE(RUTUTORIAL)
===> RUCALLTST SUMBYCUSTT
If you do not get this failure message, something is wrong with your RPGUnit framework installation. Check that the RPGUnit objects are in your library list (e.g., WRKOBJ RU*) and try to run the framework's self-test feature RPGUNITT1/MKRPGUNITT.
SUMBYCUSTT to the following.
H NoMain
//---------------------------------------------------------
// Files
//---------------------------------------------------------
FCUSTSUMS IF E DISK UsrOpn
FORDERS O E DISK UsrOpn
//---------------------------------------------------------
// Prototypes
//---------------------------------------------------------
/copy RPGUNIT1,TESTCASE
// Program under test.
D SumByCust pr ExtPgm('SUMBYCUST')
// Test case prototypes.
Dtest_one_customer_one_order...
D pr
//---------------------------------------------------------
// Test Case Definitions
//---------------------------------------------------------
Ptest_one_customer_one_order...
P b Export
Dtest_one_customer_one_order...
D pi
/free
// Setup.
clrpfm('ORDERS');
clrpfm('CUSTSUMS');
open ORDERS;
ORDERID = 1;
CUSTID = 'A001';
ORDERAMT = 1000;
write ORDER;
close ORDERS;
// Run.
SumByCust();
// Check.
open CUSTSUMS;
read CUSTSUM;
assert( not %eof : 'File CUSTSUMS should not be empty' );
aEqual( 'A001' : CUSTID );
iEqual( 1000 : SUM );
close CUSTSUMS;
/end-free
P e
===> RUCRTTST TSTPGM(SUMBYCUSTT) SRCFILE(RUTUTORIAL)
===> RUCALLTST SUMBYCUSTT
RPGUNIT spool file (User Data SUMBYCUSTT).
*** Tests from SUMBYCUSTT *** -----------------------
SUMBYCUST program yet. Let's create an RPGLE source member called SUMBYCUST with the following content.
//---------------------------------------------------------
// Main Procedure
//---------------------------------------------------------
/free
*inlr = *on;
/end-free
===> CRTBNDRPG PGM(SUMBYCUST) SRCFILE(RUTUTORIAL)
===> RUCALLTST SUMBYCUSTT
RPGUNIT spool file, we can find some details about the failure.
*** Tests from SUMBYCUSTT *** -----------------------
SUMBYCUST, the CUSTSUMS file, is empty.
SUMBYCUST program a little bit. The new code is highlighted.
//--------------------------------------------------------- // Files //--------------------------------------------------------- FCUSTSUMS O E DISK //--------------------------------------------------------- // Main Procedure //--------------------------------------------------------- /free write CUSTSUM; *inlr = *on; /end-free
SUMBYCUST and running the tests suite once more, we get the following message.
===> CRTBNDRPG PGM(SUMBYCUST) SRCFILE(RUTUTORIAL)
===> RUCALLTST SUMBYCUSTT
DSPSPLF FILE(RPGUNIT) SPLNBR(*LAST)).
*** Tests from SUMBYCUSTT *** -----------------------
SUMBYCUSTT, we can see that the field CUSTID is wrong. It is blank, though it should contain the customer ID "A001".
//---------------------------------------------------------
// Main Procedure
//---------------------------------------------------------
/free
CUSTID = 'A001';
SUM = 1000;
write CUSTSUM;
*inlr = *on;
/end-free
//---------------------------------------------------------
// Files
//---------------------------------------------------------
FORDERS IF E DISK
FCUSTSUMS O E DISK
//---------------------------------------------------------
// Main Procedure
//---------------------------------------------------------
/free
read ORDER;
SUM = ORDERAMT;
write CUSTSUM;
*inlr = *on;
/end-free
// Test case prototypes.
Dtest_one_customer_one_order...
D pr
Dtest_one_customer_two_orders...
D pr
Ptest_one_customer_two_orders...
P b Export
Dtest_one_customer_two_orders...
D pi
/free
// Setup.
clrpfm('ORDERS');
clrpfm('CUSTSUMS');
open ORDERS;
ORDERID = 1;
CUSTID = 'A001';
ORDERAMT = 1000;
write ORDER;
ORDERID = 2;
CUSTID = 'A001';
ORDERAMT = 2000;
write ORDER;
close ORDERS;
// Run.
SumByCust();
// Check.
open CUSTSUMS;
read CUSTSUM;
assert( not %eof : 'File CUSTSUMS should not be empty' );
aEqual( 'A001' : CUSTID );
iEqual( 1000+2000 : SUM );
close CUSTSUMS;
/end-free
P e
===> RUCRTTST TSTPGM(SUMBYCUSTT) SRCFILE(RUTUTORIAL)
===> RUCALLTST SUMBYCUSTT
RPGUNIT spool file contains the following information.
*** Tests from SUMBYCUSTT *** -----------------------
SUMBYCUST program.
read ORDER;
dow not %eof;
SUM += ORDERAMT;
read ORDER;
enddo;
write CUSTSUM;
===> CRTBNDRPG PGM(SUMBYCUST) SRCFILE(RUTUTORIAL)
===> RUCALLTST SUMBYCUSTT
setUp to put the shared code. The RPGUnit framework will call this procedure once before running each test procedure. Let's factor the duplicated code of SUMBYCUSTT.
// Test case prototypes. DsetUp pr Dtest_one_customer_one_order... D pr Dtest_one_customer_two_orders... D pr //--------------------------------------------------------- // Test Case Definitions //--------------------------------------------------------- PsetUp b Export DsetUp pi /free clrpfm('ORDERS'); clrpfm('CUSTSUMS'); open ORDERS; ORDERID = 1; CUSTID = 'A001'; ORDERAMT = 1000; write ORDER; close ORDERS; /end-free P e Ptest_one_customer_one_order... P b Export Dtest_one_customer_one_order... D pi /free // Setup. // Run. SumByCust(); // Check. open CUSTSUMS; read CUSTSUM; assert( not %eof : 'File CUSTSUMS should not be empty' ); aEqual( 'A001' : CUSTID ); iEqual( 1000 : SUM ); close CUSTSUMS; /end-free P e Ptest_one_customer_two_orders... P b Export Dtest_one_customer_two_orders... D pi /free // Setup. open ORDERS; ORDERID = 2; CUSTID = 'A001'; ORDERAMT = 2000; write ORDER; close ORDERS; // Run. SumByCust(); // Check. open CUSTSUMS; read CUSTSUM; assert( not %eof : 'File CUSTSUMS should not be empty' ); aEqual( 'A001' : CUSTID ); iEqual( 1000+2000 : SUM ); close CUSTSUMS; /end-free P e
// Test case prototypes.
DsetUp pr
Dtest_one_customer_one_order...
D pr
Dtest_one_customer_two_orders...
D pr
Dtest_two_customers_one_order_each...
D pr
Ptest_two_customers_one_order_each...
P b Export
Dtest_two_customers_one_order_each...
D pi
/free
// Setup.
open ORDERS;
ORDERID = 2;
CUSTID = 'B002';
ORDERAMT = 2000;
write ORDER;
close ORDERS;
// Run.
SumByCust();
// Check.
open CUSTSUMS;
// First customer.
read CUSTSUM;
assert( not %eof : 'File CUSTSUMS should not be empty' );
aEqual( 'A001' : CUSTID );
iEqual( 1000 : SUM );
// Second customer.
read CUSTSUM;
assert( not %eof : 'File CUSTSUMS should have two records' );
aEqual( 'B002' : CUSTID );
iEqual( 2000 : SUM );
close CUSTSUMS;
/end-free
P e
SUMBYCUST) so that it handles customer breaks.
//--------------------------------------------------------- // Global Variables //--------------------------------------------------------- // Current order. D orderDS ds LikeRec(ORDER) // Current customer sum. D custSumDS ds LikeRec(CUSTSUM:*output) // Customer break indicator. D custBreak s n //--------------------------------------------------------- // Main Procedure //--------------------------------------------------------- /free read ORDER orderDS; clear custSumDS; custSumDS.CUSTID = orderDS.CUSTID; dow not %eof; custBreak = (custSumDS.CUSTID <> orderDS.CUSTID); if custBreak; write CUSTSUM custSumDS; clear custSumDS; custSumDS.CUSTID = orderDS.CUSTID; endif; custSumDS.SUM += orderDS.ORDERAMT; read ORDER orderDS; enddo; write CUSTSUM custSumDS; *inlr = *on; /end-free
SUMBYCUSTTT.
Ptest_two_customers_with_orders_not_grouped...
P b Export
Dtest_two_customers_with_orders_not_grouped...
D pi
/free
// Setup.
open ORDERS;
ORDERID = 2;
CUSTID = 'B002';
ORDERAMT = 2000;
write ORDER;
ORDERID = 3;
CUSTID = 'A001'; // Back to the first customer.
ORDERAMT = 3000;
write ORDER;
close ORDERS;
// Run.
SumByCust();
// Check.
open CUSTSUMS;
read CUSTSUM;
assert( not %eof : 'File CUSTSUMS should not be empty' );
aEqual( 'A001' : CUSTID );
iEqual( 1000+3000 : SUM );
// No test on second customer.
close CUSTSUMS;
/end-free
P e
ORDERS2.
R ORDER PFILE(ORDERS)
K CUSTID
K ORDERID
SUMBYCUST so that it uses ORDERS2 instead of ORDERS.
//---------------------------------------------------------
// Files
//---------------------------------------------------------
FORDERS2 IF E K DISK
FCUSTSUMS O E DISK
For your convenience, here is the full source for the test program and the program under test.
H NoMain
//---------------------------------------------------------
// Files
//---------------------------------------------------------
FCUSTSUMS IF E DISK UsrOpn
FORDERS O E DISK UsrOpn
//---------------------------------------------------------
// Prototypes
//---------------------------------------------------------
/copy RPGUNIT1,TESTCASE
// Program under test.
D SumByCust pr ExtPgm('SUMBYCUST')
// Test case prototypes.
DsetUp pr
Dtest_one_customer_one_order...
D pr
Dtest_one_customer_two_orders...
D pr
Dtest_two_customers_one_order_each...
D pr
Dtest_two_customers_with_orders_not_grouped...
D pr
//---------------------------------------------------------
// Test Case Definitions
//---------------------------------------------------------
PsetUp b Export
DsetUp pi
/free
clrpfm('ORDERS');
clrpfm('CUSTSUMS');
open ORDERS;
ORDERID = 1;
CUSTID = 'A001';
ORDERAMT = 1000;
write ORDER;
close ORDERS;
/end-free
P e
Ptest_one_customer_one_order...
P b Export
Dtest_one_customer_one_order...
D pi
/free
// Setup.
// Run.
SumByCust();
// Check.
open CUSTSUMS;
read CUSTSUM;
assert( not %eof : 'File CUSTSUMS should not be empty' );
aEqual( 'A001' : CUSTID );
iEqual( 1000 : SUM );
close CUSTSUMS;
/end-free
P e
Ptest_one_customer_two_orders...
P b Export
Dtest_one_customer_two_orders...
D pi
/free
// Setup.
open ORDERS;
ORDERID = 2;
CUSTID = 'A001';
ORDERAMT = 2000;
write ORDER;
close ORDERS;
// Run.
SumByCust();
// Check.
open CUSTSUMS;
read CUSTSUM;
assert( not %eof : 'File CUSTSUMS should not be empty' );
aEqual( 'A001' : CUSTID );
iEqual( 1000+2000 : SUM );
close CUSTSUMS;
/end-free
P e
Ptest_two_customers_one_order_each...
P b Export
Dtest_two_customers_one_order_each...
D pi
/free
// Setup.
open ORDERS;
ORDERID = 2;
CUSTID = 'B002';
ORDERAMT = 2000;
write ORDER;
close ORDERS;
// Run.
SumByCust();
// Check.
open CUSTSUMS;
// First customer.
read CUSTSUM;
assert( not %eof : 'File CUSTSUMS should not be empty' );
aEqual( 'A001' : CUSTID );
iEqual( 1000 : SUM );
// Second customer.
read CUSTSUM;
assert( not %eof : 'File CUSTSUMS should have two records' );
aEqual( 'B002' : CUSTID );
iEqual( 2000 : SUM );
close CUSTSUMS;
/end-free
P e
Ptest_two_customers_with_orders_not_grouped...
P b Export
Dtest_two_customers_with_orders_not_grouped...
D pi
/free
// Setup.
open ORDERS;
ORDERID = 2;
CUSTID = 'B002';
ORDERAMT = 2000;
write ORDER;
ORDERID = 3;
CUSTID = 'A001';
ORDERAMT = 3000;
write ORDER;
close ORDERS;
// Run.
SumByCust();
// Check.
open CUSTSUMS;
read CUSTSUM;
assert( not %eof : 'File CUSTSUMS should not be empty' );
aEqual( 'A001' : CUSTID );
iEqual( 1000+3000 : SUM );
// No test on second customer.
close CUSTSUMS;
/end-free
P e
//---------------------------------------------------------
// Files
//---------------------------------------------------------
FORDERS2 IF E K DISK
FCUSTSUMS O E DISK
//---------------------------------------------------------
// Global Variables
//---------------------------------------------------------
// Current order.
D orderDS ds LikeRec(ORDER)
// Current customer sum.
D custSumDS ds LikeRec(CUSTSUM:*output)
// Customer break indicator.
D custBreak s n
//---------------------------------------------------------
// Main Procedure
//---------------------------------------------------------
/free
read ORDER orderDS;
clear custSumDS;
custSumDS.CUSTID = orderDS.CUSTID;
dow not %eof;
custBreak = (custSumDS.CUSTID <> orderDS.CUSTID);
if custBreak;
write CUSTSUM custSumDS;
clear custSumDS;
custSumDS.CUSTID = orderDS.CUSTID;
endif;
custSumDS.SUM += orderDS.ORDERAMT;
read ORDER orderDS;
enddo;
write CUSTSUM custSumDS;
*inlr = *on;
/end-free
If you are not comfortable with free format, here is the equivalent in fixed-position style.
H NoMain
//---------------------------------------------------------
// Files
//---------------------------------------------------------
FCUSTSUMS IF E DISK UsrOpn
FORDERS O E DISK UsrOpn
//---------------------------------------------------------
// Prototypes
//---------------------------------------------------------
/copy RPGUNIT1,TESTCASE
// Program under test.
D SumByCust pr ExtPgm('SUMBYCUST')
// Test case prototypes.
DsetUp pr
Dtest_one_customer_one_order...
D pr
Dtest_one_customer_two_orders...
D pr
Dtest_two_customers_one_order_each...
D pr
Dtest_two_customers_with_orders_not_grouped...
D pr
//---------------------------------------------------------
// Test Case Definitions
//---------------------------------------------------------
PsetUp b Export
DsetUp pi
C callp clrpfm('ORDERS')
C callp clrpfm('CUSTSUMS')
C open ORDERS
C eval ORDERID = 1
C eval CUSTID = 'A001'
C eval ORDERAMT = 1000
C write ORDER
C close ORDERS
P e
Ptest_one_customer_one_order...
P b Export
Dtest_one_customer_one_order...
D pi
// Setup.
// Run.
C callp SumByCust()
// Check.
C open CUSTSUMS
C read CUSTSUM
C callp assert( not %eof
C : 'File CUSTSUMS should not be empty' )
C callp aEqual( 'A001' : CUSTID )
C callp iEqual( 1000 : SUM )
C close CUSTSUMS
P e
Ptest_one_customer_two_orders...
P b Export
Dtest_one_customer_two_orders...
D pi
// Setup.
C open ORDERS
C eval ORDERID = 2
C eval CUSTID = 'A001'
C eval ORDERAMT = 2000
C write ORDER
C close ORDERS
// Run.
C callp SumByCust()
// Check.
C open CUSTSUMS
C read CUSTSUM
C callp assert( not %eof
C : 'File CUSTSUMS should not be empty' )
C callp aEqual( 'A001' : CUSTID )
C callp iEqual( 1000+2000 : SUM )
C close CUSTSUMS
P e
Ptest_two_customers_one_order_each...
P b Export
Dtest_two_customers_one_order_each...
D pi
// Setup.
C open ORDERS
C eval ORDERID = 2
C eval CUSTID = 'B002'
C eval ORDERAMT = 2000
C write ORDER
C close ORDERS
// Run.
C callp SumByCust()
// Check.
C open CUSTSUMS
// First customer.
C read CUSTSUM
C callp assert( not %eof
C : 'File CUSTSUMS should not be empty' )
C callp aEqual( 'A001' : CUSTID )
C callp iEqual( 1000 : SUM )
// Second customer.
C read CUSTSUM
C callp assert( not %eof
C : 'File CUSTSUMS should have two records' )
C callp aEqual( 'B002' : CUSTID )
C callp iEqual( 2000 : SUM )
C close CUSTSUMS
P e
Ptest_two_customers_with_orders_not_grouped...
P b Export
Dtest_two_customers_with_orders_not_grouped...
D pi
// Setup.
C open ORDERS
C eval ORDERID = 2
C eval CUSTID = 'B002'
C eval ORDERAMT = 2000
C write ORDER
C eval ORDERID = 3
C eval CUSTID = 'A001' Back to 1st customer
C eval ORDERAMT = 3000
C write ORDER
C close ORDERS
// Run.
C callp SumByCust()
// Check.
C open CUSTSUMS
C read CUSTSUM
C callp assert( not %eof
C : 'File CUSTSUMS should not be empty' )
C callp aEqual( 'A001' : CUSTID )
C callp iEqual( 1000+3000 : SUM )
// No test on second customer.
C close CUSTSUMS
P e
//--------------------------------------------------------- // Files //--------------------------------------------------------- FORDERS2 IF E K DISK FCUSTSUMS O E DISK //--------------------------------------------------------- // Global Variables //--------------------------------------------------------- // Current order. D orderDS ds LikeRec(ORDER) // Current customer sum. D custSumDS ds LikeRec(CUSTSUM:*output) //--------------------------------------------------------- // Main Procedure //--------------------------------------------------------- C READ ORDER orderDS C CLEAR custSumDS C EVAL custSumDS.CUSTID = orderDS.CUSTID C DOW not %eof C IF custSumDS.CUSTID <> orderDS.CUSTID Customer break C WRITE CUSTSUM custSumDS C CLEAR custSumDS C EVAL custSumDS.CUSTID = orderDS.CUSTID C ENDIF C EVAL custSumDS.SUM += orderDS.ORDERAMT C READ ORDER orderDS C ENDDO C WRITE CUSTSUM custSumDS C SETON LR