A testing framework that extends unittest with plugins and enhanced discovery capabilities
—
System for managing test configuration through command-line arguments, configuration files, and programmatic settings. Provides flexible configuration options for test discovery, plugin behavior, and execution parameters.
The Config class provides type-safe access to configuration values with default handling.
class Config:
"""
Configuration for a plugin or other entities.
Encapsulates configuration for a single plugin or element.
Corresponds to a ConfigParser section but provides extended
interface for extracting items as specific types.
"""
def __init__(self, items):
"""
Initialize configuration with key-value pairs.
Parameters:
- items: List of (key, value) tuples from config section
"""
def __getitem__(self, key):
"""
Get raw configuration values for key.
Parameters:
- key: Configuration key name
Returns:
List of values for the key
"""
def as_bool(self, key, default=None):
"""
Get key value as boolean.
1, t, true, on, yes, y (case insensitive) are accepted as True.
All other values are False. Empty setting (key=) returns False.
Parameters:
- key: Configuration key name
- default: Default value if key not found
Returns:
Boolean value or default
"""
def as_int(self, key, default=None):
"""
Get key value as integer.
Parameters:
- key: Configuration key name
- default: Default value if key not found
Returns:
Integer value or default
"""
def as_float(self, key, default=None):
"""
Get key value as float.
Parameters:
- key: Configuration key name
- default: Default value if key not found
Returns:
Float value or default
"""
def as_str(self, key, default=None):
"""
Get key value as string.
Parameters:
- key: Configuration key name
- default: Default value if key not found
Returns:
String value or default
"""
def as_list(self, key, default=None):
"""
Get key value as list.
The value is split into lines and returned as a list. Lines
are stripped of whitespace, and lines beginning with # are
skipped.
Parameters:
- key: Configuration key name
- default: Default value if key not found
Returns:
List of string values or default
"""
def get(self, key, default=None):
"""
Get configuration value with default.
Alias for as_str() method for compatibility.
Parameters:
- key: Configuration key name
- default: Default value if key not found
Returns:
String value or default
"""
def items(self):
"""
Get all configuration items.
Returns:
List of (key, value) tuples
"""
def has_option(self, key):
"""
Check if configuration key exists.
Parameters:
- key: Configuration key name
Returns:
True if key exists, False otherwise
"""Core command-line arguments supported by nose2.
# Test Discovery Arguments
def add_discovery_arguments(parser):
"""Add test discovery arguments to parser."""
parser.add_argument('-s', '--start-dir',
help='Directory to start discovery')
parser.add_argument('-t', '--top-level-directory',
help='Top level directory of project')
parser.add_argument('-p', '--pattern',
help='Pattern to match test files')
# Plugin Arguments
def add_plugin_arguments(parser):
"""Add plugin-related arguments to parser."""
parser.add_argument('--plugin',
help='Load this plugin module')
parser.add_argument('--exclude-plugin',
help='Do not load this plugin module')
parser.add_argument('--no-plugins',
help='Do not load any plugins')
# Configuration Arguments
def add_config_arguments(parser):
"""Add configuration file arguments to parser."""
parser.add_argument('-c', '--config',
help='Config files to load')
parser.add_argument('--no-user-config',
help='Do not load user config files')
# Output Arguments
def add_output_arguments(parser):
"""Add output control arguments to parser."""
parser.add_argument('-v', '--verbose',
help='Increase verbosity')
parser.add_argument('-q', '--quiet',
help='Decrease verbosity')
parser.add_argument('--log-level',
help='Set logging level')nose2 searches for configuration files in the following order:
--config or -cunittest.cfg, nose2.cfg, pyproject.toml (in start directory)~/.unittest.cfg, ~/.nose2.cfg (if --no-user-config not used)# unittest.cfg or nose2.cfg
[unittest]
# Core nose2 settings
start-dir = tests
top-level-directory = .
pattern = test_*.py
test-method-prefix = test
plugins = nose2.plugins.coverage
nose2.plugins.junitxml
my_custom_plugin
# Plugin-specific sections
[coverage]
always-on = True
coverage = mypackage
coverage-report = html
coverage-config = .coveragerc
[junitxml]
always-on = False
path = test-results.xml
[my-custom-plugin]
enabled = true
threshold = 1.0
output-file = results.txt[tool.nose2]
start-dir = "tests"
top-level-directory = "."
pattern = "test_*.py"
plugins = [
"nose2.plugins.coverage",
"nose2.plugins.junitxml"
]
[tool.nose2.coverage]
always-on = true
coverage = ["mypackage"]
coverage-report = "html"
[tool.nose2.junitxml]
path = "test-results.xml"from nose2.session import Session
from nose2.config import Config
# Create and configure session
session = Session()
# Load configuration files
session.loadConfigFiles('unittest.cfg', 'nose2.cfg')
# Get plugin configuration
coverage_config = session.get('coverage')
enabled = coverage_config.as_bool('always-on', default=False)
report_type = coverage_config.as_str('coverage-report', default='term')
packages = coverage_config.as_list('coverage', default=[])
print(f"Coverage enabled: {enabled}")
print(f"Report type: {report_type}")
print(f"Packages: {packages}")from nose2.events import Plugin
class MyPlugin(Plugin):
configSection = 'my-plugin'
def __init__(self):
# Extract all config values in __init__ for sphinx docs
self.enabled = self.config.as_bool('enabled', default=True)
self.threshold = self.config.as_float('threshold', default=1.0)
self.output_file = self.config.as_str('output-file', default='output.txt')
self.patterns = self.config.as_list('patterns', default=['*.py'])
self.debug = self.config.as_bool('debug', default=False)
# Check if required config is present
if not self.config.has_option('required-setting'):
raise ValueError("required-setting must be specified")
def some_method(self):
if self.enabled:
# Use configuration values
with open(self.output_file, 'w') as f:
f.write(f"Threshold: {self.threshold}\n")
f.write(f"Patterns: {self.patterns}\n")# Basic test discovery
nose2
# Custom start directory and pattern
nose2 --start-dir tests --pattern 'spec_*.py'
# Load specific plugins
nose2 --plugin nose2.plugins.coverage --plugin my_plugin
# Exclude plugins
nose2 --exclude-plugin nose2.plugins.buffer
# Custom configuration file
nose2 --config my_test_config.cfg
# Verbosity control
nose2 -v -v # Very verbose
nose2 -q # Quiet
# Logging level
nose2 --log-level DEBUG
# No user config
nose2 --no-user-config
# No plugins at all
nose2 --no-plugins# nose2.cfg - Complete configuration example
[unittest]
# Test discovery
start-dir = tests
top-level-directory = .
pattern = test_*.py
test-method-prefix = test
# Plugin loading
plugins = nose2.plugins.coverage
nose2.plugins.junitxml
nose2.plugins.mp
nose2.plugins.logcapture
custom_plugins.timing
custom_plugins.database
# Exclude problematic plugins
exclude-plugins = nose2.plugins.debugger
# Verbosity and logging
verbosity = 2
log-level = INFO
[coverage]
always-on = true
coverage = mypackage
mypackage.submodule
coverage-report = html
coverage-config = .coveragerc
fail-under = 80
[junitxml]
path = test-results/junit.xml
test-properties = name value
version 1.0
[mp]
processes = 4
test-timeout = 30
[logcapture]
always-on = true
clear-handlers = true
filter = -nose2
log-level = DEBUG
[timing]
enabled = true
threshold = 0.5
output-file = slow-tests.txt
[database]
url = postgresql://localhost/test_db
reset = true
fixtures = fixtures/users.json
fixtures/products.jsonimport os
from nose2.main import PluggableTestProgram
# Override config with environment variables
start_dir = os.environ.get('NOSE2_START_DIR', 'tests')
verbosity = int(os.environ.get('NOSE2_VERBOSITY', '1'))
plugins = os.environ.get('NOSE2_PLUGINS', '').split(',') if os.environ.get('NOSE2_PLUGINS') else []
# Run with environment-based configuration
PluggableTestProgram(
argv=['nose2', '--start-dir', start_dir, f'--verbosity={verbosity}'] +
[f'--plugin={p}' for p in plugins if p.strip()]
)from nose2.events import Plugin
class ValidatedPlugin(Plugin):
configSection = 'validated-plugin'
def __init__(self):
# Validate required settings
required_keys = ['api_key', 'endpoint']
for key in required_keys:
if not self.config.has_option(key):
raise ValueError(f"Missing required configuration: {key}")
# Extract and validate settings
self.api_key = self.config.as_str('api_key')
self.endpoint = self.config.as_str('endpoint')
self.timeout = self.config.as_int('timeout', default=30)
self.retries = self.config.as_int('retries', default=3)
# Validate ranges
if self.timeout < 1 or self.timeout > 300:
raise ValueError("timeout must be between 1 and 300 seconds")
if self.retries < 0 or self.retries > 10:
raise ValueError("retries must be between 0 and 10")
# Validate formats
if not self.endpoint.startswith('http'):
raise ValueError("endpoint must be a valid HTTP URL")Install with Tessl CLI
npx tessl i tessl/pypi-nose2