40

I would like my Python unittest module to tell the test runner to skip it entirety under some situations (such as being unable to import a module or locate a critical resource).

I can use @unittest.skipIf(...) to skip a unittest.TestCase class, but how do I skip the entire module? Applying skips to every class is not sufficient because the class definitions themselves could cause exceptions if a module fails to import.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jace Browning
  • 11,699
  • 10
  • 66
  • 90
  • 2
    FYI, there's a blog post about this exact thing at http://colinnewell.wordpress.com/2012/08/31/skippng-python-unit-tests-if-a-dependency-is-missing/ – Mu Mind Sep 19 '12 at 22:24
  • 1
    @Mu Mind, this works except I am telling `nose` to "fail fast". Calling `unittest.SkipTest()` seems to count as a failure and halts execution. – Jace Browning Sep 20 '12 at 02:48

13 Answers13

26

If you look at the definition of unittest.skipIf and unittest.skip, you can see that the key is doing raise unittest.SkipTest(reason) when the test is executed. If you're okay with having it show up as one skipped test instead of several in the testrunner, you can simply raise unittest.SkipTest yourself on import:

import unittest
try:
    # do thing
except SomeException:
    raise unittest.SkipTest("Such-and-such failed. Skipping all tests in foo.py")

Running with nosetests -v gives:

Failure: SkipTest (Such-and-such failed. Skipping all tests in foo.py) ... SKIP:
Such-and-such failed. Skipping all tests in foo.py

----------------------------------------------------------------------
Ran 1 test in 0.002s

OK (SKIP=1)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mu Mind
  • 10,935
  • 4
  • 38
  • 69
  • 1
    I don't know if that would work at *module* scope; `loader.py` seems to suggest that the module fails if it throws *any* exception. – nneonneo Sep 19 '12 at 04:23
  • I tried it out running tests with nosetests and it worked fine. Editing my answer to add the actual output... – Mu Mind Sep 19 '12 at 04:26
  • 5
    `nosetests` != `unittest`, though they use the same library. Pretty sure it fails for plain `unittest`. – nneonneo Sep 19 '12 at 04:29
  • 1
    You're right, `unittest.main()` and `python -m unittest discover` don't seem to like this. – Mu Mind Sep 19 '12 at 06:20
  • @MuMind Using a `unittest` inbuilt like `.SkipTest` should have been the ideal workaround. Too bad that those two don't like it. +1 not your fault unittest was designed that way :P – aneroid Sep 19 '12 at 08:49
  • @Mu Mind, the problem with this solution is the `raise unittest.SkipTest()` causes nose to "fail fast" (if enabled) which I would like to avoid. – Jace Browning Sep 19 '12 at 10:19
  • What do you mean "fail fast"? I see other modules running after this module fails under nose. – Mu Mind Sep 19 '12 at 10:46
  • It's strange. https://docs.python.org/3/library/unittest.html#unittest.TestLoader.discover says: Changed in version 3.4: Modules that raise SkipTest on import are recorded as skips, not errors. But it doesn't work. – g1itch Jan 29 '21 at 14:56
  • Also see [docs](https://docs.python.org/3/library/unittest.html#skipping-tests-and-expected-failures): "Skipping a test is simply a matter of [...], or raising `SkipTest` directly." – djvg Jun 04 '21 at 10:09
14

I found that using skipTest in setUp worked well. If you need a module imported, you use a try block to set e.g. module_failed = True, and in setUp call skipTest if it's set. This reports the correct number of test skips with only a short try block needed:

import unittest

try:
    import my_module
    module_failed = False
except ImportError:
    module_failed = True

class MyTests(unittest.TestCase):
    def setUp(self):
        if module_failed:
            self.skipTest('module not tested')

    def test_something(self):
            #...
otus
  • 5,572
  • 1
  • 34
  • 48
8

After looking at the other answers here, this is the best answer I've come up with. It's ugly, embedding the whole test suite in the exception handling, but it appears to do what you want. Specifically skipping the tests when the imports don't work.

Assuming you're talking about using nosetests -x for running the tests it should carry on past the tests that skip, at least it appeared to when I tried it.

import unittest
try:
    import PyQt4
    # the rest of the imports


    # actual tests go here.
    class TestDataEntryMixin(unittest.TestCase):
        def test_somefeature(self):
            # ....

except ImportError, e:
    if e.message.find('PyQt4') >= 0:
        class TestMissingDependency(unittest.TestCase):

            @unittest.skip('Missing dependency - ' + e.message)
            def test_fail():
                pass
    else:
        raise

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

If the import fails it replaces the test run with a single test that simply skips. I've also attempted to make sure that it doesn't swallow any exceptions unintentionally. This solution owes a lot to all the other answers and comments to the question.

If you run it in verbose mode you will see this when it skips,

test_fail (test_openihm_gui_interface_mixins.TestMissingDependency) ... skipped 'Missing dependency - No module named PyQt4'
Colin Newell
  • 3,073
  • 1
  • 22
  • 36
  • 2
    I think this is the best answer, but you're right, it's ugly. :-) – Jace Browning Sep 20 '12 at 14:47
  • Getting `DeprecationWarning: BaseException.message has been deprecated as of Python 2.6`? You need to do this: http://stackoverflow.com/questions/1272138/baseexception-message-deprecated-in-python-2-6 – nelsonjchen Jan 28 '14 at 19:28
