I am having a difficult time understanding how to use assertions in unit tests in Python.
Original code:
class A:
def method(file):
if file:
<do something>
else:
raise Exception("file not found")
Now to create its test. Let's say I don't want to pass a file and test it.
t1 = A()
class Test(TestCase):
def test_method_no_path(self):
t1.method(' ') #passed no file
<Now do what> ??
self.assert ??
# tests/test_ex.py
from os import path
from unittest import TestCase
class Error(Exception):
pass
class FileChecker:
def process_file(self, f_path: str):
if path.exists(f_path) and path.isfile(f_path):
# just an example
return 'result'
raise Error(f_path)
class TestFileChecker(TestCase):
_CHECKER = FileChecker()
def test_done(self):
# check method result
self.assertTrue(self._CHECKER.process_file('/tmp/1.txt') == 'result')
def test_error(self):
# check method exception
with self.assertRaises(Error):
self._CHECKER.process_file('/tmp/1.txt')
Run our test(nosetests tests/test_ex.py). test_done failed because /tmp/1.txt file does not exist:
======================================================================
ERROR: test_done (tests.test_ex.TestFileChecker)
Let's create a file(echo 'test' >> /tmp/1.txt) and run test one more time:
======================================================================
FAIL: test_error (tests.test_ex.TestFileChecker)
As you can see test_done works fine, because we got expected result, but now we have problems with test_error(because Error wasn't raised).
Related
I'm new in Python. I have a __init__ function with information about data sink config and I have to write some test cases fot it. But init is a function which doesn't return any result so confuses me. Can you show me some ways to implement test case for it ? Thank you.
My function :
class DataSinkConfig():
DEFAULT_ENTRIES_TABLE = 'entries'
DEFAULT_OUTLIERS_TABLE = 'outliers'
DEFAULT_OUTLIERCAUSES_TABLE = 'outlier_causes'
# Note
def __init__(self, dbconf):
insopt = dbconf.get('insopt')
if insopt is None:
self.entries_table_name = self.DEFAULT_ENTRIES_TABLE
self.outliers_table_name = self.DEFAULT_OUTLIERS_TABLE
self.outlier_causes_table_name = self.DEFAULT_OUTLIERCAUSES_TABLE
else:
try:
dict(insopt)
except Exception as e:
raise Exception(
"Invalid value {} for parameter 'insopt'.".format(insopt))
self.entries_table_name = self.__complement_item(insopt, 'entry_tbl', self.DEFAULT_ENTRIES_TABLE)
self.outliers_table_name = self.__complement_item(insopt, 'outlier_tbl', self.DEFAULT_OUTLIERS_TABLE)
self.outlier_causes_table_name = self.__complement_item(insopt, 'cause_tbl', self.DEFAULT_OUTLIERCAUSES_TABLE)
My test is stuck :
import unittest
import DataSinkConfig, DataSourceConfig
class TestDataSink(unittest.TestCase):
def setUp(self):
#pass
if __name__ == "__main__":
unittest.main()
You can do stuff like check the state on the object.
Obviously you can move DataSink instantiation to the setUp method.
import unittest
import DataSinkConfig, DataSourceConfig
class TestDataSink(unittest.TestCase):
def setUp(self):
#pass
def test_data_sink(self):
dsc = DataSinkConfig(db_conf)
self.assertEqual(dsc.entries_table_name, "WHATREVER COMPLIMENT ITEM WAS SUPPOSE TO SET")
if __name__ == "__main__":
unittest.main()
I have the following code:
import unittest, mock
class MockedRRData(object):
def to_text(self):
return '0 example.com.'
class MockedResponse(object):
answer = [[MockedRRData()]]
class MockedReturnValue(object):
response = MockedResponse()
class MockedDNSResolver(object):
def query(self, domain_name, query_type):
return MockedReturnValue()
class DNSQueryTest(unittest.TestCase):
def setUp(self):
# MockedRRData = mock.Mock(to_text=lambda: '0 example.com.')
# MockedResponse = mock.Mock(answer=[[MockedRRData()]])
# MockedReturnValue = mock.Mock()
# MockedReturnValue.attach_mock(MockedResponse, 'response')
# MockedReturnValue = mock.Mock(response=MockedResponse())
self.fake_dns_resolver = mock.Mock(query=lambda *args, **kwargs: MockedReturnValue())
def test_mock(self):
for rrset in self.fake_dns_resolver.query('a', 'b').response.answer:
for rrdata in rrset:
print(rrdata.to_text())
unittest.main()
It works, but I'd like to transition to fully using mock classes - as shown in the comments. The problem is that when I uncomment even just the last line of my comments, I get the following:
E
======================================================================
ERROR: test_mock (__main__.DNSQueryTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/a.py", line 33, in test_mock
for rrset in self.fake_dns_resolver.query('a', 'b').response.answer:
TypeError: 'Mock' object is not iterable
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
How do I fix that?
Apparently, I made the mistake of trying to instantiate mocks twice by adding extra () (mock.Mock returns an instance, not a class template). Here's the working code - compare the previously commented part.
import unittest, mock
class MockedRRData(object):
def to_text(self):
return '0 profound.mail.pairserver.com.'
class MockedResponse(object):
answer = [[MockedRRData()]]
class MockedReturnValue(object):
response = MockedResponse()
class MockedDNSResolver(object):
def query(self, domain_name, query_type):
return MockedReturnValue()
class DNSQueryTest(unittest.TestCase):
def setUp(self):
MockedRRData = mock.Mock(to_text=lambda: '0 profound.mail.pairserver.com.')
MockedResponse = mock.Mock(answer=[[MockedRRData]])
MockedReturnValue = mock.Mock(response=MockedResponse)
self.fake_dns_resolver = mock.Mock(query=lambda *args, **kwargs: MockedReturnValue)
def test_mock(self):
for rrset in self.fake_dns_resolver.query('a', 'b').response.answer:
for rrdata in rrset:
print(rrdata.to_text())
unittest.main()
After:
import unittest
loader = unittest.TestLoader()
tests = loader.discover('.')
testRunner = unittest.runner.TextTestRunner()
testResult = testRunner.run(tests)
I can get a list failures names and messages with:
for t in testResult.failures:
print t[0].id()
print t[1]
How to do the same for the successes?
I expect to be able to do it by overriding something in TextTestRunner. What is the easiest way?
Tested on Python 2.7.
elethan put me on the right direction: https://stackoverflow.com/a/40613695/895245
This is what I got after using TextTestRunner(resultclass= and adding a nice constructor:
import unittest
from unittest.runner import TextTestResult
class TextTestResultWithSuccesses(TextTestResult):
def __init__(self, *args, **kwargs):
super(TextTestResultWithSuccesses, self).__init__(*args, **kwargs)
self.successes = []
def addSuccess(self, test):
super(TextTestResultWithSuccesses, self).addSuccess(test)
self.successes.append(test)
loader = unittest.TestLoader()
tests = loader.discover('.')
testRunner = unittest.runner.TextTestRunner(resultclass=TextTestResultWithSuccesses)
testResult = testRunner.run(tests)
for t in testResult.failures:
print t[0].id()
print t[1]
print
for t in testResult.errors:
print t[0].id()
print t[1]
print
for t in testResult.successes:
print t.id()
I have done something similar in the past. The way I would do this is by first creating a test result class that in inherits from TestTextResult with a custom addSuccess method which will be called on each test success (addFailure is the method where the failing tests get appended to failures):
from unittest.runner import TextTestResult, TextTestRunner
class MyTestResult(TextTestResult):
successes = [] # As OP points out, this makes more sense as instance attribute
def addSuccess(self, test):
super(TextTestResult, self).addSuccess(test)
self.successes.append(test) # add other info if you want
and then make your own custom test runner, adding your custom test result as resultclass
class MyTestRunner(TextTestRunner):
resultclass = MyTestResult
# If this is your only customization, you can just pass
# the test runner class to `TextTestRunner`; see OP's answer
I'm writing a test runner. I have an object that can catch and store exceptions, which will be formatted as a string later as part of the test failure report. I'm trying to unit-test the procedure that formats the exception.
In my test setup, I don't want to actually throw an exception for my object to catch, mainly because it means that the traceback won't be predictable. (If the file changes length, the line numbers in the traceback will change.)
How can I attach a fake traceback to an exception, so that I can make assertions about the way it's formatted? Is this even possible? I'm using Python 3.3.
Simplified example:
class ExceptionCatcher(object):
def __init__(self, function_to_try):
self.f = function_to_try
self.exception = None
def try_run(self):
try:
self.f()
except Exception as e:
self.exception = e
def format_exception_catcher(catcher):
pass
# No implementation yet - I'm doing TDD.
# This'll probably use the 'traceback' module to stringify catcher.exception
class TestFormattingExceptions(unittest.TestCase):
def test_formatting(self):
catcher = ExceptionCatcher(None)
catcher.exception = ValueError("Oh no")
# do something to catcher.exception so that it has a traceback?
output_str = format_exception_catcher(catcher)
self.assertEquals(output_str,
"""Traceback (most recent call last):
File "nonexistent_file.py", line 100, in nonexistent_function
raise ValueError("Oh no")
ValueError: Oh no
""")
Reading the source of traceback.py pointed me in the right direction. Here's my hacky solution, which involves faking the frame and code objects which the traceback would normally hold references to.
import traceback
class FakeCode(object):
def __init__(self, co_filename, co_name):
self.co_filename = co_filename
self.co_name = co_name
class FakeFrame(object):
def __init__(self, f_code, f_globals):
self.f_code = f_code
self.f_globals = f_globals
class FakeTraceback(object):
def __init__(self, frames, line_nums):
if len(frames) != len(line_nums):
raise ValueError("Ya messed up!")
self._frames = frames
self._line_nums = line_nums
self.tb_frame = frames[0]
self.tb_lineno = line_nums[0]
#property
def tb_next(self):
if len(self._frames) > 1:
return FakeTraceback(self._frames[1:], self._line_nums[1:])
class FakeException(Exception):
def __init__(self, *args, **kwargs):
self._tb = None
super().__init__(*args, **kwargs)
#property
def __traceback__(self):
return self._tb
#__traceback__.setter
def __traceback__(self, value):
self._tb = value
def with_traceback(self, value):
self._tb = value
return self
code1 = FakeCode("made_up_filename.py", "non_existent_function")
code2 = FakeCode("another_non_existent_file.py", "another_non_existent_method")
frame1 = FakeFrame(code1, {})
frame2 = FakeFrame(code2, {})
tb = FakeTraceback([frame1, frame2], [1,3])
exc = FakeException("yo").with_traceback(tb)
print(''.join(traceback.format_exception(FakeException, exc, tb)))
# Traceback (most recent call last):
# File "made_up_filename.py", line 1, in non_existent_function
# File "another_non_existent_file.py", line 3, in another_non_existent_method
# FakeException: yo
Thanks to #User for providing FakeException, which is necessary because real exceptions type-check the argument to with_traceback().
This version does have a few limitations:
It doesn't print the lines of code for each stack frame, as a real
traceback would, because format_exception goes off to look for the
real file that the code came from (which doesn't exist in our case).
If you want to make this work, you need to insert fake data into
linecache's
cache (because traceback uses linecache to get hold of the source
code), per #User's answer
below.
You also can't actually raise exc and expect the fake traceback
to survive.
More generally, if you have client code that traverses tracebacks in
a different manner than traceback does (such as much of the inspect
module), these fakes probably won't work. You'd need to add whatever
extra attributes the client code expects.
These limitations are fine for my purposes - I'm just using it as a test double for code that calls traceback - but if you want to do more involved traceback manipulation, it looks like you might have to go down to the C level.
EDIT2:
That is the code of linecache.. I will comment on it.
def updatecache(filename, module_globals=None): # module_globals is a dict
# ...
if module_globals and '__loader__' in module_globals:
name = module_globals.get('__name__')
loader = module_globals['__loader__']
# module_globals = dict(__name__ = 'somename', __loader__ = loader)
get_source = getattr(loader, 'get_source', None)
# loader must have a 'get_source' function that returns the source
if name and get_source:
try:
data = get_source(name)
except (ImportError, IOError):
pass
else:
if data is None:
# No luck, the PEP302 loader cannot find the source
# for this module.
return []
cache[filename] = (
len(data), None,
[line+'\n' for line in data.splitlines()], fullname
)
return cache[filename][2]
That means before you testrun just do:
class Loader:
def get_source(self):
return 'source of the module'
import linecache
linecache.updatecache(filename, dict(__name__ = 'modulename without <> around',
__loader__ = Loader()))
and 'source of the module' is the source of the module you test.
EDIT1:
My solution so far:
class MyExeption(Exception):
_traceback = None
#property
def __traceback__(self):
return self._traceback
#__traceback__.setter
def __traceback__(self, value):
self._traceback = value
def with_traceback(self, tb_or_none):
self.__traceback__ = tb_or_none
return self
Now you can set the custom tracebacks of the exception:
e = MyExeption().with_traceback(1)
What you usually do if you reraise an exception:
raise e.with_traceback(fake_tb)
All exception prints walk through this function:
import traceback
traceback.print_exception(_type, _error, _traceback)
Hope it helps somehow.
You should be able to simply raise whatever fake exception you want where you want it in your test runs. The python exception docs suggest you create a class and raise that as your exception. It's section 8.5 of the docs.
http://docs.python.org/2/tutorial/errors.html
Should be pretty straightforward once you've got the class created.
I have a following function in Python and I want to test with unittest that if the function gets 0 as argument, it throws a warning. I already tried assertRaises, but since I don't raise the warning, that doesn't work.
def isZero(i):
if i != 0:
print "OK"
else:
warning = Warning("the input is 0!")
print warning
return i
Starting with Python 3.2, you can simply use assertWarns() method.
with self.assertWarns(Warning):
do_something()
You can use the catch_warnings context manager. Essentially this allows you to mock the warnings handler, so that you can verify details of the warning. See the official docs for a fuller explanation and sample test code.
import warnings
def fxn():
warnings.warn("deprecated", DeprecationWarning)
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
# Trigger a warning.
fxn()
# Verify some things
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated" in str(w[-1].message)
You can write your own assertWarns function to incapsulate catch_warnings context. I've just implemented it the following way, with a mixin:
class WarningTestMixin(object):
'A test which checks if the specified warning was raised'
def assertWarns(self, warning, callable, *args, **kwds):
with warnings.catch_warnings(record=True) as warning_list:
warnings.simplefilter('always')
result = callable(*args, **kwds)
self.assertTrue(any(item.category == warning for item in warning_list))
A usage example:
class SomeTest(WarningTestMixin, TestCase):
'Your testcase'
def test_something(self):
self.assertWarns(
UserWarning,
your_function_which_issues_a_warning,
5, 10, 'john', # args
foo='bar' # kwargs
)
The test will pass if at least one of the warnings issued by your_function is of type UserWarning.
#ire_and_curses' answer is quite useful and, I think, canonical. Here is another way to do the same thing. This one requires Michael Foord's excellent Mock library.
import unittest, warnings
from mock import patch_object
def isZero( i):
if i != 0:
print "OK"
else:
warnings.warn( "the input is 0!")
return i
class Foo(unittest.TestCase):
#patch_object(warnings, 'warn')
def test_is_zero_raises_warning(self, mock_warn):
isZero(0)
self.assertTrue(mock_warn.called)
if __name__ == '__main__':
unittest.main()
The nifty patch_object lets you mock out the warn method.
One problem with the warnings.catch_warnings approach is that warnings produced in different tests can interact in strange ways through global state kept in __warningregistry__ attributes.
To address this, we should clear the __warningregistry__ attribute of every module before every test that checks warnings.
class MyTest(unittest.TestCase):
def setUp(self):
# The __warningregistry__'s need to be in a pristine state for tests
# to work properly.
for v in sys.modules.values():
if getattr(v, '__warningregistry__', None):
v.__warningregistry__ = {}
def test_something(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always", MySpecialWarning)
...
self.assertEqual(len(w), 1)
self.assertIsInstance(w[0].message, MySpecialWarning)
This is how Python 3's assertWarns() method is implemented.
Building off the answer from #ire_and_curses,
class AssertWarns(warnings.catch_warnings):
"""A Python 2 compatible version of `unittest.TestCase.assertWarns`."""
def __init__(self, test_case, warning_type):
self.test_case = test_case
self.warning_type = warning_type
self.log = None
super(AssertWarns, self).__init__(record=True, module=None)
def __enter__(self):
self.log = super(AssertWarns, self).__enter__()
return self.log
def __exit__(self, *exc_info):
super(AssertWarns, self).__exit__(*exc_info)
self.test_case.assertEqual(type(self.log[0]), self.warning_type)
This can be called similarly to unittest.TestCase.assertWarns:
with AssertWarns(self, warnings.WarningMessage):
warnings.warn('test warning!')
where self is a unittest.TestCase.
Per Melebius' answer, you can use self.assertWarns().
Additionally, if you want to check the warning message as well, you can use self.assertWarnsRegex() for that greater specificity:
import warnings
from unittest import TestCase
class MyCustomWarning(Warning):
...
def is_zero(i: int) -> int:
if i != 0:
print("OK")
else:
warnings.warn("the input is 0!", MyCustomWarning)
return i
class TestIsZero(TestCase):
def test_when_then_input_is_zero(self):
regex = "the input is 0"
with self.assertWarnsRegex(MyCustomWarning, expected_regex=regex):
_ = is_zero(0)
This test will fail if the regex is not found in the warning message.