A Simple Guide to Fixing The 'Pytest Collected 0 Items'" Error

Ever been baffled by the “Pytest collected 0 items” error message?

It’s like setting off on a testing treasure hunt, only to realize you don’t have a map. This error often leaves many scratching their heads – what’s wrong? Why can’t Pytest find my tests?

At the heart of this mystery is Pytest’s test discovery mechanism.

Even in a well-organized project, Pytest comes back empty-handed, flashing the infamous “collected 0 items” message. It’s as if your tests have turned invisible!

In this article, we’ll dissect the causes behind the “Pytest collected 0 items” error and arm you with strategies to structure your tests effectively.

It’s all about understanding the rules of the game – from naming conventions to configuration nuances.

Ready to crack the code? Let’s dive in!

Example Code

What You’ll Learn

By the end of this tutorial, you should be able to:

  • Investigate the “Pytest collected 0 items” error and fix it.
  • Have a clear understanding of the set of conventions, rules and best practices to discover and execute tests.
  • Ensure that Pytest can discover, collect and run your tests correctly.

How To Fix The Pytest Collected 0 Items Error

Pytest follows a set of conventions and rules to discover and execute tests. Here’s a high-level overview of why Pytest may fail to discover your tests, with some practical solutions:

Incorrect Test File Naming

Pytest follows a naming convention, looking for files named test_*.py or *_test.py.

If your test files don’t adhere to this naming convention, Pytest won’t recognize them as test files.

Solution:
Rename your test files to match the Pytest naming convention.

Incorrect Directory Structure

Pytest may not be looking in the right directory for tests.

By default, it searches the current directory and subdirectories for test files. Most developers commonly use a tests directory.

Solution:
Ensure your test files are in the correct directory. If you’re using a custom directory, configure it using a pytest.ini, tox.ini, setup.cfg, or pyproject.toml file with the testpaths attribute.

Test Functions or Classes Not Properly Named

Test functions must be named with a test_ prefix, and test classes should start with Test and contain methods that start with test_.

Solution:
Rename your test functions and classes to follow these naming conventions.

Misconfiguration in conftest.py or Configuration Files

The conftest.py file in Pytest is used to share configurations, fixtures, plugins, hooks, and other reusable components across multiple test files.

This file allows you to define fixtures and configurations that can be accessed by tests in multiple test modules, promoting reusability and maintainability.

Pytest searches for conftest.py files in all the directories of your test paths, starting from the root test directory down to subdirectories. This allows for different levels of configuration at different directory hierarchies within your test structure.

Each conftest.py file’s scope is limited to the directory it is in and its subdirectories.

Misconfigured settings in conftest.py or configuration files like pytest.ini can lead to tests not being discovered.

Solution:
Review and correct the configurations in these files. Pay special attention to hooks in conftest.py that might alter test collection behavior.

Note - If you need guidance on how to use conftest.py files, check out this guide.

Incorrect Usage of Markers or Custom Decorators

If you’ve used markers or custom decorators incorrectly, Pytest may not recognize some functions as tests.

If you’re not too familiar with markers, this guide offers a practical introduction to Pytest Markers and Good Test Management.

Solution:
Review your use of markers and decorators. Ensure they’re correctly implemented and that you’ve registered custom markers in your configuration files if necessary.

Pytest Version or Plugin Issues

An outdated Pytest version or issues with plugins can sometimes lead to problems in test discovery.

Solution:
Update Pytest to the latest version. If you suspect a plugin is causing issues, try running Pytest with the -p no:<plugin_name> option to disable the plugin or simply uninstall it (temporarily) to see if it resolves the issue.

Environment or Interpreter Issues

Sometimes, issues with your Python environment or interpreter can cause unexpected behavior.

Solution:
Ensure you’re using the correct Python interpreter and that your environment is properly set up with all dependencies installed.

If you’re having issues running the pytest command from the terminal it may have to do with your Python environment.

Here’s a guide to resolving the pytest command not found error.

Command-Line Invocation Issues

Incorrect command-line options or paths can cause Pytest to look in the wrong place for tests.

