pytest’s ability to read environment variables for configuration is often overlooked, leading many to believe you must use pytest.ini or conftest.py for all settings.

Let’s see how this works in practice. Imagine you have a test suite that needs to connect to a specific database, and the connection string can change based on whether you’re running locally or on a CI server.

# test_database.py
import os

def test_database_connection():
    db_url = os.environ.get("DATABASE_URL", "sqlite:///default.db")
    print(f"\nConnecting to: {db_url}")
    # In a real test, you'd establish a connection here
    assert "db" in db_url # A simple check for demonstration

If you run this without setting DATABASE_URL, it defaults to sqlite:///default.db.

pytest test_database.py

Output will show:

Connecting to: sqlite:///default.db

Now, let’s set the environment variable:

export DATABASE_URL="postgresql://user:password@host:port/dbname"
pytest test_database.py

The output now reflects the environment variable:

Connecting to: postgresql://user:password@host:port/dbname

This is incredibly powerful because it allows you to inject configuration directly into your tests without modifying your test code or configuration files. You can manage sensitive credentials, toggle features, or point to different resources all through environment variables.

The mental model here is straightforward: os.environ.get() is your gateway. It provides a dictionary-like interface to the environment variables available to the process running pytest. When pytest executes a test file, that test file inherits the environment variables of the pytest process itself.

You can leverage this for almost any setting that might vary between environments:

  • API Endpoints: API_BASE_URL="https://api.staging.example.com"
  • Feature Flags: ENABLE_NEW_FEATURE="true"
  • Resource Paths: DATA_DIR="/opt/test_data/staging"
  • Credentials: DB_PASSWORD="supersecret" (though storing secrets directly in env vars isn’t always ideal; consider dedicated secret management tools that inject into env vars).

The key is that your test code needs to be written to look for these variables. You’re not configuring pytest itself this way, but rather providing configuration to your tests.

The core lever you control is how your Python code interacts with os.environ. You can use os.environ.get('VAR_NAME') for a single variable, os.environ.get('VAR_NAME', 'default_value') to provide a fallback, or even loop through a set of expected variables.

A common pattern is to centralize environment variable loading in your conftest.py or a dedicated config module.

# conftest.py
import os

class Settings:
    DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///default.db")
    API_KEY = os.environ.get("API_KEY")

settings = Settings()

# Then in your test_database.py:
# from conftest import settings
# def test_db():
#    db_url = settings.DATABASE_URL
#    ...

This approach keeps your test files cleaner.

What most people miss is how seamlessly this integrates with pytest’s fixture system. Instead of just reading os.environ directly in a test function, you can expose these environment-derived settings as fixtures.

# conftest.py
import os
import pytest

@pytest.fixture(scope="session")
def database_url():
    return os.environ.get("DATABASE_URL", "sqlite:///default.db")

@pytest.fixture(scope="session")
def api_key():
    key = os.environ.get("API_KEY")
    if not key:
        pytest.skip("API_KEY environment variable not set")
    return key

# test_api.py
def test_api_call(api_key):
    print(f"\nUsing API Key: {api_key[:4]}...") # Don't print full keys!
    assert api_key is not None

This fixture approach allows you to conditionally skip tests if a required environment variable isn’t present, and it makes the dependencies of your tests explicit.

You can even use environment variables to control pytest’s own command-line arguments, like the verbosity level. If you set PYTEST_VERBOSE=1, you could conditionally add -v to the pytest command when invoking it.

The next logical step is to explore how to manage these environment variables across different testing stages or projects, often involving tools like python-dotenv or CI/CD pipeline configurations.

Want structured learning?

Take the full Pytest course →