Find duplicate tests in your suite
Tests run twice when a test class is imported into another module or a parametrize case repeats another, and a copied test often doesn't test what its name says. How to find and fix them.
Every suite collects some tests that add nothing: the same test running twice, or a copy of a test that checks the same thing under another name. They cost time on every run, and the copies are worse than wasted time: a test named test_model_copy that never calls model_copy() tells everyone that model_copy() is tested when it isn't.
Three kinds are worth finding. The first two are exact: the duplicate is the same test. The third is usually a bug in the test.
A test class imported into another module runs there too
pytest collects every class whose name starts with Test in a test module's namespace, and every function whose name starts with test, including the ones it imports:
# tests/test_api.py
from tests.test_orders import TestOrders # to reuse a helper method
def test_api_lists_orders(client): ...
Now every test in TestOrders runs twice, once as tests/test_orders.py::TestOrders::test_total and once as tests/test_api.py::TestOrders::test_total. Nothing fails, so nobody notices.
Import the module instead of the class (from tests import test_orders), or move what you share to a helper module or conftest.py. The plain unittest runner does the same with imported TestCase classes; Django's test runner removes the repeats.
Parametrize cases with the same values
@pytest.mark.parametrize("currency", ["eur", "usd", "gbp", "eur"])
def test_price(currency): ...
pytest gives the repeated value its own id (eur0, eur1) and runs it again. In a short list it's easy to spot. In a list built from a fixture file or a long table of cases, it isn't. Drop the repeated case.
Tests with the same code as another test
A test copied to start a new one, and never changed to test the new thing:
def test_dunder_copy():
model = Model(a=1)
copied = model.__copy__()
assert copied == model
def test_model_copy():
"""model_copy() returns a copy."""
model = Model(a=1)
other = model.__copy__() # should be model.model_copy()
assert other == model
Both tests pass, and model_copy() is untested. pydantic's suite had a test just like this one. Django's suite has a few more, such as a test of localized time fields that never localizes the field, and a test_equals that asserts .contains().
The fix is to make each copy test what its name says (the method, input or setting it's named after), or to delete it if the other test already covers that.
Some copies are on purpose. Django runs test_request_after_client_2 with the same code as test_request, to check that nothing the previous test did leaks into the next one. Keep those, and leave a comment that says why.
Inherited tests run in every subclass
class CheckoutChecks(TestCase):
def test_total(self): ...
def test_tax(self): ...
class CardCheckoutTest(CheckoutChecks): ...
class InvoiceCheckoutTest(CheckoutChecks): ...
Each subclass runs every test it inherits, and the base class runs them too. That's often the point: the same tests against another backend or configuration. When a test doesn't depend on anything the subclasses change, it runs once per class for nothing. Move those tests to a class of their own. A base class that only exists to be inherited shouldn't run on its own: give it a name pytest doesn't collect (no Test prefix) or set __test__ = False on it, or make it a mixin that doesn't inherit from TestCase.
What isn't a duplicate
- Tests that differ only in their data.
test_search_exactandtest_search_startswithcan share every line but the lookup. Those are two scenarios, not one. - Tests that cover the same lines. Two tests can run exactly the same code and check different things: one asserts the count, the other the error message. In a study of 18 projects, suites cut down to the tests that kept the same statement coverage missed up to 20% of the injected bugs (mutants) that the full suites caught. Don't delete a test because another one covers its lines.
How flakytest finds them
flakytest finds the first two kinds when pytest collects the tests: a test function of the same class collected under a second node id, and parametrize cases whose values are equal (numbers, strings, None, and lists, tuples and dicts of them). It reports both as Tests run twice, with the time the repeats take.
It finds copies from your test files' source when it builds the report. Two tests count as copies only when they have the same code once their local variable names are ignored, and when everything that could make them behave differently is the same too: decorators and parametrize data, the fixtures they ask for, their class and their module's setup, and what every name they use refers to. It reports them as Tests have the same code as another test. On Django's 18,000 tests that takes about 3 seconds and finds 16 tests.
Inherited tests come from the class each test method is defined in, and flakytest reports them as Test classes run the tests they inherit again.
In flakytest.dev, a test that repeats another one has a badge in the run's list of tests, and its page says which test it repeats.
Further reading
- pytest: conventions for test discovery
- pytest: parametrizing tests
- Shi et al., Balancing Trade-offs in Test-Suite Reduction (FSE 2014)
Related guides
- Keep tests independent of each other: Tests should pass alone, in any order and in parallel. Find order-dependent pytest tests with pytest-randomly and pytest-xdist, and fix the shared state behind them.
- Find your slowest pytest tests: List the slowest pytest tests with --durations and --durations-min, read setup vs call vs teardown times, then fix, split or mark slow tests so they stop holding up the suite.
- pytest fixtures best practices: How to write pytest fixtures that stay fast and readable: scopes, yield teardown, factories as fixtures, conftest.py placement, autouse, parametrize, tmp_path and monkeypatch.
Updated November 9, 2026 · Suggest a change