-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement exercise bank-account (#1260)
* Implement exercise bank-account * bank-account: generate README using configlet * bank-account: fix typo and comments
- Loading branch information
1 parent
8a19d21
commit 1c03092
Showing
5 changed files
with
238 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# Bank Account | ||
|
||
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. | ||
|
||
## Instructions | ||
|
||
Run the test file, and fix each of the errors in turn. When you get the | ||
first test to pass, go to the first pending or skipped test, and make | ||
that pass as well. When all of the tests are passing, feel free to | ||
submit. | ||
|
||
Remember that passing code is just the first step. The goal is to work | ||
towards a solution that is as readable and expressive as you can make | ||
it. | ||
|
||
Have fun! | ||
|
||
## Exception messages | ||
|
||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to | ||
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not | ||
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include | ||
a message. | ||
|
||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of | ||
`raise Exception`, you shold write: | ||
|
||
```python | ||
raise Exception("Meaningful message indicating the source of the error") | ||
``` | ||
|
||
## Submitting Exercises | ||
|
||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/bank-account` directory. | ||
|
||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`. | ||
|
||
For more detailed information about running tests, code style and linting, | ||
please see the [help page](http://exercism.io/languages/python). | ||
|
||
## Submitting Incomplete Solutions | ||
It's possible to submit an incomplete solution so you can see how others have completed the exercise. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
class BankAccount(object): | ||
def __init__(self): | ||
pass | ||
|
||
def get_balance(self): | ||
pass | ||
|
||
def open(self): | ||
pass | ||
|
||
def deposit(self, amount): | ||
pass | ||
|
||
def withdraw(self, amount): | ||
pass | ||
|
||
def close(self): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import sys | ||
import threading | ||
import time | ||
import unittest | ||
|
||
from bank_account import BankAccount | ||
|
||
|
||
class BankAccountTests(unittest.TestCase): | ||
|
||
def setUp(self): | ||
self.account = BankAccount() | ||
|
||
def test_newly_opened_account_has_zero_balance(self): | ||
self.account.open() | ||
self.assertEqual(self.account.get_balance(), 0) | ||
|
||
def test_can_deposit_money(self): | ||
self.account.open() | ||
self.account.deposit(100) | ||
self.assertEqual(self.account.get_balance(), 100) | ||
|
||
def test_can_deposit_money_sequentially(self): | ||
self.account.open() | ||
self.account.deposit(100) | ||
self.account.deposit(50) | ||
|
||
self.assertEqual(self.account.get_balance(), 150) | ||
|
||
def test_can_withdraw_money(self): | ||
self.account.open() | ||
self.account.deposit(100) | ||
self.account.withdraw(50) | ||
|
||
self.assertEqual(self.account.get_balance(), 50) | ||
|
||
def test_can_withdraw_money_sequentially(self): | ||
self.account.open() | ||
self.account.deposit(100) | ||
self.account.withdraw(20) | ||
self.account.withdraw(80) | ||
|
||
self.assertEqual(self.account.get_balance(), 0) | ||
|
||
def test_checking_balance_of_closed_account_throws_error(self): | ||
self.account.open() | ||
self.account.close() | ||
|
||
with self.assertRaises(ValueError): | ||
self.account.get_balance() | ||
|
||
def test_deposit_into_closed_account(self): | ||
self.account.open() | ||
self.account.close() | ||
|
||
with self.assertRaises(ValueError): | ||
self.account.deposit(50) | ||
|
||
def test_withdraw_from_closed_account(self): | ||
self.account.open() | ||
self.account.close() | ||
|
||
with self.assertRaises(ValueError): | ||
self.account.withdraw(50) | ||
|
||
def test_cannot_withdraw_more_than_deposited(self): | ||
self.account.open() | ||
self.account.deposit(25) | ||
|
||
with self.assertRaises(ValueError): | ||
self.account.withdraw(50) | ||
|
||
def test_cannot_withdraw_negative(self): | ||
self.account.open() | ||
self.account.deposit(100) | ||
|
||
with self.assertRaises(ValueError): | ||
self.account.withdraw(-50) | ||
|
||
def test_cannot_deposit_negative(self): | ||
self.account.open() | ||
|
||
with self.assertRaises(ValueError): | ||
self.account.deposit(-50) | ||
|
||
def test_can_handle_concurrent_transactions(self): | ||
self.account.open() | ||
self.account.deposit(1000) | ||
|
||
for _ in range(10): | ||
self.adjust_balance_concurrently() | ||
|
||
def adjust_balance_concurrently(self): | ||
def transact(): | ||
self.account.deposit(5) | ||
time.sleep(0.001) | ||
self.account.withdraw(5) | ||
|
||
# Greatly improve the chance of an operation being interrupted | ||
# by thread switch, thus testing synchronization effectively | ||
try: | ||
sys.setswitchinterval(1e-12) | ||
except AttributeError: | ||
# For Python 2 compatibility | ||
sys.setcheckinterval(1) | ||
|
||
threads = [] | ||
for _ in range(1000): | ||
t = threading.Thread(target=transact) | ||
threads.append(t) | ||
t.start() | ||
|
||
for thread in threads: | ||
thread.join() | ||
|
||
self.assertEqual(self.account.get_balance(), 1000) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import threading | ||
|
||
|
||
class BankAccount(object): | ||
def __init__(self): | ||
self.is_open = False | ||
self.balance = 0 | ||
self.lock = threading.Lock() | ||
|
||
def get_balance(self): | ||
with self.lock: | ||
if self.is_open: | ||
return self.balance | ||
else: | ||
raise ValueError | ||
|
||
def open(self): | ||
self.is_open = True | ||
|
||
def deposit(self, amount): | ||
with self.lock: | ||
if self.is_open and amount > 0: | ||
self.balance += amount | ||
else: | ||
raise ValueError | ||
|
||
def withdraw(self, amount): | ||
with self.lock: | ||
if self.is_open and 0 < amount <= self.balance: | ||
self.balance -= amount | ||
else: | ||
raise ValueError | ||
|
||
def close(self): | ||
self.is_open = False |