Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decorator works for Function but not Class

Need a bit of help correcting a decorator for unittest. I'm not sure how to meet the requirements of unit test. The idea of this decorator is to mark the test as an expectedFailure IF True is passed in. Otherwise allow the test to simply run. This decorator works for test functions but doesn't work for class definitions

import unittest

def expectedFailureIf(expFailure):
    if expFailure: 
        return unittest.expectedFailure
    return lambda func: func

@expectedFailureIf(GetCurrentOS() == kPlatMac)  # Fails on Class
class someClass(unittest.TestCase):
    #@expectedFailureIf(GetCurrentOS() == kPlatMac) # Works on Function
    def test_sometestA(self):
        assert True

    def test_sometestB(self):
        assert False

The error that I'm getting is test_sometest() takes exactly 1 argument. Removing the decorator allows the test to run. Moving the decorator to top of function allows the test to run.

History... One of my platforms works fine and the other platform does not. I want to allow one platform to run all the tests while the other platform will be marked as expected failures. Of course I do not want to use skip or skip if. Since that won't allow the valid platform to run. Marking them as expected failure will also not work since one platform will return unexpected success. With the expectedFailureIf() in place each platform will correctly report and once things are fixed these tests will report as unexpected success. Which will notify ME when things are fixed. For me... this seems a better outcome.

like image 501
Keith Avatar asked Oct 16 '22 16:10

Keith


1 Answers

On Python 3 your code is working fine. The implementation of unittest.expectedFailure is better there, and it works correctly when decorating on either classes or functions.

On Python 2, the unittest.expectedFailure is only designed to work on functions.

Here's a replacement which works on Python 2.

import inspect
import types
import unittest

def expectedFailureIf(condition):
    if callable(condition):
        condition = condition()
    if not condition:
        # return identity function for no-op
        return lambda x: x
    def patch(func_or_class):
        if isinstance(func_or_class, types.FunctionType):
            return unittest.expectedFailure(func_or_class)
        for name, member in inspect.getmembers(func_or_class):
            if name.startswith('test') and isinstance(member, types.MethodType):
                setattr(func_or_class, name, unittest.expectedFailure(member))
        return func_or_class
    return patch

@expectedFailureIf(True)
class MyTest(unittest.TestCase):

    def test_that_passes(self):
        assert 2 + 2 == 4

    def test_that_fails(self):
        assert 2 + 2 == 5

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

Result:

test_that_fails (__main__.MyTest) ... expected failure
test_that_passes (__main__.MyTest) ... unexpected success

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK (expected failures=1, unexpected successes=1)

Warning!

The fact that an unexpected success doesn't fail the test run is a bug in Python! It was addressed back in Jan 2014 (Python 3.4), but this bugfix was not merged into 2.7's branch due to backwards compatibility concerns (see the comments on issue20165). So, that's now a Python 2 "feature", unfortunately.

If this is a deal-breaker to you, consider upgrading to a more recent Python version and/or using a better test runner.

like image 153
wim Avatar answered Oct 20 '22 23:10

wim