Skip to content

Commit

Permalink
Add the Bank Account exercise
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelBunker committed Aug 3, 2023
1 parent cd1ca26 commit 3e4f84d
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 0 deletions.
9 changes: 9 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,15 @@
"prerequisites": [],
"difficulty": 4,
"topics": ["conditionals", "strings"]
},
{
"slug": "bank-account",
"name": "Bank Account",
"uuid": "D85C328B-0778-46F6-BD50-D14417FA8347",
"practices": [],
"prerequisites": ["classes", "conditionals", "integers"],
"difficulty": 3,
"topics": ["classes", "conditionals"]
}
]
},
Expand Down
12 changes: 12 additions & 0 deletions exercises/practice/bank-account/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Description

Simulate a bank account supporting opening/closing, withdrawals, and deposits of money.
Watch out for concurrent transactions!

A bank account can be accessed in multiple ways.
Clients can make deposits and withdrawals using the internet, mobile phones, etc.
Shops can charge against the account.

Create an account that can be accessed from multiple threads/processes (terminology depends on your programming language).

It should be possible to close an account; operations against a closed account must fail.
10 changes: 10 additions & 0 deletions exercises/practice/bank-account/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"blurb": "Simulate a bank account supporting opening/closing, withdraws, and deposits of money.",
"authors": ["MichaelBunker"],
"contributors": [],
"files": {
"solution": ["BankAccount.php"],
"test": ["BankAccountTest.php"],
"example": [".meta/example.php"]
}
}
90 changes: 90 additions & 0 deletions exercises/practice/bank-account/.meta/example.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

/*
* By adding type hints and enabling strict type checking, code can become
* easier to read, self-documenting and reduce the number of potential bugs.
* By default, type declarations are non-strict, which means they will attempt
* to change the original type to match the type specified by the
* type-declaration.
*
* In other words, if you pass a string to a function requiring a float,
* it will attempt to convert the string value to a float.
*
* To enable strict mode, a single declare directive must be placed at the top
* of the file.
* This means that the strictness of typing is configured on a per-file basis.
* This directive not only affects the type declarations of parameters, but also
* a function's return type.
*
* For more info review the Concept on strict type checking in the PHP track
* <link>.
*
* To disable strict typing, comment out the directive below.
*/

declare(strict_types=1);

class BankAccount
{
private int $balance = 0;
private bool $opened = false;

public function open()
{
if ($this->opened) {
throw new Exception('account already open');
}

$this->opened = true;
$this->balance = 0;
}

public function close()
{
if (!$this->opened) {
throw new Exception('account not open');
}

$this->balance = 0;
$this->opened = false;
}

public function balance(): int
{
if (!$this->opened) {
throw new Exception('account not open');
}

return $this->balance;
}

public function deposit(int $amt)
{
if (0 > $amt) {
throw new InvalidArgumentException('amount must be greater than 0');
}

if (!$this->opened) {
throw new Exception('account not open');
}

$this->balance += $amt;
}

public function withdraw(int $amt)
{
if (0 > $amt) {
throw new InvalidArgumentException('amount must be greater than 0');
}

if (!$this->opened) {
throw new Exception('account not open');
}

if ($amt > $this->balance) {
throw new InvalidArgumentException('amount must be less than balance');
}

$this->balance -= $amt;
}
}
53 changes: 53 additions & 0 deletions exercises/practice/bank-account/BankAccount.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

/*
* By adding type hints and enabling strict type checking, code can become
* easier to read, self-documenting and reduce the number of potential bugs.
* By default, type declarations are non-strict, which means they will attempt
* to change the original type to match the type specified by the
* type-declaration.
*
* In other words, if you pass a string to a function requiring a float,
* it will attempt to convert the string value to a float.
*
* To enable strict mode, a single declare directive must be placed at the top
* of the file.
* This means that the strictness of typing is configured on a per-file basis.
* This directive not only affects the type declarations of parameters, but also
* a function's return type.
*
* For more info review the Concept on strict type checking in the PHP track
* <link>.
*
* To disable strict typing, comment out the directive below.
*/

declare(strict_types=1);

class BankAccount
{
public function open()
{
throw new \Exception(sprintf('Implement the %s method', __FUNCTION__));
}

public function close()
{
throw new \Exception(sprintf('Implement the %s method', __FUNCTION__));
}

public function balance(): int
{
throw new \Exception(sprintf('Implement the %s method', __FUNCTION__));
}

public function deposit(int $amt)
{
throw new \Exception(sprintf('Implement the %s method', __FUNCTION__));
}

public function withdraw(int $amt)
{
throw new \Exception(sprintf('Implement the %s method', __FUNCTION__));
}
}
173 changes: 173 additions & 0 deletions exercises/practice/bank-account/BankAccountTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