Solution:
Check your command-line invocation. Ensure you’re running Pytest in the correct directory and with the correct options.

Important Debugging Steps

  • Run Pytest with increased verbosity (pytest -vv) to get more information. This practical guide on Pytest Verbosity covers all options and how to use them.
  • Use pytest --collect-only to see what tests Pytest is collecting.

Practice Example

Let’s look at an example of how the Pytest collected 0 items error occurs and how to address it.

First, let’s set up our test environment to simulate the error.

Prerequisites

To follow this guide, you should have:

  • Python 3.11+ installed.
  • An elementary grasp of Python and Pytest.

Getting Started

This is how our example repo looks like,

pytest-collected-0-items

To get started. clone the Github Repo here, or you can create your own repo by creating a folder and running git init to initialize it.

Example Code

We’ll start with a straightforward example that contains some basic timestamp functions.

Don’t worry too much about the source code as the focus of this article is to understand the “collected 0 items” error.

src/stamp.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import time
from datetime import datetime

def timestamp_using_time():
"""
Function to generate timestamp using time library
"""
return time.time()

def timestamp_using_datetime():
"""
Function to generate timestamp using datetime library
"""
current_time = datetime.now()
return current_time.timestamp()

def custom_timestamp(date_string):
"""
Function to generate custom timestamp
"""
dt_format = datetime.strptime(date_string, '%d.%m.%Y %H:%M:%S')
return dt_format.timestamp()

def convert_timestamp(time_stamp):
"""
Function to convert timestamp to normal date and time
"""
return datetime.fromtimestamp(time_stamp)

In the example code above, we have four distinct functions that generate timestamps using different methods.

The docstrings will give you an idea about each function.

Test Code

Let’s write some test code for our example,

tests/test_stamp.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pytest
from src.stamp import timestamp_using_time, timestamp_using_datetime, convert_timestamp, custom_timestamp

@pytest.fixture()
def get_timestamp_using_time():
return timestamp_using_time()

@pytest.fixture()
def get_timestamp_using_datetime():
return timestamp_using_datetime()

# Check if the timestamps are correct.
def validitytest(get_timestamp_using_time, get_timestamp_using_datetime):
assert convert_timestamp(get_timestamp_using_time) != None
assert convert_timestamp(get_timestamp_using_datetime) != None

def test_custom_timestamp():
assert custom_timestamp("23.02.2021 09:12:00") == 1614049920.0
assert custom_timestamp("23.10.2023 10:12:00") == 1698034320.0

In our test code, we define two fixtures: get_timestamp_using_time() and get_timestamp_using_datetime(). Additionally, there are two test functions: validitytest() and test_custom_timestamp().

Running Test Code

Finally, let’s run the test,

1
pytest

We’ve got the below output:

pytest-collected-0-items

You can see that we got an error “collected 0 items” which means Pytest failed to discover tests during execution.

Investigating and Fixing The Error

Let’s find out why Pytest failed to discover the tests,

Check 1: Let’s check out config file - pytest.ini. As you can see we set testpaths incorrectly.

1
2
[pytest]
testpaths="/"

You have 2 options here.

  1. You can disable the testpaths attribute as we want Pytest to discover the tests from the current directory under tests.
  2. You can set the testpaths to ./tests to look for tests in the tests directory.

I find the latter to be much cleaner and controlled.

1
2
[pytest]
testpaths="./tests/"

pytest-collected-0-items

Yes! now Pytest can discover the tests.

But wait!

We have 2 test functions whereas Pytest discovered only 1! 🤔

Let’s continue.

Check 2: Check the test file naming convention,

Our project structure follows Pytest standard test file naming and directory strategy. We have one test file named test_stamp.py which follows the Pytest module naming convention. No issues here.

Check 3: Lastly let’s check the function and class names. we have two test functions, one of which is named validitytest() which is not the standard Pytest naming convention. That’s why Pytest failed to discover the test.

Let’s rename it to test_validity()

Now our test code will look like this,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pytest
from src.stamp import timestamp_using_time, timestamp_using_datetime, convert_timestamp, custom_timestamp

