I try raise exception in pytest when battery run out from device when calculator count 100x times.
class:
class Calculator:
def __init__(self, battery: int = 100, filename=None):
self.battery = battery
self.filename = filename
self.check_battery()
def check_battery(self):
if self.battery <= 0:
raise NoBatteryError("The battery has run out")
def add(self, *args):
self.check_battery()
self.battery -= 1
result_add = 0
for number_to_add in args:
result_add += number_to_add
return result_add
class NoBatteryError(Exception):
pass
Pytest:
def test_battery_by_calculate_100x_times(calc):
# Given
for iteration in range(1, 103):
value = 2
result = calc.add(value, value)
expected_value = value + value
try:
result == expected_value
except NoBatteryError:
assert True
Output:
FAILED test_electronic_device_calculator.py::test_battery_by_calculate_100x_times - electronic_devices.calculator.NoBatteryError: The battery has run out
=========1 failed, 29 passed in 0.20s =======
Simple test like:
def test_check_battery_should_raise_no_battery_error(calc):
calc.battery = 0
with pytest.raises(NoBatteryError):
calc.check_battery()
work for me. I try use also in for loop with pytest.raises(NoBatteryError) - but my function also don't work.
I am not sure that it should looks like that but works:
def test_battery_by_calculate_100x_times(calc):
for iteration in range(0, 102):
value = 2
try:
calc.add(value, value)
except NoBatteryError:
assert True
Related
I have a DynamicArray class shown below. (I have only included relevant methods. The rest can be viewed from https://www.geeksforgeeks.org/implementation-of-dynamic-array-in-python/)
import ctypes
class DynamicArray:
'''
Dynamic Array class
'''
def __init__(self):
self.n = 0 # count number of elements
self.capacity = 1 # default capacity
self.A = self.make_array(self.capacity)
def __len__(self):
"""
Return number of elements in the array
"""
return self.n
def __getitem__(self,k):
"""
Return element at k index
"""
#Check if K index is out of bounds#
if not 0 <= k < self.n:
return IndexError('{} is out of bounds'.format(k))
return self.A[k] #Retrieve from the array at index k#
Then I have another unit test file down below
from DynamicArray import DynamicArray
import unittest
class MyTestCase(unittest.TestCase):
def setUp(self) -> None:
self.a = DynamicArray() # empty array
self.b = DynamicArray()
self.b.append(0)
self.c = DynamicArray()
self.c.append(0)
self.c.append(1)
def test_getitem(self):
self.assertEqual(self.a.__getitem__(0),IndexError('0 is out of bounds'))
When I run the test I expect self.a.__getitem__(0) to throw IndexError('0 is out of bounds') and I can't see why the assertion fails? The only difference is that self.a.__getitem__(0) will yield IndexError('{} is out of bounds'.format(0)), which seems to me the same as IndexError('0 is out of bounds')
I tried running below code to see if the string by themselves were any different
if '{} is out of bounds'.format(0) == '0 is out of bounds':
print('str equal')
if '{} is out of bounds'.format(0).__len__() == '0 is out of bounds'.__len__():
print('len equal')
if IndexError('{} is out of bounds'.format(0)) == IndexError('0 is out of bounds'):
print('IndexError equal')
and confirmed that only the third if statement did not print
below is the photo of the console
Thanks in advance. Constructive criticisms and feedbacks are welcome.
Exceptions can't be conpared with assertEqual.
with self.assertRaises(IndexError, msg='0 is out of bounds'):
self.a[0]
And Exceptions must be raiseed to be captured.
You're returning IndexError
raise IndexError('{} is out of bounds'.format(k))
https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises
I understand that I can use "while true" and call the 'get_ticker' method to get the last price of the product, but this drives from python instead of the market itself. I was wondering if there is a way to get the last price as BitMEX's website changes. Thanks
Check my bitmex project, there is solution of your problem: bitmex-supervisor
Essential code fragments:
In __init__():
self.last_price = 0
self._min_price = float('inf')
self._max_price = -1
self.initial_price = float('nan')
self.tracking = False
Methods:
#property
def min_price(self):
return self._min_price
#min_price.setter
def min_price(self, value):
if value < self.initial_price:
self.callback_price_decreased() # here you can do some stuff
self._min_price = value
#property
def max_price(self):
return self._max_price
#max_price.setter
def max_price(self, value):
if value > self.initial_price:
self.callback_price_increased() # here you can do some stuff
self._max_price = value
def stop_trailing(self):
self.tracking = False
def start_trailing(self, initial_price: float):
"""
:param initial_price: the price after reaching which order will be moving
"""
self._max_price = -1
self._min_price = float('inf')
self.initial_price = initial_price
self.tracking = True
In __on_message():
instrument = self.get_instrument(symbol=self.order.symbol)
if instrument is not None:
self.last_price = instrument['lastPrice']
if self.tracking:
if self.last_price > self.max_price and self.order.side == 'Sell':
self.max_price = self.last_price
elif self.last_price < self.min_price and self.order.side == 'Buy':
self.min_price = self.last_price
The title pretty much explains the problem. I don't know if there's a practical solution to this or if I'm being too picky over the behavior of my code.
This article was hinting in the right direction, but I never got any code to work.
https://medium.com/#adamshort/python-gems-5-silent-function-chaining-a6501b3ef07e
Here's an example of the functionality that I want:
class Calc:
def __init__(self, n=0):
self.n = n
def add(self, n):
self.n += n
return self
def might_return_false(self):
return False
def print(self):
print(self.n)
return self
w = Calc()
# The rest of the chain after might_return_false should be ignored
addedTwice = w.add(5).might_return_false().add(5).print()
w.print() # Should print 5
print(addedTwice) # Should print False
I think the article meant something more or less like below (but I prefer the other answer using exception, as it's more readable and better testable).
Create a helper class:
class Empty:
def __call__(self, *args, **kwargs):
return self
def __getattr__(self, *args, **kwargs):
return self
def print(self, *args, **kwargs):
return False
and
def might_return_false(self):
return Empty()
Exceptions are a great way to interrupt a chained operation:
class CalcError(Exception):
pass
class Calc:
def __init__(self, n: int = 0):
self.n = n
def add(self, n: int) -> 'Calc':
self.n += n
return self
def might_raise(self) -> 'Calc':
raise CalcError
def __str__(self) -> str:
return str(self.n)
w = Calc()
try:
w.add(5).might_raise().add(5)
addedTwice = True
except CalcError:
addedTwice = False
print(w) # prints 5
print(addedTwice) # prints False
You could also do chains like:
w = Calc()
num_added = 0
try:
w.add(5)
num_added += 1
w.add(5)
num_added += 1
w.might_raise()
w.add(5)
num_added += 1
w.add(5)
num_added += 1
except CalcError:
print(f"Stopped after {num_added} additions")
If you attempt to do this with return instead of raise, you need to check the status at each step of the chain so that you can switch off to some other branch of code (probably via an if block). Raising an exception has the very useful property of immediately interrupting execution, no matter where you are, and taking you straight to the nearest matching except.
I wrote a simple Proxy class in python3, but I have a problem with "was_called" function
class Proxy:
last_invoked = ""
calls = {}
def __init__(self, obj):
self._obj = obj
def __getattr__(self, item):
attrs = dir(self._obj)
if item in attrs:
Proxy.last_invoked = item
if item in Proxy.calls.keys():
Proxy.calls[item] += 1
else:
Proxy.calls[item] = 1
if item in Proxy.calls.keys():
Proxy.calls[item] += 1
else:
Proxy.calls[item] = 1
return getattr(self._obj, item)
else:
raise Exception('No Such Method')
def last_invoked_method(self):
if Proxy.last_invoked == "":
raise Exception('No Method Is Invoked')
else:
return Proxy.last_invoked
def count_of_calls(self, method_name):
if method_name in Proxy.calls.keys():
return Proxy.calls[method_name]
return 0
def was_called(self, method_name):
if method_name in Proxy.calls.keys():
if Proxy.calls[method_name] > 0: return True
return False
class Radio():
def __init__(self):
self._channel = None
self.is_on = False
self.volume = 0
def get_channel(self):
return self._channel
def set_channel(self, value):
self._channel = value
def power(self):
self.is_on = not self.is_on
radio = Radio()
radio_proxy = Proxy(radio)
radio.number = 3
radio_proxy.number = 3
radio_proxy.power()
print(radio_proxy.was_called("number"))
print(radio_proxy.was_called("power"))
"was_called" function is work for functions and attributes that is in radio at first such as "power", but it's not work for new attributes that we add such as "number".
I expect for both print "True", because both of "power" and "number" is called. but first print return False!
What do you suggest?
def Proxy(class_type):
class ProxyClass(class_type):
def __init__(self, *args, **kwargs):
# Set your _calls and _last_invoked here, so that they are not class attributes (and are instead instance attributes).
self._calls = {}
self._last_invoked = ""
# Pass the arguments back to the class_type (in our case Radio) to initialize the class.
super().__init__(*args, **kwargs)
def __getattribute__(self, item):
# We must do this prelimary check before continuing on to the elif statement.
# This is since _calls and _last_invoked is grabbed when self._last_invoked/self._calls is called below.
if item in ("_calls", "_last_invoked"):
return super(ProxyClass, self).__getattribute__(item)
elif not item.startswith("_"):
self._last_invoked = item
self._calls[item] = 1 if item not in self._calls.keys() else self._calls[item] + 1
return super(ProxyClass, self).__getattribute__(item)
def __setattr__(self, item, val):
# Wait until _calls is initialized before trying to set anything.
# Only set items that do not start with _
if not item == "_calls" and not item.startswith("_"):
self._calls[item] = 0
super(ProxyClass, self).__setattr__(item, val)
def last_invoked_method(self):
if self._last_invoked == "":
raise Exception('No Method Is Invoked')
else:
return self._last_invoked
def count_of_calls(self, method_name):
return self._calls[method_name] if method_name in self._calls.keys() else 0
def was_called(self, method_name):
return True if method_name in self._calls.keys() and self._calls[method_name] > 0 else False
return ProxyClass
#Proxy
class Radio():
def __init__(self):
self._channel = None
self.is_on = False
self.volume = 0
def get_channel(self):
return self._channel
def set_channel(self, value):
self._channel = value
def power(self):
self.is_on = not self.is_on
radio = Proxy(Radio)()
radio.number = 3 # Notice that we are only setting the digit here.
radio.power()
print(radio._calls)
print(radio.number) # Notice that this when we are actually calling it.
print(radio._calls)
outputs:
{'is_on': 0, 'volume': 0, 'number': 0, 'power': 1}
3
{'is_on': 0, 'volume': 0, 'number': 1, 'power': 1}
A few modifications here and there, but you should be able to see the bigger idea by reading through the code. From here you should be able to modify the code to your liking. Also note that any variable that starts with _ is automatically removed from the _calls dictionary.
If you rather not use the decorator #Proxy, you may initialize your Radio class (as a proxy) like so:
# Second parentheses is where your Radio args go in.
# Since Radio does not take any args, we leave it empty.
radio_proxy = Proxy(Radio)()
Also, make sure to understand the difference between class attributes, and instance attributes.
Edit:
class Test:
def __init__(self, var):
self.var = var
self.dictionary = {}
def __getattribute__(self, item):
print("we are GETTING the following item:", item)
# If we don't do this, you end up in an infinite loop in which Python is
# trying to get the `dictionary` class to do `self.dictionary['dictionary'] = ...`
if item == "dictionary":
super(Test, self).__getattribute__(item)
else:
self.dictionary[item] = "Now we can use this!"
return super(Test, self).__getattribute__(item)
def __setattr__(self, item, key):
print("we are SETTING the following item:", item)
super(Test, self).__setattr__(item, key)
Notice:
test = Test(4)
outputs:
we are SETTING the following item: var
we are SETTING the following item: dictionary
then following it:
test.var
outputs:
we are GETTING the following item: var
we are GETTING the following item: dictionary
I'm trying to unit test purchaseItem when there is no positive amount of given product and find_price_of_given_id methods in my Automat class.
import unittest
from Automat import Automat
from Bank import Bank
from Item import Item
from exceptions.NoItemException import NoItemException
class AutomatTest(unittest.TestCase):
def test_checkPriceOfGivenID(self):
bank = Bank()
automat = Automat(bank)
Cola = Item(2)
automat.add_object(Cola)
self.assertEqual(automat.find_price_of_given_id(30), 2)
def test_checkIfPurchaseItemCanBeProcessedWhenNoProduct(self):
bank = Bank()
automat2 = Automat(bank)
Soda = Item(2, 0)
automat2.add_object(Soda)
self.assertEqual(automat2.purchaseItem(30), "Given amount it too small and " \
"no item with " + str(30) + " in automat!")
if __name__ == '__main__':
unittest.main()
If I run test one by one, both are passed. If I run the whole class then it says thattest_checkPriceOfGivenID is failed :
Testing started at 14:12 ...
C:\Users\Admin\PycharmProjects\vending-machine\venv\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2018.3.4\helpers\pycharm\_jb_unittest_runner.py" --path C:/Users/Admin/PycharmProjects/vending-machine/AutomatTest.py
Launching unittests with arguments python -m unittest C:/Users/Admin/PycharmProjects/vending-machine/AutomatTest.py in C:\Users\Admin\PycharmProjects\vending-machine
Ran 2 tests in 0.004s
Error
Traceback (most recent call last):
File "C:\ProgramData\Anaconda3\lib\unittest\case.py", line 59, in testPartExecutor
yield
File "C:\ProgramData\Anaconda3\lib\unittest\case.py", line 615, in run
testMethod()
File "C:\Users\Admin\PycharmProjects\vending-machine\AutomatTest.py", line 15, in test_checkPriceOfGivenID
self.assertEqual(automat.find_price_of_given_id(30), 2)
File "C:\Users\Admin\PycharmProjects\vending-machine\Automat.py", line 28, in find_price_of_given_id
raise NoItemException
exceptions.NoItemException.NoItemException
FAILED (errors=1)
Process finished with exit code 1
Automat class
from Item import Item
from exceptions.NoItemException import NoItemException
from exceptions.NoProperAmountException import NoProperAmountException
from Coin import Coin
from decimal import *
class Automat:
item_id = 30
def __init__(self, _bank, objects=None):
self.bank = _bank
if objects is None:
objects = {}
self.objects = objects
self.purchaseItemBank = []
def add_object(self, obj: Item):
id_to_assign = Automat.item_id
self.objects.update({id_to_assign: obj})
Automat.item_id = Automat.item_id + 1
return id_to_assign
def find_price_of_given_id(self, item_id):
if self.objects.get(item_id) is not None:
return self.objects.get(item_id).get_price()
else:
raise NoItemException
def find_amount_of_given_id(self, item_id):
if self.objects.get(item_id) is not None:
return self.objects.get(item_id).get_amount()
else:
raise NoItemException
def checkIfAmountIsPositive(self, item_id):
if self.objects.get(item_id) is not None:
var = True if self.objects.get(item_id).get_amount() > 0 else False
return var
else:
raise NoItemException
def withdrawItem(self, item_id):
self.objects.get(item_id).decrease()
def purchaseItem(self, item_id):
sumOfCoins = 0
if 30 <= item_id <= 50:
lista = []
for i in self.purchaseItemBank:
lista.append(i)
sumOfCoins += i.getCoinValue()
priceOfGivenProduct = self.find_price_of_given_id(item_id)
if sumOfCoins < priceOfGivenProduct:
if not self.checkIfAmountIsPositive(item_id):
return "Given amount it too small and " \
"no item with " + str(item_id) + " in automat!"
else:
raise NoProperAmountException
else:
if not self.checkIfAmountIsPositive(item_id):
return "No item with " + str(item_id) + " in automat!"
a = round(abs(Decimal(priceOfGivenProduct) - sumOfCoins), 2)
listaCopy = self.bank.getSortedBankListWithCoins()
if a > 0:
if len(listaCopy) == 0:
return "Nie mozna wydac"
for i, v in enumerate(self.bank.bank):
if a == 0:
break
elif a >= v.getCoinValue():
a = a - v.getCoinValue()
listaCopy.remove(v)
elif a < v.getCoinValue():
continue
if i + 1 == (len(self.bank.bank)):
return "Nie mozna wydac"
if a > 0:
return "Nie mozna wydac"
self.bank.bank = listaCopy.copy()
self.withdrawItem(item_id)
for iterator in lista:
self.bank.addMoney(iterator)
return "Wydano towar"
else:
raise NoItemException
Item class:
class Item:
def __init__(self, price, amount=5):
self.amount = amount
self.price = price
def get_price(self):
return self.price
def get_amount(self):
return self.amount
def decrease(self):
self.amount -= 1
def __str__(self):
return f"{self.amount} # {self.price}"
Bank class:
class Bank:
def __init__(self):
self.bank = []
def addMoney(self, value):
self.bank.append(value)
def getSumBank(self):
return sum(value.getCoinValue() for value in self.bank)
def getSumOfGivenCoins(self, *args):
suma = 0
for i in args:
suma += i.getCoinValue()
return suma
def getSortedBankListWithCoins(self):
self.bank.sort(key=lambda x: x.getCoinValue(), reverse=True)
listaCopy = self.bank.copy()
return listaCopy
Here:
class Automat:
item_id = 30
item_id is a class attribute - it's shared between all instances of Automat, which is probably how the tests are interfering with one another.
Update:
One way to fix the problem would be to reset the item_id in a setUp method.
What you need is to convert item_id to an instance variable:
class Automat:
def __init__(self, _bank, objects=None):
self.item_id = 30
# rest of init here ...
def add_object(self, obj: Item):
id_to_assign = self.item_id
self.objects.update({id_to_assign: obj})
self.item_id += 1
return id_to_assign
Now all ids will start at 30 and your unit tests will be able to find these items.