6
@unittest.skip('comments_for_skipping_unit_tests')
class MyTests(unittest.TestCase):
def setUp(self):
    pass

def test_something(self):

You can skip the whole unit test class by using the @unittest.skip decorator.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mitendra
  • 1,426
  • 17
  • 11
  • OP explicitly asks/states "_how do I skip the entire module? Applying skips to every class is not sufficient because the class definitions themselves could cause exceptions if a module fails to import._" - this answer does not address the question – raner Oct 17 '22 at 22:27
4

The solution proposed by otus works and is easier than the accepted solution in my opinion. But there is at least one downside. If you query my_module in a decorator to skip a single test such as

@unittest.skipIf(my_module.support_foo, 'foo not supported')
def test_foo(self):
...

you will get a NameError: name 'my_module' is not defined. The solution is put the skip inside the function definition:

def test_foo(self):
    if not my_module.support_foo:
        self.skipTest('foo not supported')
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Fred Schoen
  • 1,372
  • 13
  • 18
4

For Python 2.7+ (or using the unittest2 backport):

...

import unittest

def setUpModule():
    try:
        import something
    except ImportError as err:
        raise unittest.SkipTest(str(err))

class Tests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        try:
            import something
        except ImportError as err:
            raise unittest.SkipTest(str(err))
...
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Zart
  • 1,421
  • 10
  • 18
3

Try defining a custom load_tests function in your module:

import unittest
try:
    (testcases)
except ImportError as e:
    def load_tests(*args, **kwargs):
        print("Failed to load tests: skipping")
        return unittest.TestSuite() # no tests
nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • 1
    I like this approach. It would probably be better if it made some kind of use of the SkipTest mechanism instead of just a `print` since that output can get drowned out in a bunch of other test output/results. – Mu Mind Sep 19 '12 at 08:08
  • It's possible. Just have the `TestSuite` contain a dummy test case that says it was skipped. (The `unittest` module's `loader` does a similar thing, producing guaranteed-failure test cases if e.g. an import fails). – nneonneo Sep 19 '12 at 08:24
2

It might be dirty to put all the unittest.TestCase subclass definitions in a try...except block, but it would work:

import unittest
try:
    import eggs
    class Spam(unittest.TestCase):
        pass
    class Ham(unittest.TestCase):
        pass
    # ...
except ImportError:
    # print 'could not import eggs'
    pass

