1

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.

  • would `importlib.import_module()` help you at all? – JonSG Mar 20 '23 at 20:39
  • I'm not really sure how it would - I want Pytest to collect the tests so I don't think just having access to it is sufficient. I tried putting the other tests in a file called `test/integration/base_tests.py` and running `importlib.import_module("base_tests")` - no dice. – Harsha Nandiwada Mar 20 '23 at 21:35

2 Answers2

1

What you're seeking is called parametrization

The most idiomatic way would be to apply it to a fixture. When any test utilizes the fixture, it will repeat the test case for all parameters supplied.

Example:

import app1
import app2
import app3

@pytest.fixture(params=[app1, app2, app3])
def app(request):
    return request.param

def test_app(app):
    response = app.get_response()
    assert response == True
Teejay Bruno
  • 1,716
  • 1
  • 4
  • 11
  • How would the file structure look in this case? I tried putting this in the conftest and it didn't work. This also doesn't allow me to override classes, which I'd prefer. – Harsha Nandiwada Mar 21 '23 at 16:57
  • pytest does not collect tests from `conftest.py`, they must be in their own test file. – Teejay Bruno Mar 21 '23 at 17:26
  • Also not sure why you would want to mess around with classes. I'd recommend to give this a try as it's extremely simple. – Teejay Bruno Mar 21 '23 at 17:28
0

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.