How can I convert Python unittests to py.test when having global fixtures?

sorin picture sorin · Jul 19, 2014 · Viewed 7.8k times · Source

I do have a set of unit tests written using the Python's unittest module. They are using the setUpModule() function to load a global variable with the shared "stuff" that is required to run the tests (including some HTTP sessions).

When running my tests with unittest, they are running fine. With py.test they are failing.

I patched it a little bit to make it run using the old pytest fixture functions (which happen not to have same names as unittest ones). It worked, but only when not executed on multiple threads, and that's a feature I do want to use.

The documentation examples are useless in my case because I do have like 20 classes (unittest.TestCase) with 10 tests inside each class. Obviously I do not want to add a new parameter to each test.

Until now I used the class setUp() method to load the shared dictionary inside self and use it from there inside each test.

#!/usr/bin/env python
# conftest.py
@pytest.fixture(scope="session")
def manager():
    return { "a": "b"}

And now the tests:

#!/usr/bin/env python
# tests.py

class VersionTests(unittest.TestCase):

    def setUp(self):
        self.manager = manager

    def test_create_version(self):
        # do something with self.manager
        pass

Please remember that I need a solution that would work with multiple threads, calling the fixture a single time.

Answer

famousgarkin picture famousgarkin · Jul 19, 2014

pytest can run unittest tests for sure, as documented in Support for unittest.TestCase / Integration of fixtures. The tricky part is that using pytest funcargs fixtures directly is discouraged:

While pytest supports receiving fixtures via test function arguments for non-unittest test methods, unittest.TestCase methods cannot directly receive fixture function arguments as implementing that is likely to inflict on the ability to run general unittest.TestCase test suites.

Suppose we have a tests module like this, using the standard unittest initialization facilities:

# test_unittest_tests.py (for the sake of clarity!)
import unittest

manager = None

def setUpModule():
    global manager
    manager = {1: 2}

class UnittestTests(unittest.TestCase):
    def setUp(self):
        self.manager = manager

    def test_1_in_manager(self):
        assert 1 in self.manager

    def test_a_in_manager(self):
        assert 'a' in self.manager

It yields the following output when ran with unittest:

$ python -m unittest -v test_unittest_tests
...
test_1_in_manager (test_unittest_tests.UnittestTests) ... ok
test_a_in_manager (test_unittest_tests.UnittestTests) ... FAIL
...

The test_a_in_manager fails as expected. There isn't any 'a' key in the manager directory.

We set up a conftest.py to provide scoped pytest fixtures for these tests. Like this for example, without breaking the standard unittest behavior and without the need to touch them tests at all using pytest autouse:

# conftest.py
import pytest

@pytest.fixture(scope='session', autouse=True)
def manager_session(request):
    # create a session-scoped manager
    request.session.manager = {'a': 'b'}

@pytest.fixture(scope='module', autouse=True)
def manager_module(request):
    # set the sessions-scoped manager to the tests module at hand
    request.module.manager = request.session.manager

Running the tests with pytest (using pytest-xdist) for parallelization, yields the following output:

$ py.test -v -n2
...
[gw1] PASSED test_unittest_tests.py:17: UnittestTests.test_a_in_manager
[gw0] FAILED test_unittest_tests.py:14: UnittestTests.test_1_in_manager
...

Now the test_1_in_manager fails instead; there isn't any 1 key in the pytest-provided manager dictionary.