Skip to content

Commit 1c03092

Browse files
mrcfpscmccandless
authored andcommitted
Implement exercise bank-account (#1260)
* Implement exercise bank-account * bank-account: generate README using configlet * bank-account: fix typo and comments
1 parent 8a19d21 commit 1c03092

File tree

5 files changed

+238
-0
lines changed

5 files changed

+238
-0
lines changed

config.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,18 @@
666666
"transforming"
667667
]
668668
},
669+
{
670+
"uuid": "83a3ff95-c043-401c-bc2c-547d52344b02",
671+
"slug": "bank-account",
672+
"core": false,
673+
"unlocked_by": null,
674+
"difficulty": 4,
675+
"topics": [
676+
"classes",
677+
"concurrency",
678+
"conditionals"
679+
]
680+
},
669681
{
670682
"uuid": "d41238ce-359c-4a9a-81ea-ca5d2c4bb50d",
671683
"slug": "twelve-days",

exercises/bank-account/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Bank Account
2+
3+
Simulate a bank account supporting opening/closing, withdrawals, and deposits
4+
of money. Watch out for concurrent transactions!
5+
6+
A bank account can be accessed in multiple ways. Clients can make
7+
deposits and withdrawals using the internet, mobile phones, etc. Shops
8+
can charge against the account.
9+
10+
Create an account that can be accessed from multiple threads/processes
11+
(terminology depends on your programming language).
12+
13+
It should be possible to close an account; operations against a closed
14+
account must fail.
15+
16+
## Instructions
17+
18+
Run the test file, and fix each of the errors in turn. When you get the
19+
first test to pass, go to the first pending or skipped test, and make
20+
that pass as well. When all of the tests are passing, feel free to
21+
submit.
22+
23+
Remember that passing code is just the first step. The goal is to work
24+
towards a solution that is as readable and expressive as you can make
25+
it.
26+
27+
Have fun!
28+
29+
## Exception messages
30+
31+
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
32+
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
33+
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
34+
a message.
35+
36+
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
37+
`raise Exception`, you shold write:
38+
39+
```python
40+
raise Exception("Meaningful message indicating the source of the error")
41+
```
42+
43+
## Submitting Exercises
44+
45+
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/bank-account` directory.
46+
47+
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
48+
49+
For more detailed information about running tests, code style and linting,
50+
please see the [help page](http://exercism.io/languages/python).
51+
52+
## Submitting Incomplete Solutions
53+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class BankAccount(object):
2+
def __init__(self):
3+
pass
4+
5+
def get_balance(self):
6+
pass
7+
8+
def open(self):
9+
pass
10+
11+
def deposit(self, amount):
12+
pass
13+
14+
def withdraw(self, amount):
15+
pass
16+
17+
def close(self):
18+
pass
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import sys
2+
import threading
3+
import time
4+
import unittest
5+
6+
from bank_account import BankAccount
7+
8+
9+
class BankAccountTests(unittest.TestCase):
10+
11+
def setUp(self):
12+
self.account = BankAccount()
13+
14+
def test_newly_opened_account_has_zero_balance(self):
15+
self.account.open()
16+
self.assertEqual(self.account.get_balance(), 0)
17+
18+
def test_can_deposit_money(self):
19+
self.account.open()
20+
self.account.deposit(100)
21+
self.assertEqual(self.account.get_balance(), 100)
22+
23+
def test_can_deposit_money_sequentially(self):
24+
self.account.open()
25+
self.account.deposit(100)
26+
self.account.deposit(50)
27+
28+
self.assertEqual(self.account.get_balance(), 150)
29+
30+
def test_can_withdraw_money(self):
31+
self.account.open()
32+
self.account.deposit(100)
33+
self.account.withdraw(50)
34+
35+
self.assertEqual(self.account.get_balance(), 50)
36+
37+
def test_can_withdraw_money_sequentially(self):
38+
self.account.open()
39+
self.account.deposit(100)
40+
self.account.withdraw(20)
41+
self.account.withdraw(80)
42+
43+
self.assertEqual(self.account.get_balance(), 0)
44+
45+
def test_checking_balance_of_closed_account_throws_error(self):
46+
self.account.open()
47+
self.account.close()
48+
49+
with self.assertRaises(ValueError):
50+
self.account.get_balance()
51+
52+
def test_deposit_into_closed_account(self):
53+
self.account.open()
54+
self.account.close()
55+
56+
with self.assertRaises(ValueError):
57+
self.account.deposit(50)
58+
59+
def test_withdraw_from_closed_account(self):
60+
self.account.open()
61+
self.account.close()
62+
63+
with self.assertRaises(ValueError):
64+
self.account.withdraw(50)
65+
66+
def test_cannot_withdraw_more_than_deposited(self):
67+
self.account.open()
68+
self.account.deposit(25)
69+
70+
with self.assertRaises(ValueError):
71+
self.account.withdraw(50)
72+
73+
def test_cannot_withdraw_negative(self):
74+
self.account.open()
75+
self.account.deposit(100)
76+
77+
with self.assertRaises(ValueError):
78+
self.account.withdraw(-50)
79+
80+
def test_cannot_deposit_negative(self):
81+
self.account.open()
82+
83+
with self.assertRaises(ValueError):
84+
self.account.deposit(-50)
85+
86+
def test_can_handle_concurrent_transactions(self):
87+
self.account.open()
88+
self.account.deposit(1000)
89+
90+
for _ in range(10):
91+
self.adjust_balance_concurrently()
92+
93+
def adjust_balance_concurrently(self):
94+
def transact():
95+
self.account.deposit(5)
96+
time.sleep(0.001)
97+
self.account.withdraw(5)
98+
99+
# Greatly improve the chance of an operation being interrupted
100+
# by thread switch, thus testing synchronization effectively
101+
try:
102+
sys.setswitchinterval(1e-12)
103+
except AttributeError:
104+
# For Python 2 compatibility
105+
sys.setcheckinterval(1)
106+
107+
threads = []
108+
for _ in range(1000):
109+
t = threading.Thread(target=transact)
110+
threads.append(t)
111+
t.start()
112+
113+
for thread in threads:
114+
thread.join()
115+
116+
self.assertEqual(self.account.get_balance(), 1000)
117+
118+
119+
if __name__ == '__main__':
120+
unittest.main()

exercises/bank-account/example.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import threading
2+
3+
4+
class BankAccount(object):
5+
def __init__(self):
6+
self.is_open = False
7+
self.balance = 0
8+
self.lock = threading.Lock()
9+
10+
def get_balance(self):
11+
with self.lock:
12+
if self.is_open:
13+
return self.balance
14+
else:
15+
raise ValueError
16+
17+
def open(self):
18+
self.is_open = True
19+
20+
def deposit(self, amount):
21+
with self.lock:
22+
if self.is_open and amount > 0:
23+
self.balance += amount
24+
else:
25+
raise ValueError
26+
27+
def withdraw(self, amount):
28+
with self.lock:
29+
if self.is_open and 0 < amount <= self.balance:
30+
self.balance -= amount
31+
else:
32+
raise ValueError
33+
34+
def close(self):
35+
self.is_open = False

0 commit comments

Comments
 (0)