0
# Test Reporting and Results
1
2
Comprehensive reporting system for test execution results, collection reports, and terminal output formatting. These classes provide detailed information about test outcomes and handle presentation of results to users.
3
4
## Capabilities
5
6
### Test Execution Reports
7
8
Classes representing the results of test execution.
9
10
```python { .api }
11
@dataclasses.dataclass
12
class CallInfo(Generic[TResult]):
13
"""Result/Exception info of a function invocation."""
14
15
def __init__(
16
self,
17
result: TResult | None,
18
excinfo: ExceptionInfo[BaseException] | None,
19
start: float,
20
stop: float,
21
duration: float,
22
when: Literal["collect", "setup", "call", "teardown"],
23
_ispytest: bool = False,
24
) -> None:
25
"""
26
Initialize call information.
27
28
Parameters:
29
- result: Return value (None if exception occurred)
30
- excinfo: Exception info if raised
31
- start: Start time (seconds since epoch)
32
- stop: End time (seconds since epoch)
33
- duration: Call duration in seconds
34
- when: Invocation context
35
- _ispytest: Internal pytest flag
36
"""
37
38
# Attributes
39
_result: TResult | None # Private result storage
40
excinfo: ExceptionInfo[BaseException] | None # Captured exception
41
start: float # Start timestamp
42
stop: float # End timestamp
43
duration: float # Execution duration
44
when: str # Context of invocation ("collect", "setup", "call", "teardown")
45
46
@property
47
def result(self) -> TResult:
48
"""
49
Get the call result.
50
51
Returns:
52
The function result
53
54
Raises:
55
AttributeError: If excinfo is not None (exception occurred)
56
"""
57
58
@classmethod
59
def from_call(
60
cls,
61
func: Callable[[], TResult],
62
when: str,
63
reraise=None
64
) -> CallInfo[TResult]:
65
"""
66
Execute function and capture call information.
67
68
Parameters:
69
- func: Function to execute
70
- when: Context identifier
71
- reraise: Exception types to reraise immediately
72
73
Returns:
74
CallInfo with execution results and timing
75
"""
76
77
class TestReport(BaseReport):
78
"""Basic test report object for test execution results.
79
80
Also used for setup and teardown calls if they fail.
81
Reports can contain arbitrary extra attributes.
82
"""
83
84
def __init__(
85
self,
86
nodeid: str,
87
location: tuple[str, int | None, str],
88
keywords: Mapping[str, Any],
89
outcome: Literal["passed", "failed", "skipped"],
90
longrepr: None | ExceptionInfo | tuple[str, int, str] | str | TerminalRepr,
91
when: Literal["setup", "call", "teardown"],
92
sections: Iterable[tuple[str, str]] = (),
93
duration: float = 0,
94
start: float = 0,
95
stop: float = 0,
96
user_properties: Iterable[tuple[str, object]] | None = None,
97
**extra
98
) -> None:
99
"""
100
Initialize test report.
101
102
Parameters:
103
- nodeid: Normalized collection nodeid
104
- location: File path, line number, domain info
105
- keywords: Name->value dict of keywords/markers
106
- outcome: Test outcome ("passed", "failed", "skipped")
107
- longrepr: Failure representation
108
- when: Test phase ("setup", "call", "teardown")
109
- sections: Extra information tuples (heading, content)
110
- duration: Test execution time in seconds
111
- start: Start time (seconds since epoch)
112
- stop: End time (seconds since epoch)
113
- user_properties: User-defined properties
114
- **extra: Additional arbitrary attributes
115
"""
116
117
# Core attributes
118
nodeid: str # Test identifier
119
location: tuple[str, int | None, str] # Test location info
120
keywords: Mapping[str, Any] # Associated keywords and markers
121
outcome: str # One of "passed", "failed", "skipped"
122
longrepr # Failure representation (None for passed tests)
123
when: str # Phase: "setup", "call", or "teardown"
124
user_properties: list[tuple[str, object]] # User-defined test properties
125
sections: list[tuple[str, str]] # Captured output sections
126
duration: float # Execution duration
127
start: float # Start timestamp
128
stop: float # End timestamp
129
wasxfail: str # XFail reason (if applicable)
130
131
@classmethod
132
def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport:
133
"""
134
Create TestReport from test item and call info.
135
136
Parameters:
137
- item: Test item that was executed
138
- call: Call information from execution
139
140
Returns:
141
TestReport with complete execution information
142
"""
143
144
def _to_json(self) -> dict[str, Any]:
145
"""
146
Serialize report to JSON-compatible dict.
147
148
Returns:
149
Dictionary representation for JSON serialization
150
"""
151
152
@classmethod
153
def _from_json(cls, reportdict: dict[str, object]) -> TestReport:
154
"""
155
Deserialize TestReport from JSON dict.
156
157
Parameters:
158
- reportdict: Dictionary from JSON deserialization
159
160
Returns:
161
TestReport instance
162
"""
163
164
class CollectReport(BaseReport):
165
"""Collection report object representing collection results.
166
167
Reports can contain arbitrary extra attributes.
168
"""
169
170
def __init__(
171
self,
172
nodeid: str,
173
outcome: Literal["passed", "failed", "skipped"],
174
longrepr: None | ExceptionInfo | tuple[str, int, str] | str | TerminalRepr,
175
result: list[Item | Collector] | None,
176
sections: Iterable[tuple[str, str]] = (),
177
**extra
178
) -> None:
179
"""
180
Initialize collection report.
181
182
Parameters:
183
- nodeid: Normalized collection nodeid
184
- outcome: Collection outcome ("passed", "failed", "skipped")
185
- longrepr: Failure representation
186
- result: Collected items and nodes
187
- sections: Extra information sections
188
- **extra: Additional arbitrary attributes
189
"""
190
191
# Attributes
192
when = "collect" # Always "collect" for collection reports
193
nodeid: str # Collection node identifier
194
outcome: str # Collection result
195
longrepr # Collection failure details (if any)
196
result: list[Item | Collector] # Successfully collected items
197
sections: list[tuple[str, str]] # Captured output during collection
198
199
@property
200
def location(self) -> tuple[str, None, str]:
201
"""
202
Get collection location.
203
204
Returns:
205
Tuple of (fspath, None, fspath)
206
"""
207
208
def _to_json(self) -> dict[str, Any]:
209
"""
210
Serialize report to JSON-compatible dict.
211
212
Returns:
213
Dictionary representation for JSON serialization
214
"""
215
216
@classmethod
217
def _from_json(cls, reportdict: dict[str, object]) -> CollectReport:
218
"""
219
Deserialize CollectReport from JSON dict.
220
221
Parameters:
222
- reportdict: Dictionary from JSON deserialization
223
224
Returns:
225
CollectReport instance
226
"""
227
```
228
229
### Terminal Output and Formatting
230
231
Classes controlling terminal output during test execution.
232
233
```python { .api }
234
class TestShortLogReport(NamedTuple):
235
"""Container for test status information used in terminal reporting.
236
237
Used to store the test status result category, shortletter and verbose word.
238
For example "rerun", "R", ("RERUN", {"yellow": True}).
239
"""
240
241
category: str # Result class ("passed", "skipped", "error", or empty)
242
letter: str # Short letter for progress (".", "s", "E", or empty)
243
word: str | tuple[str, Mapping[str, bool]] # Verbose word or (word, markup) tuple
244
245
class TerminalReporter:
246
"""Controls terminal output formatting and reporting during test execution.
247
248
Main class responsible for all terminal output including progress indicators,
249
test results, summaries, and formatting.
250
"""
251
252
def __init__(self, config: Config, file: TextIO | None = None) -> None:
253
"""
254
Initialize terminal reporter.
255
256
Parameters:
257
- config: pytest configuration object
258
- file: Output stream (defaults to sys.stdout)
259
"""
260
261
# Core attributes
262
config: Config # pytest configuration
263
stats: dict[str, list[Any]] # Statistics by category (failed, passed, etc.)
264
_tw: TerminalWriter # Terminal writer for output
265
reportchars: str # Characters controlling what to report
266
hasmarkup: bool # Whether terminal supports markup
267
isatty: bool # Whether output is to a terminal
268
verbosity: int # Verbosity level
269
showfspath: bool # Whether to show file paths
270
showlongtestinfo: bool # Whether to show detailed test info
271
_session: Session | None # Current test session
272
_numcollected: int # Number of collected items
273
currentfspath: Path | str | int | None # Current file being processed
274
275
def write(self, content: str, *, flush: bool = False, **markup: bool) -> None:
276
"""
277
Write content to terminal.
278
279
Parameters:
280
- content: Content to write
281
- flush: Whether to flush output immediately
282
- **markup: Markup options (bold, red, etc.)
283
"""
284
285
def write_line(self, line: str | bytes, **markup: bool) -> None:
286
"""
287
Write line with newline to terminal.
288
289
Parameters:
290
- line: Line content
291
- **markup: Markup options
292
"""
293
294
def write_sep(self, sep: str, title: str | None = None, **markup: bool) -> None:
295
"""
296
Write separator line.
297
298
Parameters:
299
- sep: Separator character
300
- title: Optional title to include
301
- **markup: Markup options
302
"""
303
304
def rewrite(self, line: str, **markup: bool) -> None:
305
"""
306
Rewrite current line (for progress indicators).
307
308
Parameters:
309
- line: New line content
310
- **markup: Markup options
311
"""
312
313
def section(self, title: str, sep: str = "=", **kw: bool) -> None:
314
"""
315
Write section header.
316
317
Parameters:
318
- title: Section title
319
- sep: Separator character
320
- **kw: Keyword arguments for formatting
321
"""
322
323
def pytest_runtest_logreport(self, report: TestReport) -> None:
324
"""
325
Handle test report logging.
326
327
Parameters:
328
- report: Test execution report
329
"""
330
331
def pytest_collectreport(self, report: CollectReport) -> None:
332
"""
333
Handle collection report logging.
334
335
Parameters:
336
- report: Collection report
337
"""
338
339
def pytest_sessionstart(self, session: Session) -> None:
340
"""
341
Handle session start.
342
343
Parameters:
344
- session: Test session
345
"""
346
347
def pytest_sessionfinish(self, session: Session, exitstatus: int | ExitCode) -> None:
348
"""
349
Handle session finish.
350
351
Parameters:
352
- session: Test session
353
- exitstatus: Exit status code
354
"""
355
356
def summary_failures(self) -> None:
357
"""Show failure summary section."""
358
359
def summary_errors(self) -> None:
360
"""Show error summary section."""
361
362
def summary_warnings(self) -> None:
363
"""Show warning summary section."""
364
365
def short_test_summary(self) -> None:
366
"""Show short test summary section."""
367
368
def build_summary_stats_line(self) -> tuple[list[tuple[str, dict[str, bool]]], str]:
369
"""
370
Build final statistics line.
371
372
Returns:
373
Tuple of (stat_parts, duration_string)
374
"""
375
```
376
377
## Usage Examples
378
379
### Creating Custom Reports
380
381
```python
382
import pytest
383
from _pytest.reports import TestReport, CollectReport
384
385
# Custom plugin to process test reports
386
class CustomReportPlugin:
387
def __init__(self):
388
self.test_results = []
389
390
def pytest_runtest_logreport(self, report: TestReport):
391
# Process test reports
392
if report.when == "call": # Only process main test execution
393
self.test_results.append({
394
'nodeid': report.nodeid,
395
'outcome': report.outcome,
396
'duration': report.duration,
397
'location': report.location,
398
'user_properties': report.user_properties,
399
})
400
401
def pytest_collection_finish(self, session):
402
# Process collection results
403
for item in session.items:
404
print(f"Collected test: {item.nodeid}")
405
406
# Register plugin
407
pytest.main(["-p", "no:terminal", "--tb=no"], plugins=[CustomReportPlugin()])
408
```
409
410
### Custom Terminal Output
411
412
```python
413
from _pytest.terminal import TerminalReporter
414
415
class ColorfulReporter(TerminalReporter):
416
def pytest_runtest_logreport(self, report):
417
# Custom colored output
418
if report.when == "call":
419
if report.outcome == "passed":
420
self.write("✓ ", green=True)
421
elif report.outcome == "failed":
422
self.write("✗ ", red=True)
423
elif report.outcome == "skipped":
424
self.write("- ", yellow=True)
425
426
self.write_line(f"{report.nodeid}")
427
428
# Call parent implementation for other functionality
429
super().pytest_runtest_logreport(report)
430
431
# Use custom reporter
432
def pytest_configure(config):
433
if config.pluginmanager.get_plugin("terminalreporter"):
434
config.pluginmanager.unregister(name="terminalreporter")
435
436
config.pluginmanager.register(ColorfulReporter(config), "terminalreporter")
437
```
438
439
### Accessing Call Information
440
441
```python
442
from _pytest.runner import CallInfo
443
444
def test_with_call_info():
445
# CallInfo is typically used internally, but can be accessed in hooks
446
def example_function():
447
return "result"
448
449
# Capture call information
450
call_info = CallInfo.from_call(example_function, when="call")
451
452
assert call_info.result == "result"
453
assert call_info.excinfo is None
454
assert call_info.duration > 0
455
assert call_info.when == "call"
456
457
# Plugin to access call information in hooks
458
def pytest_runtest_call(pyfuncitem):
459
# This hook has access to CallInfo through the test execution
460
pass
461
462
def pytest_runtest_makereport(item, call):
463
# call parameter is a CallInfo object
464
if call.when == "call" and call.excinfo is None:
465
print(f"Test {item.nodeid} passed in {call.duration:.2f}s")
466
```
467
468
## Integration with Reporting Tools
469
470
### JSON Report Generation
471
472
```python
473
import json
474
from _pytest.reports import TestReport, CollectReport
475
476
class JSONReporter:
477
def __init__(self):
478
self.reports = []
479
480
def pytest_runtest_logreport(self, report: TestReport):
481
# Convert TestReport to JSON-serializable format
482
report_data = report._to_json()
483
self.reports.append(report_data)
484
485
def pytest_sessionfinish(self, session):
486
# Write JSON report
487
with open("test_report.json", "w") as f:
488
json.dump(self.reports, f, indent=2)
489
490
# Usage
491
pytest.main(["--tb=short"], plugins=[JSONReporter()])
492
```
493
494
### Database Integration
495
496
```python
497
import sqlite3
498
from _pytest.reports import TestReport
499
500
class DatabaseReporter:
501
def __init__(self):
502
self.conn = sqlite3.connect("test_results.db")
503
self.setup_database()
504
505
def setup_database(self):
506
self.conn.execute("""
507
CREATE TABLE IF NOT EXISTS test_results (
508
nodeid TEXT,
509
outcome TEXT,
510
duration REAL,
511
when_phase TEXT,
512
timestamp REAL,
513
location_file TEXT,
514
location_line INTEGER
515
)
516
""")
517
518
def pytest_runtest_logreport(self, report: TestReport):
519
if report.when == "call":
520
self.conn.execute("""
521
INSERT INTO test_results
522
(nodeid, outcome, duration, when_phase, timestamp, location_file, location_line)
523
VALUES (?, ?, ?, ?, ?, ?, ?)
524
""", (
525
report.nodeid,
526
report.outcome,
527
report.duration,
528
report.when,
529
report.start,
530
report.location[0],
531
report.location[1]
532
))
533
self.conn.commit()
534
535
# Usage with database integration
536
pytest.main(["-v"], plugins=[DatabaseReporter()])
537
```