import sys import threading import time import unittest from bank_account import BankAccount class BankAccountTest(unittest.TestCase): def test_newly_opened_account_has_zero_balance(self): account = BankAccount() account.open() self.assertEqual(account.get_balance(), 0) def test_can_deposit_money(self): account = BankAccount() account.open() account.deposit(100) self.assertEqual(account.get_balance(), 100) def test_can_deposit_money_sequentially(self): account = BankAccount() account.open() account.deposit(100) account.deposit(50) self.assertEqual(account.get_balance(), 150) def test_can_withdraw_money(self): account = BankAccount() account.open() account.deposit(100) account.withdraw(50) self.assertEqual(account.get_balance(), 50) def test_can_withdraw_money_sequentially(self): account = BankAccount() account.open() account.deposit(100) account.withdraw(20) account.withdraw(80) self.assertEqual(account.get_balance(), 0) def test_checking_balance_of_closed_account_throws_error(self): account = BankAccount() account.open() account.close() with self.assertRaisesWithMessage(ValueError): account.get_balance() def test_deposit_into_closed_account(self): account = BankAccount() account.open() account.close() with self.assertRaisesWithMessage(ValueError): account.deposit(50) def test_withdraw_from_closed_account(self): account = BankAccount() account.open() account.close() with self.assertRaisesWithMessage(ValueError): account.withdraw(50) def test_close_already_closed_account(self): account = BankAccount() with self.assertRaisesWithMessage(ValueError): account.close() def test_open_already_opened_account(self): account = BankAccount() account.open() with self.assertRaisesWithMessage(ValueError): account.open() def test_reopened_account_does_not_retain_balance(self): account = BankAccount() account.open() account.deposit(50) account.close() account.open() self.assertEqual(account.get_balance(), 0) def test_cannot_withdraw_more_than_deposited(self): account = BankAccount() account.open() account.deposit(25) with self.assertRaises(ValueError): account.withdraw(50) def test_cannot_withdraw_negative(self): account = BankAccount() account.open() account.deposit(100) with self.assertRaisesWithMessage(ValueError): account.withdraw(-50) def test_cannot_deposit_negative(self): account = BankAccount() account.open() with self.assertRaisesWithMessage(ValueError): account.deposit(-50) def test_can_handle_concurrent_transactions(self): account = BankAccount() account.open() account.deposit(1000) self.adjust_balance_concurrently(account) self.assertEqual(account.get_balance(), 1000) def adjust_balance_concurrently(self, account): def transact(): account.deposit(5) time.sleep(0.001) 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 = [threading.Thread(target=transact) for _ in range(1000)] for thread in threads: thread.start() for thread in threads: thread.join() # Utility functions def assertRaisesWithMessage(self, exception): return self.assertRaisesRegex(exception, r".+") if __name__ == '__main__': unittest.main()