This rule raises an issue when at least 3 test methods could be refactored into a single parameterized test with less than 4 parameters.

Why is this an issue?

When multiple tests differ only by a few hardcoded values, they should be refactored into a single parameterized test. This reduces duplication, makes the tests easier to read, and lowers the risk of introducing bugs when the test logic needs to change.

Parameterized tests are supported by most testing frameworks.

The right balance still needs to be found. There is little value in parameterizing tests when the resulting test becomes significantly more complex than the original versions.

What is the potential impact?

Duplicated test methods lead to higher maintenance costs and increased likelihood of bugs. When test logic needs to be updated, developers must remember to make identical changes across multiple methods, which is easy to forget.

This can result in inconsistent test coverage where some variations of a test are updated while others remain outdated. Such inconsistencies can cause false positives or, worse, false negatives that let bugs slip through.

The impact on team productivity compounds over time as the test suite grows. More time is spent maintaining redundant code instead of writing new tests or improving application code.

How to fix it in pytest

Use pytest’s @pytest.mark.parametrize decorator to combine similar tests into a single parameterized test. The decorator accepts a string of parameter names and a list of tuples containing the values for each test case.

Code examples

Noncompliant code example

import pytest

class TestApp:
    def test_level1(self):  # Noncompliant
        set_level(1)
        run_game()
        assert player_health() == 100

    def test_level2(self):
        set_level(2)
        run_game()
        assert player_health() == 200

    def test_level3(self):
        set_level(3)
        run_game()
        assert player_health() == 300

Compliant solution

import pytest

class TestApp:
    @pytest.mark.parametrize("level,health", [
        (1, 100),
        (2, 200),
        (3, 300),
    ])
    def test_levels(self, level, health):
        set_level(level)
        run_game()
        assert player_health() == health

How to fix it in unittest

For unittest, you can use the parameterized library to achieve similar functionality. Install it with pip install parameterized and use the @parameterized.expand decorator.

Code examples

Noncompliant code example

import unittest

class TestApp(unittest.TestCase):
    def test_not_null1(self):  # Noncompliant
        setup_tax()
        self.assertIsNotNone(get_tax(1))

    def test_not_null2(self):
        setup_tax()
        self.assertIsNotNone(get_tax(2))

    def test_not_null3(self):
        setup_tax()
        self.assertIsNotNone(get_tax(3))

Compliant solution

import unittest
from parameterized import parameterized

class TestApp(unittest.TestCase):
    @parameterized.expand([1, 2, 3])
    def test_not_null(self, tax_id):
        setup_tax()
        self.assertIsNotNone(get_tax(tax_id))

Resources

Documentation