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