Congratulations!! Your team has been selected to create a banking simulator. After meeting with the clients, you have agreed on requirements and even have developed a basic design of the program. Basically, the software must be able to create accounts, deposit, withdraw, and transfer funds, and deal with fees, interest, etc.
Your software development group has decided to embrace Test-Driven Development (TTD) for this project. (Good for you!) This means that, now that the requirements and design have been developed, the next step is to create unit tests for all the classes identified in the design. (Normally you might develop the tests at the same time as starting the implementation, but for this project, we are only developing unit tests.)
View the UML class diagram for the bank simulation. (You can learn about UML from the class resources.)
In this project, you will be given a list of classes and their public methods, for a simple (not realistic) banking simulation program. These comprise the design, or specification, for a banking simulator. Your group's task is to create a thorough set of unit tests. In order to run your tests, you will need skeleton classes with stub methods; you should not attempt to actually create a banking simulation program! To save time, I have provided a zip file with the skeleton classes you will need. You should probably add all those, and a package for your unit tests, before your initial commit.
Since there is no real code to test (only stub methods), it is expected that all your tests will fail. Keep in mind you are only to create unit tests, not a working program. (So, do not implement ANY of the banking simulator's methods.)
Naturally a real banking system needs persistent data (CRUD) to hold account data between runs of the program and many other features. Normally you would need to test that functionality as well. To keep this project simple, no persistence classes or methods need to be tested, and are not mentioned in this project's requirements. No exception handling is included in this project either.
In other words, this simulation is not terribly realistic. (As a matter of fact, it is not a great design either!) For a first project where you must create unit tests in a group, it should be complex enough as is. Keep in mind the requirements for testing on financial software is high, so be sure to have sufficient tests for the simulator (and not just a few “happy path” tests).
Of the six classes shown, some may not have any methods except trivial ones, and thus do not require any unit tests. Some of the methods shown might be considered trivial by your team; you shouldn't include tests for those either. (Adding a comment stating those classes/methods were trivial would be a good idea.) Additionally, some methods cannot be tested easily using JUnit, such as methods that display a GUI. Don't try to test such methods, although some of them are shown in the requirements. Testing such functionality requires advanced tools and techniques, such as GUI testers and a mocking framework (e.g., Mockito).
I have also omitted nearly all getters and setters, toString, compareTo,
clone, and other such methods from the requirements, but of course
a real program must have them as needed.
You can actually see those methods in the skeleton classes, for example
the “Account.getBalance()
” method.
In general, such methods are trivial to implement and do not need unit
testing, although you may need to use getters and possibly
setters in your unit test methods.
However, if your team feels any of those methods merit a unit test,
add appropriate unit tests for them.
(It isn't as if adding additional tests is ever a bad thing, just that
often it's a waste of time.)
Please use JUnit 5 (Jupiter) framework for your unit tests.
The Maven dependency for JUnit 5 is:
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.7.1</version> <scope>test</scope> </dependency>
(Use a newer stable release if one is available, but you may need to use newer versions of some Maven plugins if you do that.)
For the initial version, we can find many candidate classes but
in the end only a few are needed to meet the requirements for a simple
banking simulation.
these classes and their more important methods are described below.
Note all classes are in the package banking
.
Take some time to example these classes and their methods.
See how they can be used in your tests.
For example, to create a SavingsAcccount
to test
you need to create a Customer
first, since the only method
to create accounts is Customer.addSavingsAccount
.
Similarly, you will need a Bank
object to create a
Customer
.
Bank
Class Bank
is responsible for the main
method,
managing customers, and keeping track of various fees and interest rates;
only the fees and rates have setter methods (only one shown in the class
diagram).
Bank ( String name )
static void main ( String[] args )
void addCustomerGUI ()
String addCustomer ( String lastName, String firstName )
Customer getCustomer ( String customerId )
List<Customer> getCustomer ( String lastName,
String firstName )
SortedSet<Customer> getAllCustomers ()
void removeCustomer ( String customerId )
SortedSet<Account> getAllAccounts ()
Customer
Class Customer
is responsible for managing a customer's details,
including that customer's accounts.
Fields include a reference to the owning bank, a unique customer ID,
Customer's first and last names, and a SortedSet of transactions (not a List
for no particular reason).
Only the customer's name fields have setters.
Customer( Bank bank, String lastName, String firstName )
SavingsAccount addSavingsAccount ( double initBal,
String description )
Account getAccount ( String accountId )
SortedSet<Account> getCustomerAccounts()
void removeAccount ( String accountId )
double YtdFees ()
double YtdInterest ()
abstract class Account
Class Account
is responsible for managing the details of any type
of account, including an accountId, customerId, description, account creation
date, the current balance, and the account's transaction list.
Only the account description has a setter.
Account ( Customer cust, double initBal, String description )
abstract void deposit ( double amount )
abstract void withdraw ( double amount )
static void transfer ( Account fromAccount, Account toAccount,
double amount )
List<Transaction> getTransactions ()
Transaction getTransaction ( int transactionId )
getBalance
.)
You need to test any non-trivial methods your group decides
are a good idea. SavingsAccount extends Account
Class SavingsAccount
is an Account, but includes monthly
interest payments.
A field defaultInterestRate holds the assigned monthly interest rate, and has
both a setter and a getter.
SavingsAccount ( double initialBalance, String customerId,
String description )
void addInterest ()
class Transaction implements Comparable<Transaction>
Class Transaction
objects represent any deposit, withdrawal, or
other transaction on an account.
(Note transfers are implemented as a pair of transactions.)
This class contains files for a transaction ID, a timestamp (the
date and time of the transaction), the type of transaction, the amount,
and a description.
None of these fields have setters.
Transaction(TransactionType type, double amount,
String description)
enum TransactionType
Enum TransactionType
lists all possible transaction types:
DEPOSIT, WITHDRAWAL, INTEREST, CHECK, FEE, PENALTY, and ADJUSTMENT.
You will need skeleton classes with stub methods for all classes and methods in the design. (A Zip with the skeletons is provided for you, as a starting point.) They should compile and run, but do nothing. Do not implement any methods! When complete, upload to a GitHub repo for this project. (Use a different repo than for the search engine projects.)
Once you have that skeleton in place in a Maven project and your workflow documented in a ReadMe file, make your initial Git commit.
Now your team can write and then run the JUnit tests. Naturally, it is expected that all the tests will fail, since the stub methods don't do what they are supposed to do. While developing tests, you may decide additional classes and/or methods are needed. If so, include skeletons/stubs for those as well, in some Git commit. Do not forget to include any design documents (if you change or add to the design) in your projects documentation (also in the repo).
Decide who within your group will be responsible for which classes and methods to create tests for. A great way to proceed is to have all group members propose tests for everything, and then your team can merge the best ideas of those. (If you don't do that as a team, have at least two team members designing the tests for each public class member.) In addition to producing a list of unit tests, your team should also document which methods you have decided not to test, and state why. (Not necessary for missing getters, setters, toString, etc.)
Having decided as a group on which tests to have, divide up the list so each team member has about the same number of tests they must implement. Document who has been assigned which tests to implement in a file (could be the ReadMe file or another one), and keep that in the repo's documentation. As usual, each member should work in their own branch on each related batch of unit tests.
When someone has implemented one or more tests, they should push their branch to the project's GitHub repo, so code review can begin. Every team member should read the code, and make any suggestions or ask any questions that occur to them. (Your team can use the GitHub repo's wiki if your team has trouble scheduling meetings.) If changes are needed (for example, additional test cases seem to be needed), the author goes back to fix those, then pushes the updates for a new round of code review. Once a set of tests is approved by the group, merge them into the master branch using your group's agreed upon merging strategy (for example, only the team leader merges into master, or the branch owner must do the merge).
Right after any merge into master event, all group members should do a Pull to refresh their local copy of the master branch.
Grading will be done for the group's results. Individuals in the group will have their grades adjusted by peer ratings and by their commits. A rating of each team member's level of participation should be sent individually by every member directly to the instructor via email. Be sure to include yourself in the ratings! The rating is a number from 0 (didn't participate at all), 1 (less than their fair share of the work), 2 (participated fully), or 3 (did more than their fair share of the work). Additional comments are allowed if you wish to elaborate. (I will keep such comments as confidential as possible under Florida law.)
Send your group ratings as email to .
Your team should put the code, including the skeleton classes and all the JUnit tests, in a repo on GitHub and send just a link to it. Your project's final version should receive a Git tag of “Banking Simulation Unit Tests”, so I know which version to grade.
Submit the URL to the GitHub commit to grade to the correct Canvas dropbox for this assignment.
In this project, I will be grading the quality and thoroughness of your unit
tests, and the quality of the accompanying documentation (which methods
you didn't feel it necessary to test, as described above).
I will also be checking if you have tests you shouldn't, such as a getter
method that does only “return value;
”.
You should review the
Testing
resources provided on the class web page, which include the testing
lecture notes, links to JUnit API and other
documentation, and examples and demos.
As usual, you should pick descriptive names for your test classes and
methods, and use appropriate comments and code style.
With JUnit, you use the methods of the class
org.junit.Assert
, such as assertEquals
or assertFalse
, inside of test methods which are marked
with the “@Test
” annotation.
These test methods, along with other methods and fields, are part of
a class known as a test suite.
Methods marked with @Before
all get called before every
@Test
method does, every time.
You can also create @After
methods, to cleanup stuff.
(You can also have static methods marked with
“@BeforeClass
”, which get run once at the start
of a test run.
You could use that to re-create a sample file, setup a database,
or start some server program.)
Remember that all popular IDEs have wizards to create test suites (Eclipse uses the term Test Case for the class). The hard part is to come up with a good set of tests. You want confidence that if all the tests pass, the code being tested correctly implements the design.
To help get you started, look at the Account.deposit
method.
The logical way to test that is to get the current balance, then
make a deposit of a certain amount, then get the new balance.
The new balance should be equal to the previous balance plus the
amount of the deposit.
Here's some sample JUnit 5 code for this (note the
differences from JUnit 4):
package banking; import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; class BankSimTests { private Bank bank; private Customer customer; private String custID; private Account savingsAccount; // Test Fixture: @BeforeEach void setUp () { bank = new Bank( "My Bank" ); custID = bank.addCustomer("Piffl", "Hymie"); customer = bank.getCustomer( custID ); savingsAccount = customer.addSavingsAccount( 0.00, "Test Account" ); } // Test a deposit of $10.00 works: @Test @DisplayName("Account.deposit Tests") void depositShouldIncreaseBalance () { final double initialBalance = savingsAccount.getBalance(); final double amount = 10.00; savingsAccount.deposit( amount ); final double finalBalance = savingsAccount.getBalance(); assertEquals( finalBalance, initialBalance + amount, "Balance should be " + (initialBalance+amount) + "but was " + finalBalance ); } }
This is a single happy path test case. You should include some additional test cases for this method, such as boundary-value test (for example, a deposit of $0.00) and illegal data that should fail (for example, a negative deposit amount).