Wrap each pytest test function into try-except - python

I want to wrap each of my test functions into a try-except block to execute code in the except block. This code should only be executed if the test is failing.
I want to achieve this without altering the test functions, but instead use some kind of decorator/fixture. Unfortunately I can not find any examples.
Example of what I'm trying to achieve:
def test_1():
some_method_that_might_throw_an_exception()
I have multiple tests and all of them should run a function run_only_if_exception_was_thrown() if an exception was thrown by the test.
I want to achieve this without using a try/catch block inside the tests.
My current approach is to use sys.last_value inside a fixture to check if an exception was thrown:
#pytest.fixture
def fix():
yield X()
try:
if sys.last_value:
# error
except AttributeError:
# no error thrown
def test1(fix):
some_method_that_might_throw_an_exception()

How about this:
def test_dec(test_func):
def test_wrapper(fix):
try:
test_func(fix)
except:
run_only_if_exception_was_thrown(fix)
# re-raise exception to make the test fail
raise
return test_wrapper
Then in your test suite:
...
#test_dec
def test_one(fix):
# test code

Related

Is there a way to force an exception to be raised if there is a try catch that catches all exceptions?

I need to force an exception to be raised outside a function that does this:
def foo(x):
try:
some_calculation(x)
except:
print("ignore exception")
Is there a way to override the catch-all inside foo? I would like to raise an exception inside some_calculation(x) that can be caught or detected outside foo.
FYI, foo is a third party function I have no control over.
No. Your options are:
submit a fix to the library maintainers
fork the library, and provide your own vendorised version
monkey patch the library on import
The last is perhaps the easiest and quickest to get up and running.
Example:
main.py
# before doing anything else import and patch the third party library
# we need to patch foo before anyone else has a chance to import or use it
import third_party_library
# based off of third_party_library version 1.2.3
# we only catch Exception rather than a bare except
def foo(x):
try:
some_calculation(x)
except Exception:
print("ignore exception")
third_party_library.foo = foo
# rest of program as usual
...
Things might be slightly more complicated than that if foo() is re-exported across several different modules (if the third party library has its own from <x> import foo statements. But if just requires monkey patching more attributes of the various re-exporting modules.
Technically it would be possible to force an exception to be raised, but it would involve setting an execution trace and forcing an exception to be thrown in the exception handling code of the foo(). It would be weird, the exception would appear to come from print("ignore exception") rather than
some_calculation(x). So don't do that.

How to catch a custom exception not defined by me in python

I have the following try block:
try:
# depending on conditions, this can generate several types of errors
mymodule.do_stuff()
except Exception as e:
print("something went wrong, the error was :" + type(e).__name__)
I would like to catch potential errors from do_stuff(). After trial and error I was able to generate a list of potential errors that can be triggered by do_stuff() by printing their type(e).__name__ value:
DoStuffInsufficientMemoryException
DoStuffInsufficientCPUException
DoStuffInsufficientDiskException
but if I try do modify my except statement from except Exception as e to except DoStuffInsufficientMemoryException, I will get the error that DoStuffInsufficientMemoryException is not defined.
I tried defining a class that extends Exception for it, as most tutorials / questions in here suggest, basically:
class WAFInvalidParameterException(Exception):
pass
so now that variable is recognized, but since I can't control the error that do_sutff() will raise, I can't really raise this exception in my initial try block.
Ideally I would like to have 1 except block for each error so I would like to have something like:
try:
mymodule.do_stuff()
except DoStuffInsufficientMemoryException:
free_memory()
except DoStuffInsufficientCPUException:
kill_processes()
except DoStuffInsufficientDiskException:
free_disk_space()
but of course this doesn't work as these variables are not defined.
Just like you can't reference do_stuff without its module specifier, you have to specify in which module namespace these exceptions are defined.
try:
mymodule.do_stuff()
except mymodule.DoStuffInsufficientMemoryException:
free_memory()
except mymodule.DoStuffInsufficientCPUException:
kill_processes()
except mymodule.DoStuffInsufficientDiskException:
free_disk_space()
If free_memory is also in the mymodule namespace, of course you need to specify it there as well.
Alternatively, when you import mymodule, you can explicitly import selected symbols into the current namespace:
from mymodule import do_stuff, DoStuffInsufficientMemoryException, ...
and then, because they are in the current package, you can (or indeed must) refer to them without the package prefix mymodule.
A well-designed module will export selected symbols so you can refer to them without the package prefix, but whether this makes sense for your own package depends on its general design and intended audience. Some large packages define a separate subpackage for exceptions so you can say
import bigpackage.exceptions
to import them all. You will probably still want to explore the package's documentation (or, if it's lacking, its source code) to discover which exceptions exist and how they are organized. Many packages define a base exception class from which all its other exceptions are subclasses so that you can easily catch them all with just one symbol, like
try:
bigpackage.heavy_wizardry()
except bigpackage.BigBaseException:
print("you are turned into a frog")
EDIT : you can import other methods instead of creating your own, of course
The try/except block will try to execute the code and if an error is raised and specified in the except statement, it will stop the execution of the code located in the try block and execute the other code located in the except block. So, to catch your custom error, you have to raise it in the first place.
If you didn't know, you can raise errors using the raise statement. Here, I've made a simple chunk of code. I have a custom error, a variable x initialized at 2, and a method that adds 1 to the variable given in argument. The method will raise a CustomError if the variable becomes 3.
# Here, I define the custom error and allow a custom message to be displayed
# using the super() method
class CustomError(Exception):
def __init__(self, msg):
super().__init__(msg)
# I initialize x at 2
x = 2
# I create the method that will add 1 to the variable given in argument
# and raise a CustomError if the variable becomes 3
# This is completely random, and you can make whatever checks you want before raising
# Your custom error
def do_stuff(x):
x += 1
if x == 3:
raise CustomError("x is 3")
# Now, I write the try/except block. I raise e (the CustomError) if it is
# being raised in the method do_stuff(x)
try:
do_stuff(x)
except CustomError as e:
raise e
Feel free to try the code out to get a better understanding of it !
Usually if a function
module.foo()
throws an exception DoStuffInsufficientMemoryException it would be also importable as
from module import DoStuffInsufficientMemoryException
If this results in ImportError then you need the fullname function from this answer; use it with e (it takes an instance and returns the class name). If it gives
foo.bar.DoStuffInsufficientMemoryException
then you can import the exception as
from foo.bar import DoStuffInsufficientMemoryException
The above might not work for all cases. One notable case is Boto 3 AWS client library that does not make the exceptions importable - instead they will be attributes on the client instance.

