skamsie
skamsie

Reputation: 2726

Python unittest, do something only if test fails

When using the unittest library from python 3 I would like to do some actions only if a test fails (but this should be on a class level so I don't have to write it for each test). For example when using behave there was something like:

def after_step(context, step):
    if step.status == "failed":
        ...

Is there something similar for the unittestlibrary and if not, what would be the easiest approach to do something similar?

Upvotes: 12

Views: 9247

Answers (5)

DanielHefti
DanielHefti

Reputation: 344

I've found a solution by overriding the run method of the TestCase class. This solution executes some code on the first error and stops the execution of the test.

class MyTestClass(unittest.TestCase):
    if not result.errors:
        super(MyTestClass, self).run(result)
    else:
        # Execute your code here, which should only run when an error 
        #  occurs.
        print("Failure")

This solution was inspired by the blog of Kevin Sookocheff.

Upvotes: 0

Evandro Coan
Evandro Coan

Reputation: 9418

You can to these cool things. Override the default exception handler by your custom which calls the original, and set your custom attributes:

import sys
import unittest
has_failures = []

class IntegrationTests(unittest.TestCase):
    old_failureException = unittest.TestCase.failureException

    @property
    def failureException(self):
        has_failures.append('fail')
        return self.old_failureException

    def setUp(self):
        sys.stderr.write('Setup for %s\n' % self._testMethodName)

        if has_failures:
            self.skipTest("An test has failed, skipping everything else!")

    def test_thing1(self):
        self.assertEqual(1, 2)

    def test_thing2(self):
        pass

def load_tests(loader, standard_tests, pattern):
    suite = unittest.TestSuite()
    suite.addTest( IntegrationTests( 'test_thing1' ) )
    suite.addTest( IntegrationTests( 'test_thing2' ) )
    return suite

# Comment this to run individual Unit Tests
load_tests = None

if __name__ == "__main__":
    unittest.main(verbosity=3)

Results:

test_thing1 (__main__.IntegrationTests) ... Setup for test_thing1
FAIL
test_thing2 (__main__.IntegrationTests) ... Setup for test_thing2
skipped 'An test has failed, skipping everything else!'

======================================================================
FAIL: test_thing1 (__main__.IntegrationTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\User\Downloads\text.py", line 27, in test_thing1
    self.assertEqual(1, 2)
AssertionError: 1 != 2

----------------------------------------------------------------------
Ran 2 tests in 0.003s

FAILED (failures=1, skipped=1)

References:

  1. Getting Python's unittest results in a tearDown() method
  2. if condition in setUp() ignore test
  3. How do you generate dynamic (parameterized) unit tests in python?
  4. How to get currently running testcase name from testsuite in unittest

Upvotes: 0

olpa
olpa

Reputation: 1227

Using a private variable isn't very nice, but I haven't found other way to access the object which tracks the test results.

import unittest

class TmpTest(unittest.TestCase):

    def tearDown(self):
        result = self._resultForDoCleanups
        if not result.wasSuccessful():
            print "*** test failed"

    def testFoo(self):
        self.assertEqual(2, 2)

if "__main__" == __name__:
    unittest.main()

Upvotes: -1

WKPlus
WKPlus

Reputation: 7255

I am trying to do something similarly recently and I found a way out:

import unittest

class MyTestResult(unittest.TestResult):
    def addFailure(self, test, err):
        # here you can do what you want to do when a test case fails 
        print('test failed!')
        super(MyTestResult, self).addFailure(test, err)

    def addError(self, test, err):
        # here you can do what you want to do when a test case raises an error
        super(MyTestResult, self).addError(test, err)

class MyUT(unittest.TestCase):
    def test_fail(self):
        self.assertEqual(1, 2, '123')
        self.assertTrue("ABc".isupper())

if __name__ == '__main__':
    unittest.main(testRunner=unittest.TextTestRunner(resultclass=MyTestResult))

If you want to do different work according to different test case class, you can achieve it like this:

import unittest

class MyUT(unittest.TestCase):
    class TestResult(unittest.TestResult):
        def addFailure(self, test, err):
            print('do something when test case failed')
            super(MyUT.TestResult, self).addFailure(test, err)
        def addError(self, test, err):
            print('test case error')
            super(MyUT.TestResult, self).addError(test, err)

    def test_fail(self):
        self.assertEqual(1, 2, "1=2")

class MyUT2(unittest.TestCase):
    class TestResult(unittest.TestResult):
        def addFailure(self, test, err):
            print('do something else when test case failed')
            super(MyUT2.TestResult, self).addFailure(test, err)
        def addError(self, test, err):
            print('test case error')
            super(MyUT2.TestResult, self).addError(test, err)

    def test_fail(self):
        self.assertEqual(1, 2, "1=2")

if __name__ == '__main__':
    classes = [MyUT, MyUT2]
    for c in classes:
        suite = unittest.TestLoader().loadTestsFromTestCase(c)
        unittest.TextTestRunner(resultclass=c.TestResult).run(suite)

Upvotes: 7

dexiu
dexiu

Reputation: 11

You could try to do it with decorator:

class ExceptionHandler(object):
def __init__(self, f):
    self.f = f

def __call__(self, *args, **kwargs):
    try:
        self.f(*args, **kwargs)
    except:
        print('do smth')

And in unit test:

@ExceptionHandler  
def test_fail(self):
    self.assert_false(True)

Upvotes: 1

Related Questions