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.