None of the sub-classes would be defined if the eggs import fails and all those classes (Spam, Ham, etc.) get skipped. It would not be reflected in the output (good or bad depending on what you want).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
aneroid
  • 12,983
  • 3
  • 36
  • 66
  • A dirty solution to a contrived problem isn't so bad ;) – Mu Mind Sep 19 '12 at 08:10
  • 1
    @Mu Mind, is it really contrived? My use case is running the test suite on multiple platforms (some of which may not be able to `import eggs`). – Jace Browning Sep 19 '12 at 10:22
  • I was mostly giving you a hard time. I do think the bit about class definitions failing sounds a little strange, but disabling "this module" does seem cleaner than disabling "all the classes in this module" anyway. – Mu Mind Sep 19 '12 at 11:09
  • Just to clarify, @JaceBrowning and @MuMind, my reason for putting the class definitions under the same `try` block as `import egg` is that if the import egg fails, the classes never get defined (and hence, not found by 'discover') so there's **no error for the runner** to complain about (like in empty `test_*.py` files). If you prefer, the `except` block should then return an empty `return unittest.TestSuite()` like in nneonneo's solution using `load_tests` **if** you want that empty set reported. Functionally, your aim was to not have those classes' tests run so just doing a `pass` would do. – aneroid Sep 19 '12 at 11:37
2

Combining the mentioned answers and using this answer:

import unittest
def module_exists(module_name):
    try:
        __import__(module_name)
    except ImportError:
        return False
    else:
        return True

class TestClass(unittest.TestCase):

    @unittest.skipUnless(module_exists("moduleA"), 'ModuleA not installed')
    def test_something(self):
        # test something with moduleA
Community
  • 1
  • 1
crlb
  • 1,282
  • 12
  • 18
1

You can skip the whole class just like skipping individual methods. Documentation

@unittest.skip("showing class skipping")
class MySkippedTestCase(unittest.TestCase):
    def test_not_run(self):
        pass
pedram bashiri
  • 1,286
  • 15
  • 21
1

So this question is nearly a decade old, and still applicable! However, none of the other answers addressed the problem I was having today - decorators on the tests coming from the missing library. In this case, it was hypothesis and its decorators like @given... So I was forced to whip up this less-than-friendly alternative:

try:
    from hypothesis import assume, example, given, strategies as st
    hypothesis_missing = False
except ImportError:
    hypothesis_missing = True
    def whatever(*args, **kwargs):
        if args or kwargs:
            return whatever
        raise ImportError("Requires hypothesis library")
    example = given = whatever
    # Then this part is ickier to be py2.7 compatible
    from six import add_move, MovedModule
    add_move(MoveModule('mock', 'mock', 'unittest.mock'))
    from six.moves import mock
    st = mock.Mock()  # Without this, things like st.integers() fail

A Python3-native solution would just tighten it up a little:

  import unittest
  st = unittest.mock.Mock()

Then on each testing class (ugh) I needed:

@unittest.skipIf(hypothesis_missing, "Requires hypothesis library")

IMPORTANT NOTE: Without the skipIf, the tests will sometimes silently pass - the raise ImportError in whatever() only caught about half of them in my very limited testing.

Aaron D. Marasco
  • 6,506
  • 3
  • 26
  • 39
0

If you are just looking to skip an entire module/submodule then place the following code in the relevant __init__.py (e.g. tests/a/b/__init__.py). Like the other solutions, this will have one message for all skipped tests.

import unittest

raise unittest.SkipTest("Don't run these tests because x, y, and z")

The SkipTest exception is the underlying method for telling Unittest to skip, and raising this in an __init__.py causes that submodule (and all it's subsubmodules) to be skipped.

This works with nose 1.3.7

Firix
  • 124
  • 1
  • 2
  • 8
  • Not sure if this will work. Would be better to use the decorator @unittest.SkipTest for the class. If this works, please share the documentation for this solution. – Roopak A Nelliat Aug 05 '22 at 03:32
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 05 '22 at 03:32
-1

An example of decorator to skip test case if there is no internet connection at runtime

import dns
from dns.resolver import NoNameservers

def skip_test_if_no_internet_connection(f):
    def wrapper(self, *args, **kwargs):
        try:
            dns.resolver.resolve('example.org', 'a')
        except NoNameservers:
            self.skipTest(f'Test {f.__name__} skipped because no internet connection')
        else:
            return f(self, *args, **kwargs)
    return wrapper

Usage

class TestSometing(BaseTest):

    @skip_test_if_no_internet_connection
    def test_check_for_valid_dkim_record(self):
        pass
pymen
  • 5,737
  • 44
  • 35