Search code examples
python-3.xpytestconftest

How can I run the same test for every module without duplicating code?


I have a project with multiple modules that all generally require the same type of test. Each one imports a file with the same name, and given that file, all of them have the same test.

# test/integration/app1_test.py

from app1 import app

def test_app():
  response = app1.get_response()
  assert response == True

These tests are the same for app1, app2, and app3.

Is there a way for me to not have to write the same code three times in app1_test, app2_test, and app3_test? Ideally, I'd like a class that I can override like

# test/conftest.py

class TestApp:
  def __init__(self, App): # hopefully can take this as a fixture
    self.app = App
  def test_app(self):
    response = self.app.get_response()
    assert response == True
# test/integration/app1_test.py

from app1 import app

@pytest.fixture(scope="module")
def App():
  return app
# test/integration/app2_test.py

from app2 import app
from conftest import TestApp

@pytest.fixture(scope="module")
def App():
  return app

class TestApp2(TestApp):
  def test_app(self):
    response = self.app.get_response()
    assert response == False
  def test_another_response(self):
    response = self.app.get_another_response()
    assert response == True

What's the closest I can get to this type of flow?

I tried putting test functions in conftest, and none of them ran when I ran python3 -m pytest. I can write the functions and import them in the individual test modules and manually run them, but they won't show up as pytest tests, just Python functions. I want Pytest to collect and run them all for each module in the project.


Solution

  • After a bunch of small adjustments, I figured out something that works!

    # test/conftest.py
    
    class BaseTest:
        # # TODO override this fixture in the test file
        # @pytest.fixture(scope="module")
        # def app(self):
        #     pass
    
        def test_app(self, app):
            response = self.app.get_response()
            assert response == True
    
    # test/integration/app1_test.py
    
    import pytest
    from conftest import BaseTest
    from app1 import app
    
    class TestApp1(BaseTest):
        @pytest.fixture(scope="module")
        def app(self):
            return app
        # pytest collects and runs test_app with the correct fixture
    
    # test/integration/app2_test.py
    
    import pytest
    from conftest import BaseTest
    from app2 import app
    
    class TestApp2(BaseTest):
        @pytest.fixture(scope="module")
        def app(self):
            return app
    
        def test_another_response(self, app):
            response = app.get_another_response()
            assert response == True
        # pytest collects and runs test_app with the correct fixture, but that can be overridden. it also collects test_another_response.