0
# Configuration Management
1
2
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.
3
4
## Capabilities
5
6
### Configuration Class
7
8
The Config class provides type-safe access to configuration values with default handling.
9
10
```python { .api }
11
class Config:
12
"""
13
Configuration for a plugin or other entities.
14
15
Encapsulates configuration for a single plugin or element.
16
Corresponds to a ConfigParser section but provides extended
17
interface for extracting items as specific types.
18
"""
19
20
def __init__(self, items):
21
"""
22
Initialize configuration with key-value pairs.
23
24
Parameters:
25
- items: List of (key, value) tuples from config section
26
"""
27
28
def __getitem__(self, key):
29
"""
30
Get raw configuration values for key.
31
32
Parameters:
33
- key: Configuration key name
34
35
Returns:
36
List of values for the key
37
"""
38
39
def as_bool(self, key, default=None):
40
"""
41
Get key value as boolean.
42
43
1, t, true, on, yes, y (case insensitive) are accepted as True.
44
All other values are False. Empty setting (key=) returns False.
45
46
Parameters:
47
- key: Configuration key name
48
- default: Default value if key not found
49
50
Returns:
51
Boolean value or default
52
"""
53
54
def as_int(self, key, default=None):
55
"""
56
Get key value as integer.
57
58
Parameters:
59
- key: Configuration key name
60
- default: Default value if key not found
61
62
Returns:
63
Integer value or default
64
"""
65
66
def as_float(self, key, default=None):
67
"""
68
Get key value as float.
69
70
Parameters:
71
- key: Configuration key name
72
- default: Default value if key not found
73
74
Returns:
75
Float value or default
76
"""
77
78
def as_str(self, key, default=None):
79
"""
80
Get key value as string.
81
82
Parameters:
83
- key: Configuration key name
84
- default: Default value if key not found
85
86
Returns:
87
String value or default
88
"""
89
90
def as_list(self, key, default=None):
91
"""
92
Get key value as list.
93
94
The value is split into lines and returned as a list. Lines
95
are stripped of whitespace, and lines beginning with # are
96
skipped.
97
98
Parameters:
99
- key: Configuration key name
100
- default: Default value if key not found
101
102
Returns:
103
List of string values or default
104
"""
105
106
def get(self, key, default=None):
107
"""
108
Get configuration value with default.
109
110
Alias for as_str() method for compatibility.
111
112
Parameters:
113
- key: Configuration key name
114
- default: Default value if key not found
115
116
Returns:
117
String value or default
118
"""
119
120
def items(self):
121
"""
122
Get all configuration items.
123
124
Returns:
125
List of (key, value) tuples
126
"""
127
128
def has_option(self, key):
129
"""
130
Check if configuration key exists.
131
132
Parameters:
133
- key: Configuration key name
134
135
Returns:
136
True if key exists, False otherwise
137
"""
138
```
139
140
### Command Line Arguments
141
142
Core command-line arguments supported by nose2.
143
144
```python { .api }
145
# Test Discovery Arguments
146
def add_discovery_arguments(parser):
147
"""Add test discovery arguments to parser."""
148
parser.add_argument('-s', '--start-dir',
149
help='Directory to start discovery')
150
parser.add_argument('-t', '--top-level-directory',
151
help='Top level directory of project')
152
parser.add_argument('-p', '--pattern',
153
help='Pattern to match test files')
154
155
# Plugin Arguments
156
def add_plugin_arguments(parser):
157
"""Add plugin-related arguments to parser."""
158
parser.add_argument('--plugin',
159
help='Load this plugin module')
160
parser.add_argument('--exclude-plugin',
161
help='Do not load this plugin module')
162
parser.add_argument('--no-plugins',
163
help='Do not load any plugins')
164
165
# Configuration Arguments
166
def add_config_arguments(parser):
167
"""Add configuration file arguments to parser."""
168
parser.add_argument('-c', '--config',
169
help='Config files to load')
170
parser.add_argument('--no-user-config',
171
help='Do not load user config files')
172
173
# Output Arguments
174
def add_output_arguments(parser):
175
"""Add output control arguments to parser."""
176
parser.add_argument('-v', '--verbose',
177
help='Increase verbosity')
178
parser.add_argument('-q', '--quiet',
179
help='Decrease verbosity')
180
parser.add_argument('--log-level',
181
help='Set logging level')
182
```
183
184
## Configuration Files
185
186
### Configuration File Locations
187
188
nose2 searches for configuration files in the following order:
189
190
1. Files specified with `--config` or `-c`
191
2. Project files: `unittest.cfg`, `nose2.cfg`, `pyproject.toml` (in start directory)
192
3. User files: `~/.unittest.cfg`, `~/.nose2.cfg` (if `--no-user-config` not used)
193
194
### Configuration File Format
195
196
```ini
197
# unittest.cfg or nose2.cfg
198
[unittest]
199
# Core nose2 settings
200
start-dir = tests
201
top-level-directory = .
202
pattern = test_*.py
203
test-method-prefix = test
204
plugins = nose2.plugins.coverage
205
nose2.plugins.junitxml
206
my_custom_plugin
207
208
# Plugin-specific sections
209
[coverage]
210
always-on = True
211
coverage = mypackage
212
coverage-report = html
213
coverage-config = .coveragerc
214
215
[junitxml]
216
always-on = False
217
path = test-results.xml
218
219
[my-custom-plugin]
220
enabled = true
221
threshold = 1.0
222
output-file = results.txt
223
```
224
225
### TOML Configuration (pyproject.toml)
226
227
```toml
228
[tool.nose2]
229
start-dir = "tests"
230
top-level-directory = "."
231
pattern = "test_*.py"
232
plugins = [
233
"nose2.plugins.coverage",
234
"nose2.plugins.junitxml"
235
]
236
237
[tool.nose2.coverage]
238
always-on = true
239
coverage = ["mypackage"]
240
coverage-report = "html"
241
242
[tool.nose2.junitxml]
243
path = "test-results.xml"
244
```
245
246
## Usage Examples
247
248
### Programmatic Configuration
249
250
```python
251
from nose2.session import Session
252
from nose2.config import Config
253
254
# Create and configure session
255
session = Session()
256
257
# Load configuration files
258
session.loadConfigFiles('unittest.cfg', 'nose2.cfg')
259
260
# Get plugin configuration
261
coverage_config = session.get('coverage')
262
enabled = coverage_config.as_bool('always-on', default=False)
263
report_type = coverage_config.as_str('coverage-report', default='term')
264
packages = coverage_config.as_list('coverage', default=[])
265
266
print(f"Coverage enabled: {enabled}")
267
print(f"Report type: {report_type}")
268
print(f"Packages: {packages}")
269
```
270
271
### Plugin Configuration
272
273
```python
274
from nose2.events import Plugin
275
276
class MyPlugin(Plugin):
277
configSection = 'my-plugin'
278
279
def __init__(self):
280
# Extract all config values in __init__ for sphinx docs
281
self.enabled = self.config.as_bool('enabled', default=True)
282
self.threshold = self.config.as_float('threshold', default=1.0)
283
self.output_file = self.config.as_str('output-file', default='output.txt')
284
self.patterns = self.config.as_list('patterns', default=['*.py'])
285
self.debug = self.config.as_bool('debug', default=False)
286
287
# Check if required config is present
288
if not self.config.has_option('required-setting'):
289
raise ValueError("required-setting must be specified")
290
291
def some_method(self):
292
if self.enabled:
293
# Use configuration values
294
with open(self.output_file, 'w') as f:
295
f.write(f"Threshold: {self.threshold}\n")
296
f.write(f"Patterns: {self.patterns}\n")
297
```
298
299
### Command Line Usage
300
301
```bash
302
# Basic test discovery
303
nose2
304
305
# Custom start directory and pattern
306
nose2 --start-dir tests --pattern 'spec_*.py'
307
308
# Load specific plugins
309
nose2 --plugin nose2.plugins.coverage --plugin my_plugin
310
311
# Exclude plugins
312
nose2 --exclude-plugin nose2.plugins.buffer
313
314
# Custom configuration file
315
nose2 --config my_test_config.cfg
316
317
# Verbosity control
318
nose2 -v -v # Very verbose
319
nose2 -q # Quiet
320
321
# Logging level
322
nose2 --log-level DEBUG
323
324
# No user config
325
nose2 --no-user-config
326
327
# No plugins at all
328
nose2 --no-plugins
329
```
330
331
### Complex Configuration Example
332
333
```ini
334
# nose2.cfg - Complete configuration example
335
[unittest]
336
# Test discovery
337
start-dir = tests
338
top-level-directory = .
339
pattern = test_*.py
340
test-method-prefix = test
341
342
# Plugin loading
343
plugins = nose2.plugins.coverage
344
nose2.plugins.junitxml
345
nose2.plugins.mp
346
nose2.plugins.logcapture
347
custom_plugins.timing
348
custom_plugins.database
349
350
# Exclude problematic plugins
351
exclude-plugins = nose2.plugins.debugger
352
353
# Verbosity and logging
354
verbosity = 2
355
log-level = INFO
356
357
[coverage]
358
always-on = true
359
coverage = mypackage
360
mypackage.submodule
361
coverage-report = html
362
coverage-config = .coveragerc
363
fail-under = 80
364
365
[junitxml]
366
path = test-results/junit.xml
367
test-properties = name value
368
version 1.0
369
370
[mp]
371
processes = 4
372
test-timeout = 30
373
374
[logcapture]
375
always-on = true
376
clear-handlers = true
377
filter = -nose2
378
log-level = DEBUG
379
380
[timing]
381
enabled = true
382
threshold = 0.5
383
output-file = slow-tests.txt
384
385
[database]
386
url = postgresql://localhost/test_db
387
reset = true
388
fixtures = fixtures/users.json
389
fixtures/products.json
390
```
391
392
### Environment Variable Configuration
393
394
```python
395
import os
396
from nose2.main import PluggableTestProgram
397
398
# Override config with environment variables
399
start_dir = os.environ.get('NOSE2_START_DIR', 'tests')
400
verbosity = int(os.environ.get('NOSE2_VERBOSITY', '1'))
401
plugins = os.environ.get('NOSE2_PLUGINS', '').split(',') if os.environ.get('NOSE2_PLUGINS') else []
402
403
# Run with environment-based configuration
404
PluggableTestProgram(
405
argv=['nose2', '--start-dir', start_dir, f'--verbosity={verbosity}'] +
406
[f'--plugin={p}' for p in plugins if p.strip()]
407
)
408
```
409
410
### Configuration Validation
411
412
```python
413
from nose2.events import Plugin
414
415
class ValidatedPlugin(Plugin):
416
configSection = 'validated-plugin'
417
418
def __init__(self):
419
# Validate required settings
420
required_keys = ['api_key', 'endpoint']
421
for key in required_keys:
422
if not self.config.has_option(key):
423
raise ValueError(f"Missing required configuration: {key}")
424
425
# Extract and validate settings
426
self.api_key = self.config.as_str('api_key')
427
self.endpoint = self.config.as_str('endpoint')
428
self.timeout = self.config.as_int('timeout', default=30)
429
self.retries = self.config.as_int('retries', default=3)
430
431
# Validate ranges
432
if self.timeout < 1 or self.timeout > 300:
433
raise ValueError("timeout must be between 1 and 300 seconds")
434
435
if self.retries < 0 or self.retries > 10:
436
raise ValueError("retries must be between 0 and 10")
437
438
# Validate formats
439
if not self.endpoint.startswith('http'):
440
raise ValueError("endpoint must be a valid HTTP URL")
441
```