Description
Bug report
Bug description:
The practical motivation here is that the third-party decorator parameterized.parameterized.expand
from the parameterized project works by generating more test methods in the containing class, one for each expansion, and then returning None
as the result of the decorator. So if you do something like
import unittest
import parameterized
class TestStuff(unittest.TestCase):
@unittest.skip("these tests modify prod")
@parameterized.parameterized.expand([("a", 1), ("b", 2)])
def test_stuff(self, x, y):
...
then the tests actually get run. The correct way to implement this is the other way around - use @expand(...) @skip(...)
, and then parameterized will copy the skip wrapper, giving the behavior the programmer expects.
The underlying reason is that unittest.skip
's decorator never actually calls the function its decorating and therefore doesn't care what it is:
>>> import unittest
>>> unittest.skip("these tests modify prod")(None)
<function skip.<locals>.decorator.<locals>.skip_wrapper at 0x10082c1f0>
>>> _()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/unittest/case.py", line 119, in skip_wrapper
raise SkipTest(reason)
unittest.case.SkipTest: these tests modify prod
If unittest.skip(reason)(function)
were to instead raise an error if callable(function)
is false, instead of returning a wrapper, then the test case above would raise at import time, before any of the tests were run, indicating to the test author that they have implemented something incorrectly.
This should also apply to skipIf
and skipUnless
, and I think also to expectedFailure
and removeHandler
, the other decorators in unittest
.
CPython versions tested on:
3.9, 3.10, 3.11, 3.12
Operating systems tested on:
Linux, macOS
Metadata
Metadata
Assignees
Projects
Status