This example shows a complete end to end test of an ATM Banking Application. By Writing a simple Groovy script with less than 175 lines of code, we will execute a complex end to end work flow that will accomplish the following:
• Populate an empty database with programmed and conditioned data
• Simulated a complex end-to-end workflow that successfully generated:
• 1,000 Branches
• 40,000 Visa Debit Cards
• 10,000 Users
• 20,000 Customer Accounts
• 10,000 Checking
• 10,000 Savings
3. Make thousands of deposits, transfers, and withdrawals to test the ATM's workflows
In total, this test plan will simulate 169,943 combined withdrawal, deposit, and transfer transactions.
Scenarios and Configuration Files
The following sections provide a detailed break down of the Scenarios, configuration files and source code used to implement the end to end solution:
Scenarios
Nine Scenarios were used to accomplish all of the tasks in the end to end test.
- AccountDepositRestScenario.grs - Executes bank deposits over REST
- AccountTransferRestScenario.grs - Executes bank transfers over REST
- AccountTypeRestScenario.grs - Inserts new AccountTypes into the database over REST
- AccountWithdrawalRestScenario.grs - Executes back withdrawals over REST
- BranchRestScenario.grs - Inserts new Branches into the database over REST
- CardPoolRestScenario.grs - Inserts new Credit Cards into the CardPool table in the database over REST
- CardTypeRestScenario.grs - Inserts new Credit Card Types into the database over REST
- CustomerLevelRestScenario.grs - Inserts new Customer Levels into the database over REST
- OpenAccountRestScenario.grs - Opens user Checking and Savings accounts over REST
Configuration Files
Each configuration file is read by its respective Scenario to retrieve the URL for making a REST web service request. The URL was placed in the configuration file so that the URL could be modified to test on different testing environments where the URLs may be necessarily different.
- AccountDeposit.config
- AccountTransfer.config
- AccountType.config
- AccountWithdrawal.config
- Branch.config
- CardPool.config
- CardType.config
- CustomerLevel.config
- OpenAccount.config
AccountDeposit.config
<config> <requestURL>http://localhost:8080/GrailsBankingDemo/rest/makeDeposit</requestURL> </config>
AccountTransfer.config
<config> <requestURL>http://localhost:8080/GrailsBankingDemo/rest/makeTransfer</requestURL> </config>
AccountType.config
<config> <requestURL>http://localhost:8080/GrailsBankingDemo/rest/createAccountType</requestURL> </config>
AccountWithdrawal.config
<config> <requestURL>http://localhost:8080/GrailsBankingDemo/rest/makeWithdrawal</requestURL> </config>
Branch.config
<config> <requestURL>http://localhost:8080/GrailsBankingDemo/rest/createBranch</requestURL> </config>
CardPool.config
<config> <requestURL>http://localhost:8080/GrailsBankingDemo/rest/createCardPool</requestURL> </config>
CardType.config
<config> <requestURL>http://localhost:8080/GrailsBankingDemo/rest/createCardType</requestURL> </config>
CustomerLevel.config
<config> <requestURL>http://localhost:8080/GrailsBankingDemo/rest/createCustomerLevel</requestURL> </config>
OpenAccount.config
<config> <requestURL>http://localhost:8080/GrailsBankingDemo/rest/openAccount</requestURL> </config>
The Groovy Script
This section details the 175 line Groovy script that runs the end to end test/workflow. Here, the script is broken apart into different sections and each section is given a brief explanation.
Global Variables
def fileSep = System.getProperty("file.separator") def userHome = System.getProperty("user.home") def scenarioPath = "${userHome}${fileSep}Downloads${fileSep}" def branchCount = 1000 def cardPoolCount = 40000 def customerCount = 10000
Helper Methods
The helper methods do the following:
- elapsedTime - Calculates the time that has elapsed between a given start time and the current time.
- getConnection - connects to a local MySQL database via JDBC
- emptyTables - connects the genrocket_bank database and deletes all data from all tables
- getAverageBalance - calculates the average balance for checking or savings account
def static elapsedTime(long start) { long mills = System.currentTimeMillis() - start // Get elapsed time in milliseconds long sec = mills / 1000F // Get elapsed time in seconds long min = sec / 60 // Get elapsed time in minutes long hour = min / 60 // Get elapsed time in hours long day = hour / 24 // Get elapsed time in days if (day > 0) return day + "d:" + hour % 24 + "h:" + min % 60 + "m:" + sec % 60 + "s" if (hour > 0) return hour % 24 + "h:" + min % 60 + "m:" + sec % 60 + "s" return min % 60 + "m:" + sec % 60 + "s" } def static getConnection() { return Sql.newInstance("jdbc:mysql://localhost:3306/genrocket_bank", "root", "admin", "com.mysql.jdbc.Driver") } def static emptyTables() { def sql = getConnection() def tables = [ 'card', 'card_type', 'customer', 'customer_level', 'transaction', 'transaction_type', 'account', 'account_type', 'branch', 'user', 'card_pool' ] tables.each { table -> println "delete from ${table}..." sql.execute("delete from ${table}".toString()) } } def static getAverageBalance(String accountType) { def sql = getConnection() def query = """select round(avg(aco.balance)) as avg from account aco join account_type act on act.id = aco.account_type_id where act.name = '${accountType}'""" def row = sql.rows(query.toString()) return row[0]['avg'] }
Initializing The Database
The simulation starts out with an empty database. Using the GenRocket API, the initialization method will directly load and execute four GenRocket Scenarios to populate the AccountType, CardTypes, CustomerLevels, TransactionTypes. It will also populate Branches and CardPool, by first dynamically setting the Branch loopCount to 1,000 and the cardPool loopCount to 40,000.
def loadTestData() { def scenarios = [ 'AccountTypeRestScenario', 'CardTypeRestScenario', 'CustomerLevelRestScenario', 'TransactionTypeRestScenario', ] EngineAPI api = new EngineManual() try { // Loop over the list of Scenarios, load and run. scenarios.each { scenario -> api.scenarioLoad("${scenarioPath}${scenario}.grs") api.scenarioRun() } // Set the branch loopCount to 1,000 before running. api.scenarioLoad("${scenarioPath}BranchRestScenario.grs") api.domainSetLoopCount('Branch', branchCount.toString()) api.scenarioRun() // Set the cardPool loopCount to 40,000 before running. api.scenarioLoad("${scenarioPath}CardPoolRestScenario.grs") api.domainSetLoopCount('CardPool', cardPoolCount.toString()) api.scenarioRun() println('Load Data All Done!') } catch (GenRocketException e) { println(e.getMessage()) } }
Simulation
For the running of the full end to end test/workflow, the method sumulationOne(), contains the business logic that follows the flow of the Activity Diagram shown below. The code logic will make decisions when withdrawals, deposits or transfers are made and dynamically load, modify and run the appropriate GenRocket Scenario that will immediately start generating the appropriate transactions in real time using the GenRocket RealtimeTestReceiver.
Simulation Activity Diagram
Simulation Pseudo Code
If you're not familiar with reading Activity Diagrams, here's pseudo code that explains the same workflow.
1: execute 10,000 withdraws from checking, 1 for each customer
check average checking balance across all customers
if (average checking > $500)
goto 1:
for each customer
transfer $750 from savings to checking
check average savings balance across all customers
if (average savings > $1500)
goto 1:
for each customer
deposit $2000 into checking, transfer $1500 from checking to savings
end simulation
SimulationOne Method
public simulationOne() { def startTime = System.currentTimeMillis() emptyTables() loadTestData() EngineAPI api = new EngineManual() Boolean done = false Float avg = 0 // Open all checking accounts with a balance of 1000.00 // Open all savings accounts with a balance of 3000.00 api.scenarioLoad("${scenarioPath}OpenAccountRestScenario.grs") api.domainSetLoopCount('OpenAccount', customerCount.toString()) api.generatorParameterSet('OpenAccount.branchCode', 0, 'var2', (branchCount - 1).toString()) api.generatorParameterSet('OpenAccount.checking', 0, 'map', [Silver: '1000.00', Gold: '1000.00', Platinum: '1000.00']) api.generatorParameterSet('OpenAccount.savings', 0, 'map', [Silver: '3000.00', Gold: '3000.00', Platinum: '3000.00']) api.scenarioRun() while (!done) { try { // Withdrawal 100.00, 150.00, 200.00 or 250.00 from checking api.scenarioLoad("${scenarioPath}AccountWithdrawalRestScenario.grs") api.receiverParameterSet('User','RealtimeTestReceiver','threadCount', 10.toString()) api.generatorParameterSet('User.amount', 0, 'list', ['100', '150', '200', '250']) api.domainSetLoopCount('User', customerCount.toString()) api.scenarioRun() avg = getAverageBalance('Checking') if (avg <= 500) { // Switch fromCardNumber to Savings and toCardNumber to Checking // Transfer 750 from savings into checking api.scenarioLoad("${scenarioPath}AccountTransferRestScenario.grs") api.receiverParameterSet('User','RealtimeTestReceiver','threadCount', 10.toString()) api.generatorParameterSet('User.fromCardNumber', 0, 'equation', 'var1 + var1') api.generatorParameterSet('User.toCardNumber', 0, 'equation', 'var1 + var1 - 1') api.generatorParameterSet('User.amount', 0, 'value', 750.00.toString()) api.domainSetLoopCount('User', customerCount.toString()) api.scenarioRun() } avg = getAverageBalance('Savings') if (avg <= 1500) { // Deposit 2000.00 into checking api.scenarioLoad("${scenarioPath}AccountDepositRestScenario.grs") api.receiverParameterSet('User','RealtimeTestReceiver','threadCount', 10.toString()) api.domainSetLoopCount('User', customerCount.toString()) api.generatorParameterSet('User.amount', 0, 'value', 2000.00.toString()) api.scenarioRun() // Transfer 1500.00 from checking to savings api.scenarioLoad("${scenarioPath}AccountTransferRestScenario.grs") api.receiverParameterSet('User','RealtimeTestReceiver','threadCount', 10.toString()) api.generatorParameterSet('User.amount', 0, 'value', 1500.00.toString()) api.domainSetLoopCount('User', customerCount.toString()) api.scenarioRun() // End the simulation done = true } } catch (GenRocketException e) { println(e.getMessage()) } } def totalTime = ElapsedTime.elapsedTime(startTime) println("Simulation Complete! - Total Time: ${totalTime}") }
Details on How We Modified the AccountTransferRestScenario in Realtime
The video below (1 min 26 secs) will show how, in the simulation, we were able to dynamically modify the AccountTransferRestScenario, to switch transferring funds from a user's checking to savings, to transferring funds from a user's savings to checking; thus showing the power and flexibility one can have manipulating GenRocket Scenarios in realtime.