Testing UML Model: ATM Example
In this example, we define the structure and behavior of an automated teller machine (ATM) in a UML model and validate the model’s functional correctness by defining and executing test cases.
For withdrawing money from an ATM system, the client puts his card into the ATM and enters the PIN of the card, as well as the amount of money that should be withdrawn from the client’s bank account. The ATM system then checks whether the entered PIN is valid and whether the withdrawal is possible, i.e., whether the balance of the client’s bank account exceeds the amount of money to be withdrawn. If the withdrawal is possible, the balance of the account is accordingly updated.
The class diagram defining the structure of the ATM system is depicted in Figure 1. It defines several classes: Card, Account, ATM, Transaction, Record, Deposit, and Withdrawal. The class Card owns two Integer attributes number and pin and is associated with the class Account, which owns two Integer attributes number and balance. The described withdrawal functionality is defined by the operation withdraw() of the class ATM, the operation validatePin() of the class Card, as well as the operation makeWithdrawal() of the class Account. The activities defining the behavior of these three operations are depicted in Figure 2 (ATM::withdraw()), Figure 3 (Card::validatePin()), and Figure 4 (Account::makeWithdrawal()).
The activity ATM.Withdraw (Figure 2) defining the behavior of the operation ATM::withdraw() gets as input the card, the pin, and the amount of money to be withdrawn entered by the client. It first starts a new transaction by calling the operation ATM::startTransaction(), then calls the operation Card::validatePin() for validating the pin entered by the client. If the pin is valid, the account associated with the provided card is obtained, and the operation Account::makeWithdrawal() is called to perform the withdrawal of the provided amount of money from the account. Finally, the operation ATM::endTransaction() is called to end the current transaction, and add it to the completed transactions of the ATM.
The activity Card.ValidatePin (Figure 3) defines the behavior of operation Card::validatePin() and checks whether the pin stored on the card matches the provided pin. If this is the case, true is provided as output, otherwise false is provided. If the call of the operation Card::validatePin() provides false as output, the activity ATM.Withdraw also provides false as output indicating an unsuccessful withdrawal. Otherwise, the withdrawal is carried out by calling the operation Account::makeWithdrawal() (Figure 2 action makeWithdrawal) for the account associated with the provided card whose behavior is defined by the activity Account.MakeWithdrawal.
The activity Account.MakeWithdrawal (Figure 4) first checks whether the account’s balance exceeds the amount of money to be withdrawn. If this is the case, the balance is reduced by this amount, a new withdrawal record is created (action createNewWithdrawal), and the activity provides true as output indicating the successful update of the account’s balance. Otherwise false is provided as output. If the activity Account.MakeWithdrawal provides true as output, the activity ATM.Withdraw (Figure 2) also provides true as output indicating the successful withdrawal, otherwise false is provided.
To validate the correct behavior of the activity ATM.Withdraw, we defined the test suite depicted in Listing 1.
The test scenario defines one ATM object atmTD, one Account object accountTD with the balance 100, one Card object cardTD with the pin 1985, and a link between the objects cardTD and accountTD. Furthermore, it defines one Deposit object depositTD with the amount 100, and a link between the objects accountTD and depositTD.
Test case 1 atmTransactionTest is used for testing that if the correct pin is provided and the amount of money to be withdrawn does not exceed the balance of the associated account, the balance is accordingly updated and true is provided as output indicating a successful withdrawal. Further, it checks whether the pin is validated before the account is updated.
The test case defines the activity ATM.Withdraw as activity under test. The object atmTD is defined as context object (i.e., the activity shall be executed for this object). As input the object cardTD defined in the test scenario as well as the Integer values 1985 and 100 are provided for the parameters card, pin, and amount. The test case consists of one execution order assertion and two state assertions.
The execution order assertion (line 20) defines that the operation Card::validatePin() shall be called (action validatePin) before the operation Account::makeWithdrawal() is called (action makeWithdrawal).
The first state assertion (lines 21-23) checks that once a transaction is created for the withdrawal (i.e., a link for the reference ATM.currentTransaction is created), this transaction is in the end of the execution finished (i.e., the link for the reference ATM.currentTransaction is destroyed) and added to the completed transactions of the ATM system (i.e., a link for the reference ATM.completedTransactions is created). For expressing these assertions OCL constraints are used. These constraints are defined in a separate file and are depcited in Listing 2. OCL constraints are invoked by name within state assertions and can be used for both defining asertions on the state of the system and defining temporal expressions (cf. Listing 1 lines 21-23).
The second state assertion (lines 24-29) checks that in the end of the execution of the activity, the account’s balance was updated to 0 and true was provided as output. Furthermore, the OCL constraints NumOfWithdrawsSuccess and BalanceRecords (Listing 2) are evaluated on the account object. NumOfWithdrawsSuccess checks that in the end of the execution one withdrawal record was added to the account. BalanceRecords checks that in the end of the execution the balance of the account is equal to the difference between the sum of all deposit records and the sum of all withdrawal records of the account.
Test case 2 atmTransactionFail is used to validate that if the amount of money to be withdrawn exceeds the account’s balance, the withdrawal is not carried out (i.e., the account’s balance remains unchanged) and false is provided as output indicating an unsuccessful withdrawal. To test this requirement, we specify 200 as input for the amount of money to be withdrawn and define a state assertion (lines 39-44) which checks that after the last action was executed, the balance of the account is unchanged (100) and false is provided as output.
After executing the test cases defined in Listing 1, the framework returns the test results depicted in Listing 3. As can be seen from the results of the order assertions (lines 6-20 and 36-50), it is possible that the action validatePin is executed after the action makeWithdrawal. This is possible because a decision node is missing in the activity ATM.Withdraw that checks whether the pin is valid and only triggers the execution of the withdrawal in this case. The necessary corrections of the activity are presented in Figure 5.