unit test for custom exception (chained exception python 3)

Working on testing custom based exceptions within python3, within the client code I have:
class myCustomException(Exception):
pass
def someFunc():
try:
mathCheck = 2/0
print(mathCheck)
except ZeroDivisionError as e:
raise myCustomException from e
On the test side:
def testExceptionCase(self):
with self.assertRaises(ZeroDivisionError) as captureException:
self.someFunc()
My question is:
How to essentially capture the chained exception i.e. the myCustomException using unittest (so proving that the custom exception did get called and raised from the base exception which is ZeroDivisonError), assume I have already done the import of unittest, and imports within client-test files.
Is there a way to say we were able to keep track of the traceback chaining from ZeroDivisionError and myCustomException. Basically, this test should also fail if it didn't raise the myCustomException. Appreciate any help!
from client import MyCustomException
def testExceptionCase(self):
with self.assertRaises(MyCustomException):
self.someFunc()
Also you may want to use UpperCamelCase for Exceptions and Classes in general

Python Requests Mock doesn't catch Timeout exception

I wrote a unittest to test timeout with the requests package
my_module.py:
import requests
class MyException(Exception): pass
def my_method():
try:
r = requests.get(...)
except requests.exceptions.Timeout:
raise MyException()
Unittest:
from mock import patch
from unittest import TestCase
from requests.exceptions import Timeout
from my_module import MyException
#patch('my_module.requests')
class MyUnitTest(TestCase):
def my_test(self, requests):
def get(*args, **kwargs):
raise Timeout()
requests.get = get
try:
my_module.my_method(...)
except MyException:
return
self.fail("No Timeout)
But when it runs, the try block in my_method never catches the requests.exceptions.Timeout
There are two problems I see here. One that directly fixes your problem, and the second is a slight misuse of the Mocking framework that further simplifies your implementation.
First, to directly address your issue, based on how you are looking to test your assertion, what you are actually looking to do here:
requests.get = get
You should be using a side_effect here to help raise your exception. Per the documentation:
side_effect allows you to perform side effects, including raising an
exception when a mock is called
With that in mind, all you really need to do is this:
requests.get.side_effect = get
That should get your exception to raise. However, chances are you might face this error:
TypeError: catching classes that do not inherit from BaseException is not allowed
This can be best explained by actually reading this great answer about why that is happening. With that answer, taking that suggestion to actually only mock out what you need will help fully resolve your issue. So, in the end, your code will actually look something like this, with the mocked get instead of mocked requests module:
class MyUnitTest(unittest.TestCase):
#patch('my_module.requests.get')
def test_my_test(self, m_get):
def get(*args, **kwargs):
raise Timeout()
m_get.side_effect = get
try:
my_method()
except MyException:
return
You can now actually further simplify this by making better use of what is in unittest with assertRaises instead of the try/except. This will ultimately just assert that the exception was raised when the method is called. Furthermore, you do not need to create a new method that will raise a timeout, you can actually simply state that your mocked get will have a side_effect that raises an exception. So you can replace that entire def get with simply this:
m_get.side_effect = Timeout()
However, you can actually directly put this in to your patch decorator, so, now your final code will look like this:
class MyUnitTest(unittest.TestCase):
#patch('my_module.requests.get', side_effect=Timeout())
def test_my_test(self, m_get):
with self.assertRaises(MyException):
my_method()
I hope this helps!
patch('my_module.requests') will replace my_module.requests with a new mock object, but in your test method you replace the requests.get method of the directly imported and therefore on the original requests module, which means that change is not reflected within your module.
It should work if in your test method you replace it on the requests mock within your my_module instead:
my_module.requests.get = get

How to stop all tests from inside a test or setUp using unittest?

I'm extending the python 2.7 unittest framework to do some function testing. One of the things I would like to do is to stop all the tests from running inside of a test, and inside of a setUpClass() method. Sometimes if a test fails, the program is so broken it is no longer of any use to keep testing, so I want to stop the tests from running.
I noticed that a TestResult has a shouldStop attribute, and a stop() method, but I'm not sure how to get access to that inside of a test.
Does anyone have any ideas? Is there a better way?
In case you are interested, here is a simple example how you could make a decision yourself about exiting a test suite cleanly with py.test:
# content of test_module.py
import pytest
counter = 0
def setup_function(func):
global counter
counter += 1
if counter >=3:
pytest.exit("decided to stop the test run")
def test_one():
pass
def test_two():
pass
def test_three():
pass
and if you run this you get:
$ pytest test_module.py
============== test session starts =================
platform linux2 -- Python 2.6.5 -- pytest-1.4.0a1
test path 1: test_module.py
test_module.py ..
!!!! Exit: decided to stop the test run !!!!!!!!!!!!
============= 2 passed in 0.08 seconds =============
You can also put the py.test.exit() call inside a test or into a project-specific plugin.
Sidenote: py.test natively supports py.test --maxfail=NUM to implement stopping after NUM failures.
Sidenote2: py.test has only limited support for running tests in the traditional unittest.TestCase style.
Here's another answer I came up with after a while:
First, I added a new exception:
class StopTests(Exception):
"""
Raise this exception in a test to stop the test run.
"""
pass
then I added a new assert to my child test class:
def assertStopTestsIfFalse(self, statement, reason=''):
try:
assert statement
except AssertionError:
result.addFailure(self, sys.exc_info())
and last I overrode the run function to include this right below the testMethod() call:
except StopTests:
result.addFailure(self, sys.exc_info())
result.stop()
I like this better since any test now has the ability to stop all the tests, and there is no cpython-specific code.
Currently, you can only stop the tests at the suite level. Once you are in a TestCase, the stop() method for the TestResult is not used when iterating through the tests.
Somewhat related to your question, if you are using python 2.7, you can use the -f/--failfast flag when calling your test with python -m unittest. This will stop the test at the first failure.
See 25.3.2.1. failfast, catch and buffer command line options
You can also consider using Nose to run your tests and use the -x, --stop flag to stop the test early.
In the test loop of unittest.TestSuite, there is a break condition at the start:
class TestSuite(BaseTestSuite):
def run(self, result, debug=False):
topLevel = False
if getattr(result, '_testRunEntered', False) is False:
result._testRunEntered = topLevel = True
for test in self:
if result.shouldStop:
break
So I am using a custom test suite like this:
class CustomTestSuite(unittest.TestSuite):
""" This variant registers the test result object with all ScriptedTests,
so that a failed Loign test can abort the test suite by setting result.shouldStop
to True
"""
def run(self, result, debug=False):
for test in self._tests:
test.result = result
return super(CustomTestSuite, self).run(result, debug)
with a custom test result class like this:
class CustomTestResult(TextTestResult):
def __init__(self, stream, descriptions, verbosity):
super(CustomTestResult, self).__init__(stream, descriptions, verbosity)
self.verbosity = verbosity
self.shouldStop = False
and my test classes are like:
class ScriptedTest(unittest.TestCase):
def __init__(self, environment, test_cfg, module, test):
super(ScriptedTest, self).__init__()
self.result = None
Under certain conditions, I then abort the test suite; for example, the test suite starts with a login, and if that fails, I do not have to try the rest:
try:
test_case.execute_script(test_command_list)
except AssertionError as e:
if test_case.module == 'session' and test_case.test == 'Login':
test_case.result.shouldStop = True
raise TestFatal('Login failed, aborting test.')
else:
raise sys.exc_info()
Then I use the test suite in the following way:
suite = CustomTestSuite()
self.add_tests(suite)
result = unittest.TextTestRunner(verbosity=self.environment.verbosity, stream=UnitTestLoggerStream(self.logger),
resultclass=CustomTestResult).run(suite)
I'm not sure if there is a better way to do it, but it behaves correctly for my tests.
Though you won't get the usual test reports of the tests run so far, a very easy way to stop the test run from within a TestCase method is simply to raise KeyboardInterrupt inside the method.
You can see how only KeyboardInterrupt is allowed to bubble up inside unittest's test runner by looking at CPython's code here inside testPartExecutor().
The OP was about python 2.7. Skip ahead a decade, and for python 3.1 and above, the way to skip tests in python unittest has had an upgrade, but the documentation could use some clarification (IMHO):
The documentation covers the following:
Skip All tests after first failure: use failfast (only useful if you really don't want to continue any further tests at all, including in other unrelated TestCase classes)
Skip All tests in a TestCase class: decorate class with #unittest.skip(), etc.
Skip a single method within a TestCase: decorate method with #unittest.skip(), etc.
Conditionally skip a method or a class: decorate with #unittest.skipIf() or #unittest.skipUnless() etc.
Conditionally skip a method, but not until something within that method runs: use self.skipTest() inside the method (this will skip that method, and ONLY that method, not subsequent methods)
The documentation does not cover the following (as of this writing):
Skip all tests within a TestCase class if a condition is met inside the setUpClass method: solution from this post raise unittest.SkipTest("skip all tests in this class") (there may be another way, but I'm unaware)
Skip all subsequent test methods in a TestCase class after a condition is met in one of the first tests, but still continue to test other unrelated TestCase classes. For this, I propose the following solution...
This solution assumes that you encounter the "bad state" in the middle of a test method, and which could only be noticed in a test method ONLY (i.e., it is not something that could have been determined in the setUpClass method, for whatever reason). Indeed the setUpClass method is the best location for determining whether to proceed if the initial conditions aren't right, but sometimes (as I've encountered) you just don't know until you run some test method. This solution assumes that test methods are in alphabetical order and that subsequent tests methods that you don't want to run after encountering the "bad" state follow alphabetically.
import unittest
class SkipMethodsConditionally(unittest.TestCase):
#classmethod
def setUpClass(cls):
#this class variable maintains whether or not test methods should continue
cls.should_continue = True
#this class variable represents the state of your system. Replace with function of your own
cls.some_bad_condition = False
def setUp(self) -> None:
"""setUp runs before every single test method in this class"""
if not self.__class__.should_continue:
self.skipTest("no reason to go on.")
def test_1_fail(self):
#Do some work here. Let's assume you encounter a "bad state,"" that could
#only be noticed in this first test method only, (i.e., it's not something that
#can be placed in the setUpClass method, for whatever reason)
self.__class__.some_bad_condition = True
if self.__class__.some_bad_condition:
self.__class__.should_continue = False
self.assertTrue(False,"this test should fail, rendering the rest of the tests irrelevant")
def test_2_pass(self):
self.assertFalse(self.__class__.some_bad_condition,"this test would pass normally if run, but should be skipped, because it would fail")
The above test will yield the following output:
test_1_fail (__main__.SkipMethodsConditionally) ... FAIL
test_2_pass (__main__.SkipMethodsConditionally) ... skipped 'no reason to go on.'
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1, skipped=1)
I looked at the TestCase class and decided to subclass it. The class just overrides run(). I copied the method and starting at line 318 in the original class added this:
# this is CPython specific. Jython and IronPython may do this differently
if testMethod.func_code.co_argcount == 2:
testMethod(result)
else:
testMethod()
It has some CPython specific code in there to tell if the test method can accept another parameter, but since I'm using CPython everywhere, this isn't an issue for me.
Use:
if condition:
return 'pass'

Categories

Resources