I'm currently trying to run a number of tests against a JSON string there are however a few difficulties that I am encountering.
Here's what I have so far.
class PinpyTests(jsonstr, campaign):
data = json.loads(jsonstr)
test = False
def dwellTest(self):
if self.data.get('dwellTime', None) is not None:
if self.data.get('dwellTime') >= self.campaign.needed_dwellTime:
# Result matches, dwell time test passed.
self.test = True
def proximityTest(self):
if self.data.get('proximity', None) is not None:
if self.data.get('proximity') == self.campaign.needed_proximity:
# Result matches, proximity passed.
self.test = True
Basically, I need the tests to be run, only if they exist in the json string. so if proximity is present in the string, it will run the proximity test, etc etc. (there could be more tests, not just these two)
The issue seems to arise when both tests are present, and need to both return true. If they both return true then the test has passed and the class can return true, However, if dwell fails, and proximity passes I still need it to fail because not all the tests pass. (where proximity makes it pass). I'm slightly baffled as how to continue.
For starters, your class is defined incorrectly. What you probably want is an __init__ function. To achieve your desired result, I would suggest adding a testAll method that checks for each test in your json then runs that test.
class PinpyTests(Object):
test = False
def __init__(self, jsonstr, campaign):
self.data = json.loads(jsonstr)
self.campaign = campaign
def testAll(self):
passed = True
if self.data.get('dwellTime') is not None:
passed = passed and self.dwellTest()
if self.data.get('proximity') is not None:
passed = passed and self.proximityTest()
return passed
def dwellTest(self):
if self.data.get('dwellTime') >= self.campaign.needed_dwellTime:
# Result matches, dwell time test passed.
return True
return False
def proximityTest(self):
if self.data.get('proximity') == self.campaign.needed_proximity:
# Result matches, proximity passed.
return True
return False
Related
I want to be able to mock a function that mutates an argument, and that it's mutation is relevant in order for the code to continue executing correctly.
Consider the following code:
def mutate_my_dict(mutable_dict):
if os.path.exists("a.txt"):
mutable_dict["new_key"] = "new_value"
return True
def function_under_test():
my_dict = {"key": "value"}
if mutate_my_dict(my_dict):
return my_dict["new_key"]
return "No Key"
def test_function_under_test():
with patch("stack_over_flow.mutate_my_dict") as mutate_my_dict_mock:
mutate_my_dict_mock.return_value = True
result = function_under_test()
assert result == "new_value"
**Please understand i know i can just mock os.path.exists in this case but this is just an example. I intentionally want to mock the function and not the external module.
**
I also read the docs here:
https://docs.python.org/3/library/unittest.mock-examples.html#coping-with-mutable-arguments
But it doesn't seem to fit in my case.
This is the test i've written so far, but it obviously doesn't work since the key changes:
def test_function_under_test():
with patch("stack_over_flow.mutate_my_dict") as mutate_my_dict_mock:
mutate_my_dict_mock.return_value = True
result = function_under_test()
assert result == "new_value"
Thanks in advance for all of your time :)
With the help of Peter i managed to come up with this final test:
def mock_mutate_my_dict(my_dict):
my_dict["new_key"] = "new_value"
return True
def test_function_under_test():
with patch("stack_over_flow.mutate_my_dict") as mutate_my_dict_mock:
mutate_my_dict_mock.side_effect = mock_mutate_my_dict
result = function_under_test()
assert result == "new_value"
How it works is that with a side effect you can run a function instead of the intended function.
In this function you need to both change all of the mutating arguments and return the value returned.
I created a class to make my life easier while doing some integration tests involving workers and their contracts. The code looks like this:
class ContractID(str):
contract_counter = 0
contract_list = list()
def __new__(cls):
cls.contract_counter += 1
new_entry = super().__new__(cls, f'Some_internal_name-{cls.contract_counter:10d}')
cls.contract_list.append(new_entry)
return new_entry
#classmethod
def get_contract_no(cls, worker_number):
return cls.contract_list[worker_number-1] # -1 so WORKER1 has contract #1 and not #0 etc.
When I'm unit-testing the class, I'm using the following code:
from test_helpers import ContractID
#pytest.fixture
def get_contract_numbers():
test_string_1 = ContractID()
test_string_2 = ContractID()
test_string_3 = ContractID()
return test_string_1, test_string_2, test_string_3
def test_contract_id(get_contract_numbers):
assert get_contract_ids[0] == 'Some_internal_name-0000000001'
assert get_contract_ids[1] == 'Some_internal_name-0000000002'
assert get_contract_ids[2] == 'Some_internal_name-0000000003'
def test_contract_id_get_contract_no(get_contract_numbers):
assert ContractID.get_contract_no(1) == 'Some_internal_name-0000000001'
assert ContractID.get_contract_no(2) == 'Some_internal_name-0000000002'
assert ContractID.get_contract_no(3) == 'Some_internal_name-0000000003'
with pytest.raises(IndexError) as py_e:
ContractID.get_contract_no(4)
assert py_e.type == IndexError
However, when I try to run these tests, the second one (test_contract_id_get_contract_no) fails, because it does not raise the error as there are more than three values. Furthermore, when I try to run all my tests in my folder test/, it fails even the first test (test_contract_id), which is probably because I'm trying to use this function in other tests that run before this test.
After reading this book, my understanding of fixtures was that it provides objects as if they were never called before, which is obviously not the case here. Is there a way how to tell the tests to use the class as if it hasn't been used before anywhere else?
If I understand that correctly, you want to run the fixture as setup code, so that your class has exactly 3 instances. If the fixture is function-scoped (the default) it is indeed run before each test, which will each time create 3 new instances for your class. If you want to reset your class after the test, you have to do this yourself - there is no way pytest can guess what you want to do here.
So, a working solution would be something like this:
#pytest.fixture(autouse=True)
def get_contract_numbers():
test_string_1 = ContractID()
test_string_2 = ContractID()
test_string_3 = ContractID()
yield
ContractID.contract_counter = 0
ContractID.contract_list.clear()
def test_contract_id():
...
Note that I did not yield the test strings, as you don't need them in the shown tests - if you need them, you can yield them, of course. I also added autouse=True, which makes sense if you need this for all tests, so you don't have to reference the fixture in each test.
Another possibility would be to use a session-scoped fixture. In this case the setup would be done only once. If that is what you need, you can use this instead:
#pytest.fixture(autouse=True, scope="session")
def get_contract_numbers():
test_string_1 = ContractID()
test_string_2 = ContractID()
test_string_3 = ContractID()
yield
the purpose of #mark.incremental is that if one test fails, the tests afterwards are marked as expected to fail.
However, when I use this in conjuction with parametrization I get undesired behavior.
For example, in the case of this fake code:
//conftest.py:
def pytest_generate_tests(metafunc):
metafunc.parametrize("input", [True, False, None, False, True])
def pytest_runtest_makereport(item, call):
if "incremental" in item.keywords:
if call.excinfo is not None:
parent = item.parent
parent._previousfailed = item
def pytest_runtest_setup(item):
if "incremental" in item.keywords:
previousfailed = getattr(item.parent, "_previousfailed", None)
if previousfailed is not None:
pytest.xfail("previous test failed (%s)" %previousfailed.name)
//test.py:
#pytest.mark.incremental
class TestClass:
def test_input(self, input):
assert input is not None
def test_correct(self, input):
assert input==True
I'd expect the test class to run
test_input on True,
followed by test_correct on True,
followed by test_input on False,
followed by test_correct on False,
folowed by test_input on None,
followed by (xfailed) test_correct on None, etc etc.
Instead, what happens is that the test class
runs test_input on True,
then runs test_input on False,
then runs test_input on None,
then marks everything from that point onwards as xfailed (including the test_corrects).
What I am assuming is happening is that parametrization takes priority over proceeding through functions in a class. The question is if it is possible to override this behaviour or work around it somehow, as the current situation makes marking a class as incremental completely useless to me.
(is the only way to handle this to copy-paste the code for the class over and over, each time with different parameters? The thought is repulsive to me)
The solution to this is described in https://docs.pytest.org/en/latest/example/parametrize.html under the header A quick port of “testscenarios”
This is the code listed there and what the code in conftest.py is doing is it is looking for variable scenarios in the test class. When it finds the variable it iterates over each item of scenarios and expects an id string with which to label the test and a dictionary of 'argnames:argvalues'
# content of conftest.py
def pytest_generate_tests(metafunc):
idlist = []
argvalues = []
for scenario in metafunc.cls.scenarios:
idlist.append(scenario[0])
items = scenario[1].items()
argnames = [x[0] for x in items]
argvalues.append(([x[1] for x in items]))
metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
# content of test_scenarios.py
scenario1 = ('basic', {'attribute': 'value'})
scenario2 = ('advanced', {'attribute': 'value2'})
class TestSampleWithScenarios(object):
scenarios = [scenario1, scenario2]
def test_demo1(self, attribute):
assert isinstance(attribute, str)
def test_demo2(self, attribute):
assert isinstance(attribute, str)
You can also modify the function pytest_generate_tests to accept different datatype inputs. For example if you have a list that you usually pass to
#pytest.mark.parametrize("varname", varval_list)
you can use that same list in the following way:
# content of conftest.py
def pytest_generate_tests(metafunc):
idlist = []
argvalues = []
argnames = metafunc.cls.scenario_keys
for idx, scenario in enumerate(metafunc.cls.scenario_parameters):
idlist.append(str(idx))
argvalues.append([scenario])
metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
# content of test_scenarios.py
varval_list = [a, b, c, d]
class TestSampleWithScenarios(object):
scenario_parameters = varval_list
scenario_keys = ['varname']
def test_demo1(self, attribute):
assert isinstance(attribute, str)
def test_demo2(self, attribute):
assert isinstance(attribute, str)
The id will be an autogenerated number (you can change that to using something you specify) and in this implementation it won't handle multiple parameterization variables so you have to compile those in a single list (or cater pytest_generate_tests to handle that for you)
The following solution does not ask to change your test class
_test_failed_incremental = defaultdict(dict)
def pytest_runtest_makereport(item, call):
if "incremental" in item.keywords:
if call.excinfo is not None and call.excinfo.typename != "Skipped":
param = tuple(item.callspec.indices.values()) if hasattr(item, "callspec") else ()
_test_failed_incremental[str(item.cls)].setdefault(param, item.originalname or item.name)
def pytest_runtest_setup(item):
if "incremental" in item.keywords:
param = tuple(item.callspec.indices.values()) if hasattr(item, "callspec") else ()
originalname = _test_failed_incremental[str(item.cls)].get(param)
if originalname:
pytest.xfail("previous test failed ({})".format(originalname))
It works by keeping a dictionary with the failed test per class and per index of parametrized input as key (and the name of the test method that failed as value).
In your example, the dictionary _test_failed_incremental will be
defaultdict(<class 'dict'>, {"<class 'test.TestClass'>": {(2,): 'test_input'}})
showing that the 3rd run (index=2) has failed for the class test.TestClass.
Before running a test method in the class for a given parameter, it checks if any previous test method in the class has not failed for the given parameter and if so xfail the test with info on the name of the method that first failed.
Not 100% tested but in use and working for my needs.
This is the code from my recent tool I have made, I am trying to write unittest, I have an idea of how to test a method that returns something but don't understand how should i test method that don't like below:
def buildUi(self):
self.label = QtGui.QLabel()
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.setCentralWidget(self.label)
def nextImage(self):
""" switch to next image or previous image
"""
if self._imagesInList:
if self._count == len(self._imagesInList):
self._count = 0
self.showImageByPath(
self._imagesInList[self._count])
if self.animFlag:
self._count += 1
else:
self._count -= 1
def showImageByPath(self, path):
if path:
image = QtGui.QImage(path)
pp = QtGui.QPixmap.fromImage(image)
self.label.setPixmap(pp.scaled(
self.label.size(),
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation))
def playPause(self):
if not self._pause:
self._pause = True
self.updateTimer.start(2500)
return self._pause
else:
self._pause = False
self.updateTimer.stop()
def keyPressEvent(self, keyevent):
""" Capture key to exit, next image, previous image,
on Escape , Key Right and key left respectively.
"""
event = keyevent.key()
if event == QtCore.Qt.Key_Escape:
self.close()
if event == QtCore.Qt.Key_Left:
self.animFlag = False
self.nextImage()
if event == QtCore.Qt.Key_Right:
self.animFlag = True
self.nextImage()
if event == 32:
self._pause = self.playPause()
the complete code for looking can be found here
is it possible to test these methods above or do I have to modify to make them testable ?
Edit: updated:
class TestSlideShow(unittest.TestCase):
""" docstring for TestSlideShow
"""
def setUp(self):
self.mox = mox.Mox()
self.imgLst = ['/folder/test/images/test1.jpg', '/folder/test/images/test2.JPG',
'/folder/test/images/test3.png', '/folder/test/images/test4.PNG']
app = QtGui.QApplication([])
self.show = slideShow.SlideShowPics(imgLst=self.imgLst, ui=False)
def tearDown(self):
self.mox.UnsetStubs()
self.mox.ResetAll()
def test_nextImage(self):
self.mox.StubOutWithMock(self.show, 'prepairWindow')
self.show.prepairWindow()
self.mox.StubOutWithMock(self.show, 'showImageByPath')
self.show.showImageByPath(self.imgLst[1])
self.show.nextImage()
# self.mox.ReplayAll()
self.assertEquals(1, self.show.count)
self.assertEquals(self.imgLst[1], self.show._imagesInList[1])
# self.mox.VerifyAll()
def test_nextImage_animFlag_False(self):
self.show.animFlag = False
self.show.count = 4
self.mox.StubOutWithMock(self.show, 'prepairWindow')
self.show.prepairWindow()
self.mox.StubOutWithMock(self.show, 'showImageByPath')
self.show.showImageByPath(self.imgLst[3])
print self.show.count
self.show.nextImage()
print self.show.count
# self.assertEquals(3, self.show.count)
self.assertEquals(self.imgLst[3], self.show._imagesInList[3])
if __name__ == '__main__':
unittest.main()
the first test when self.show.animFlag is True works fine and but the when I manually set the animFlag= False then second test fails.
This is the problem with writing unittest after the code - you then realize your code is difficult to test. Writing the tests before the code (well, really "along" the code - you don't write all tests before start coding, but still you dont write a line of code before you have a test for it) makes sure you don't have such a problem.
Now even with the "test first" approach you do have to test methods that don't return anything. The way to do so is to test for the expected side effects. Some of these side effects are easy to test - in your above case, you can test the value of self._count before and after the call to nextImage, depending on your object's state (_imagesInList and animFlag mostly). Where it gets more difficult is if you want to test that nextImage does actually call showImageByPath with the correct arguments, and with your current design the only way to do so is to monkeypatch showImageByPath for the tests. Testing showImageByPath will require patching / mocking self.label.setPixmap(), etc.
As others already pointed there are a couple mock/stub libs that can help, but they won't solve all possible testability issues and you may have to rethink your design to make things easier - like not hardcoding the call to QtGui.QLabel() in buildUI() but have some way to "inject" the desired componant (QtGui.QLabel() or a mock) instead. As a general rule, testable code has very few hard-coded dependencies, few side effects, and lot of small classes with short methods (instead of huge classes with long methods).
I have this class called DecayingSet which is a deque with expiration
class DecayingSet:
def __init__(self, timeout): # timeout in seconds
from collections import deque
self.timeout = timeout
self.d = deque()
self.present = set()
def add(self, thing):
# Return True if `thing` not already in set,
# else return False.
result = thing not in self.present
if result:
self.present.add(thing)
self.d.append((time(), thing))
self.clean()
return result
def clean(self):
# forget stuff added >= `timeout` seconds ago
now = time()
d = self.d
while d and now - d[0][0] >= self.timeout:
_, thing = d.popleft()
self.present.remove(thing)
I'm trying to use it inside a running script, that connects to a streaming api.
The streaming api is returning urls that I am trying to put inside the deque to limit them from entering the next step of the program.
class CustomStreamListener(tweepy.StreamListener):
def on_status(self, status, include_entities=True):
longUrl = status.entities['urls'][0]['expanded_url']
limit = DecayingSet(86400)
l = limit.add(longUrl)
print l
if l == False:
pass
else:
r = requests.get("http://api.some.url/show?url=%s"% longUrl)
When i use this class in an interpreter, everything is good.
But when the script is running, and I repeatedly send in the same url, l returns True every time indicating that the url is not inside the set, when is supposed to be. What gives?
Copying my comment ;-) I think the indentation is screwed up, but it looks like you're creating a brand new limit object every time on_status() is called. Then of course it would always return True: you'd always be starting with an empty limit.
Regardless, change this:
l = limit.add(longUrl)
print l
if l == False:
pass
else:
r = requests.get("http://api.some.url/show?url=%s"% longUrl)
to this:
if limit.add(longUrl):
r = requests.get("http://api.some.url/show?url=%s"% longUrl)
Much easier to follow. It's usually the case that when you're comparing something to a literal True or False, the code can be made more readable.
Edit
i just saw in the interpreter the var assignment is the culprit.
How would I use the same obj?
You could, for example, create the limit object at the module level. Cut and paste ;-)