/*
* By adding type hints and enabling strict type checking, code can become
* easier to read, self-documenting and reduce the number of potential bugs.
* By default, type declarations are non-strict, which means they will attempt
* to change the original type to match the type specified by the
* type-declaration.
*
* In other words, if you pass a string to a function requiring a float,
* it will attempt to convert the string value to a float.
*
* To enable strict mode, a single declare directive must be placed at the top
* of the file.
* This means that the strictness of typing is configured on a per-file basis.
* This directive not only affects the type declarations of parameters, but also
* a function's return type.
*
* For more info review the Concept on strict type checking in the PHP track
* <link>.
*
* To disable strict typing, comment out the directive below.
*/

declare(strict_types=1);

class BankAccountTest extends PHPUnit\Framework\TestCase
{
private BankAccount $bankAccount;

public static function setUpBeforeClass(): void
{
require_once 'BankAccount.php';
}

protected function setUp(): void
{
$this->bankAccount = new BankAccount();
}

public function testOpenNewAccount(): void
{
$this->bankAccount->open();
$this->assertEquals(0, $this->bankAccount->balance());
}

public function testSingleDeposit(): void
{
$this->bankAccount->open();
$this->bankAccount->deposit(100);
$this->assertEquals(100, $this->bankAccount->balance());
}

public function testMultipleDeposits(): void
{
$this->bankAccount->open();
$this->bankAccount->deposit(100);
$this->bankAccount->deposit(50);
$this->assertEquals(150, $this->bankAccount->balance());
}

public function testSingleWithdraw(): void
{
$this->bankAccount->open();
$this->bankAccount->deposit(100);
$this->bankAccount->withdraw(75);
$this->assertEquals(25, $this->bankAccount->balance());
}

public function testMultipleWithdraws(): void
{
$this->bankAccount->open();
$this->bankAccount->deposit(100);
$this->bankAccount->withdraw(80);
$this->bankAccount->withdraw(20);
$this->assertEquals(0, $this->bankAccount->balance());
}

public function testMultipleOperations(): void
{
$this->bankAccount->open();
$this->bankAccount->deposit(100);
$this->bankAccount->deposit(110);
$this->bankAccount->withdraw(200);
$this->bankAccount->deposit(60);
$this->bankAccount->withdraw(50);
$this->assertEquals(20, $this->bankAccount->balance());
}

public function testNoBalanceForClosedAccounts()
{
$this->bankAccount->open();
$this->bankAccount->close();
$this->expectException(Exception::class);
$this->expectExceptionMessage('account not open');
$this->bankAccount->balance();
}

public function testNoDepositsForClosedAccounts()
{
$this->bankAccount->open();
$this->bankAccount->close();
$this->expectException(Exception::class);
$this->expectExceptionMessage('account not open');
$this->bankAccount->deposit(50);
}

public function testNoDepositsForUnOpenedAccounts()
{
$this->expectException(Exception::class);
$this->expectExceptionMessage('account not open');
$this->bankAccount->deposit(50);
}

public function testNoWithdrawsFromClosedAccounts()
{
$this->bankAccount->open();
$this->bankAccount->close();
$this->expectException(Exception::class);
$this->expectExceptionMessage('account not open');
$this->bankAccount->withdraw(50);
}

public function testCannotCloseUnOpenedAccounts()
{
$this->expectException(Exception::class);
$this->expectExceptionMessage('account not open');
$this->bankAccount->close();
}

public function testCannotOpenExistingAccount()
{
$this->bankAccount->open();
$this->expectException(Exception::class);
$this->expectExceptionMessage('account already open');
$this->bankAccount->open();
}

public function testReopenedAccountDoesNotKeepBalance()
{
$this->bankAccount->open();
$this->bankAccount->deposit(50);
$this->bankAccount->close();
$this->bankAccount->open();
$this->assertEquals(0, $this->bankAccount->balance());
}

public function testCannotWithdrawMoreThanDeposited()
{
$this->bankAccount->open();
$this->bankAccount->deposit(25);
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('amount must be less than balance');
$this->bankAccount->withdraw(50);
}

public function testCannotWithdrawNegativeAmount()
{
$this->bankAccount->open();
$this->bankAccount->deposit(100);
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('amount must be greater than 0');
$this->bankAccount->withdraw(-50);
}

public function testCannotDepositNegativeAmount()
{
$this->bankAccount->open();
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('amount must be greater than 0');
$this->bankAccount->deposit(-50);
}
}

0 comments on commit 3e4f84d

Please sign in to comment.