@pytest.fixture()
def get_timestamp_using_time():
return timestamp_using_time()

@pytest.fixture()
def get_timestamp_using_datetime():
return timestamp_using_datetime()

# Check if the timestamps are correct.
def test_validity(get_timestamp_using_time, get_timestamp_using_datetime):
assert convert_timestamp(get_timestamp_using_time) != None
assert convert_timestamp(get_timestamp_using_datetime) != None

def test_custom_timestamp():
assert custom_timestamp("23.02.2021 09:12:00") == 1614049920.0
assert custom_timestamp("23.10.2023 10:12:00") == 1698034320.0

When we run the test again,

pytest-collected-0-items

Voila!! Now Pytest can discover your tests and run them successfully.

Ignoring Specific Test Paths

The --ignore flag is a powerful tool in Pytest.

Imagine you have a test file that’s not quite ready for prime time, or maybe it’s causing issues in your test suite. You can simply exclude it from the test run with a command like this:

1
pytest --ignore=tests/test_code.py

This command tells pytest, “Hey, let’s not worry about test_code.py in the tests folder for now.”

It’s an excellent way to focus on what’s essential without getting bogged down by incomplete or problematic tests.

Ignoring Specific Test Classes

Now, what if you have a specific class within a test file that you’d like to exclude? Easy! Inside your test class, you add:

1
2
class TestClass:
__test__ = False

This line is like a secret handshake with pytest, telling it, “This class is not for testing.” It’s a neat trick to keep your test suite lean and focused.

Using pytest.ini for Ignore Paths

For a more centralized approach, you can use pytest.ini.

This configuration file is like the command center for pytest.

You can specify paths to ignore by adding either collect_ignore or collect_ignore_glob attributes.

This method is particularly useful for larger projects where you might want to standardize what gets ignored across different environments or team members.

The Official Documentation contains a ton of information on how to configure this.

More Ways to Skip Tests

If you’re feeling adventurous, there’s more!

The @pytest.mark.skip and @pytest.mark.skipif decorators offer even more control.

They’re like the sophisticated cousins of the ignore methods. These decorators can conditionally skip tests based on various factors, giving you the precision of a surgeon in managing your test suite.

@pytest.mark.skip is straightforward: it tells pytest to skip a test unconditionally.

@pytest.mark.skipif, on the other hand, is more dynamic. It skips a test based on a specific condition, like an external factor or a particular configuration.

You can find more about these decorators and their practical uses here. They’re an excellent addition to your toolkit.

By mastering these methods, you’ll have unparalleled control over your test suite, allowing you to run tests that are most relevant and useful for your current development stage.

Conclusion

In this journey, we’ve learnt a lot about Pytest’s test discovery mechanism, helping you debug and resolve the Pytest collected 0 items error.

We started by unlocking the secrets of Pytest’s test discovery mechanism, gaining insights into how it spots test modules, functions, and classes.

Next, we tackled the puzzle of why Pytest might overlook certain tests, delving into common pitfalls and their solutions. This was crucial for troubleshooting and ensuring your tests get the attention they deserve.

Then we dissected an example to demonstrate this issue and walked through each step of the debugging process.

Advancing further, we explored some sophisticated techniques like pre-checking test collection with --collect-only flag and selectively ignoring or skipping tests as necessary.

Now, you’re equipped to navigate the intricacies of Pytest’s internal test collection system with confidence, focusing more on writing impactful tests and less on westling technical snags.

Next Steps

I’m always excited to hear your thoughts and suggestions. If there’s a topic you’d like me to explore further, or if you have ideas for improvement, feel free to reach out.

You can contact me via Twitter, GitHub or Email. Happy Coding 🧑🏽‍💻 🚀

Additional Reading

Example Code
How To Manage Temporary Files with Pytest tmp_path
How To Run A Single Test In Pytest (Using CLI And Markers)
A Step-by-Step Guide To Using Pytest Fixtures With Arguments
What is Setup and Teardown in Pytest? (Importance of a Clean Test Environment)
An Ultimate Guide To Using Pytest Skip Test And XFail - With Examples
Conventions for python test discovery
Changing standard (Python) test discovery