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

64 Views Asked by At

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.

2

There are 2 best solutions below

0
hnandi On BEST ANSWER

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.
3
Teejay Bruno On

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