Skip to content

Commit db37862

Browse files
Humanise test names for pytest report (#148)
* Humanise test names for pytest report Where we don't have docstrings for tests, create a human-readable description based on the test/class name. * Add docstrings for all tests Roll back the script for auto-generating test descriptions in the report.
1 parent 3cc5ec1 commit db37862

28 files changed

Lines changed: 194 additions & 8 deletions

tests/conftest.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ def pytest_html_report_title(report):
2222

2323
def _readable_test_name(item):
2424
"""
25-
Build a human-readable name for the test report from the test's
26-
docstring. Returns None when there is no docstring, so the default
27-
nodeid is kept.
25+
Use the test's docstring (first line) as its name in the report. Every
26+
test is expected to have a docstring; falls back to the default node id
27+
if one is missing.
2828
"""
2929
test_fn = getattr(item, "obj", None)
3030
docstring = inspect.getdoc(test_fn) if test_fn else None
@@ -33,18 +33,18 @@ def _readable_test_name(item):
3333

3434
# First non-empty line, whitespace collapsed — docstrings are often
3535
# multi-line and indented, which would render badly as a node id.
36-
first_line = next((line.strip() for line in docstring.splitlines() if line.strip()), "")
37-
if not first_line:
36+
base = next((line.strip() for line in docstring.splitlines() if line.strip()), "")
37+
if not base:
3838
return None
3939

4040
# Keep parametrised cases distinct (they share one docstring).
4141
param_id = getattr(getattr(item, "callspec", None), "id", None)
42-
return f"{first_line} [{param_id}]" if param_id else first_line
42+
return f"{base} [{param_id}]" if param_id else base
4343

4444

4545
@pytest.hookimpl(hookwrapper=True)
4646
def pytest_runtest_makereport(item, call):
47-
"""Use the test's docstring as its name in the HTML report, when present."""
47+
"""Use the test's docstring as its name in the report."""
4848
outcome = yield
4949
report = outcome.get_result()
5050

tests/integration/test_c_find_returns_worklist_items.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def event(self):
6363
return event
6464

6565
def test_cfind_returns_scheduled_items(self, event, storage):
66+
"""C-FIND returns scheduled items."""
6667
results = list(CFind(storage).call(event))
6768
assert len(results) == 3
6869

@@ -97,6 +98,7 @@ def test_cfind_returns_scheduled_items(self, event, storage):
9798
assert ds is None
9899

99100
def test_cfind_filters_by_scheduled_date_range(self, event, storage):
101+
"""C-FIND filters by scheduled date range."""
100102
event.identifier.ScheduledProcedureStepSequence[0].ScheduledProcedureStepStartDate = "20240101-20240201"
101103

102104
results = list(CFind(storage).call(event))
@@ -117,6 +119,7 @@ def test_cfind_filters_by_scheduled_date_range(self, event, storage):
117119
assert ds is None
118120

119121
def test_cfind_filters_by_accession_number(self, event, storage):
122+
"""C-FIND filters by accession number."""
120123
event.identifier.AccessionNumber = "ACC234567"
121124
results = list(CFind(storage).call(event))
122125
assert len(results) == 2
@@ -128,6 +131,7 @@ def test_cfind_filters_by_accession_number(self, event, storage):
128131
assert ds.AccessionNumber == "ACC234567"
129132

130133
def test_cfind_filters_by_before_scheduled_date(self, event, storage):
134+
"""C-FIND filters by before scheduled date."""
131135
event.identifier.ScheduledProcedureStepSequence[0].ScheduledProcedureStepStartDate = "-20240101"
132136

133137
results = list(CFind(storage).call(event))
@@ -148,6 +152,7 @@ def test_cfind_filters_by_before_scheduled_date(self, event, storage):
148152
assert ds is None
149153

150154
def test_cfind_filters_by_after_scheduled_date(self, event, storage):
155+
"""C-FIND filters by after scheduled date."""
151156
event.identifier.ScheduledProcedureStepSequence[0].ScheduledProcedureStepStartDate = "20240201-"
152157
results = list(CFind(storage).call(event))
153158

@@ -167,6 +172,7 @@ def test_cfind_filters_by_after_scheduled_date(self, event, storage):
167172
assert ds is None
168173

169174
def test_cfind_filters_by_scheduled_time_range(self, event, storage):
175+
"""C-FIND filters by scheduled time range."""
170176
event.identifier.ScheduledProcedureStepSequence[0].ScheduledProcedureStepStartTime = "090000-093000"
171177

172178
results = list(CFind(storage).call(event))
@@ -187,6 +193,7 @@ def test_cfind_filters_by_scheduled_time_range(self, event, storage):
187193
assert ds is None
188194

189195
def test_cfind_filters_by_before_scheduled_time(self, event, storage):
196+
"""C-FIND filters by before scheduled time."""
190197
event.identifier.ScheduledProcedureStepSequence[0].ScheduledProcedureStepStartTime = "-093000"
191198

192199
results = list(CFind(storage).call(event))
@@ -207,6 +214,7 @@ def test_cfind_filters_by_before_scheduled_time(self, event, storage):
207214
assert ds is None
208215

209216
def test_cfind_filters_by_after_scheduled_time(self, event, storage):
217+
"""C-FIND filters by after scheduled time."""
210218
event.identifier.ScheduledProcedureStepSequence[0].ScheduledProcedureStepStartTime = "093000-"
211219

212220
results = list(CFind(storage).call(event))
@@ -227,6 +235,7 @@ def test_cfind_filters_by_after_scheduled_time(self, event, storage):
227235
assert ds is None
228236

229237
def test_cfind_filters_by_date_and_time_range(self, event, storage):
238+
"""C-FIND filters by date and time range."""
230239
event.identifier.ScheduledProcedureStepSequence[0].ScheduledProcedureStepStartDate = "20240101-20240201"
231240
event.identifier.ScheduledProcedureStepSequence[0].ScheduledProcedureStepStartTime = "090000-093000"
232241

@@ -249,6 +258,7 @@ def test_cfind_filters_by_date_and_time_range(self, event, storage):
249258
assert ds is None
250259

251260
def test_cfind_filters_by_modality(self, event, storage):
261+
"""C-FIND filters by modality."""
252262
storage.store_worklist_item(
253263
WorklistItem(
254264
accession_number="ACC999999",
@@ -284,6 +294,7 @@ def test_cfind_filters_by_modality(self, event, storage):
284294
assert ds is None
285295

286296
def test_cfind_filters_by_patient_id(self, event, storage):
297+
"""C-FIND filters by patient id."""
287298
event.identifier.PatientID = "999234567"
288299

289300
results = list(CFind(storage).call(event))

tests/integration/test_c_store_saves_metadata.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def mwl_storage(self, tmp_dir):
4242
return MWLStorage(f"{tmp_dir}/worklist.db")
4343

4444
def test_existing_sop_instance_uid(self, storage, mock_event):
45+
"""Existing SOP instance UID."""
4546
sop_instance_uid = "1.2.3.4.5.6" # gitleaks:allow
4647
subject = CStore(storage)
4748
mock_event.dataset.file_meta = mock_event.file_meta
@@ -67,6 +68,7 @@ def test_existing_sop_instance_uid(self, storage, mock_event):
6768
assert len(results) == 1
6869

6970
def test_valid_event_is_stored(self, storage, mock_event):
71+
"""Valid event is stored."""
7072
subject = CStore(storage)
7173

7274
assert subject.call(mock_event) == SUCCESS
@@ -91,6 +93,7 @@ def test_valid_event_is_stored(self, storage, mock_event):
9193
assert Path(f"{storage.storage_root}/{storage_path}").is_file()
9294

9395
def test_c_store_marks_worklist_in_progress(self, storage, mwl_storage, mock_event):
96+
"""C-STORE marks worklist in progress."""
9497
item = WorklistItem(
9598
accession_number="ABC123",
9699
modality="MG",

tests/integration/test_mwl_storage_patient_name_search.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,31 +41,38 @@ def items(storage):
4141

4242
class TestPatientNameSearch:
4343
def test_trailing_wildcard(self, storage):
44+
"""Patient name search: Trailing wildcard."""
4445
results = storage.find_worklist_items(patient_name="SMITH*")
4546
assert {r.patient_name for r in results} == {"SMITH^SARITA", "SMITH^JANE"}
4647

4748
def test_wildcard_on_given_name(self, storage):
49+
"""Wildcard on given name."""
4850
results = storage.find_worklist_items(patient_name="*SARITA")
4951
assert {r.patient_name for r in results} == {"SMITH^SARITA", "JONES^SARITA"}
5052

5153
def test_single_character_wildcard(self, storage):
54+
"""Single character wildcard."""
5255
results = storage.find_worklist_items(patient_name="SMITH^J?NE")
5356
assert {r.patient_name for r in results} == {"SMITH^JANE"}
5457

5558
def test_exact_match(self, storage):
59+
"""Patient name search: Exact match."""
5660
results = storage.find_worklist_items(patient_name="JONES^SARITA")
5761
assert len(results) == 1
5862
assert results[0].patient_name == "JONES^SARITA"
5963

6064
def test_no_match(self, storage):
65+
"""Patient name search: No match."""
6166
results = storage.find_worklist_items(patient_name="BROWN*")
6267
assert results == []
6368

6469
def test_case_insensitive_match(self, storage):
70+
"""Case insensitive match."""
6571
results = storage.find_worklist_items(patient_name="smith*")
6672
assert {r.patient_name for r in results} == {"SMITH^SARITA", "SMITH^JANE"}
6773

6874
@pytest.mark.xfail(reason="SQLite's UPPER() is ASCII-only; non-ASCII case folding requires ICU compilation")
6975
def test_case_insensitive_non_ascii(self, storage):
76+
"""Case insensitive non ascii."""
7077
results = storage.find_worklist_items(patient_name="müller*")
7178
assert {r.patient_name for r in results} == {"MÜLLER^DILMA"}

tests/integration/test_n_create_updates_worklist_status.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def worklist_item(self):
3939
)
4040

4141
def test_n_create_updates_worklist_status(self, tmp_dir, worklist_item):
42+
"""N-CREATE updates worklist status."""
4243
storage = MWLStorage(f"{tmp_dir}/test.db")
4344
study_instance_uid = generate_uid()
4445
accession_number = storage.store_worklist_item(worklist_item)

tests/integration/test_n_set_updates_worklist_status.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def worklist_item(self):
4343
)
4444

4545
def test_n_set_updates_worklist_status(self, tmp_dir, worklist_item, mpps_instance_uid):
46+
"""N-SET updates worklist status."""
4647
storage = MWLStorage(f"{tmp_dir}/test.db")
4748
accession_number = storage.store_worklist_item(worklist_item)
4849
storage.update_status(accession_number, "IN PROGRESS", mpps_instance_uid)

tests/integration/test_relay_listener_processes_actions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def update_payload(self):
1818

1919
@pytest.mark.asyncio
2020
async def test_relay_listener_creates_worklist_items(self, listener_payload, tmp_dir, fake_relay):
21+
"""Relay listener creates worklist items."""
2122
storage = MWLStorage(f"{tmp_dir}/test_worklist.db")
2223
listener = RelayListener(storage)
2324
relay_message = json.dumps({"accept": {"address": "wss://accept-url"}})
@@ -34,6 +35,7 @@ async def test_relay_listener_creates_worklist_items(self, listener_payload, tmp
3435

3536
@pytest.mark.asyncio
3637
async def test_relay_listener_updates_worklist_item_status(self, update_payload, tmp_dir, fake_relay):
38+
"""Relay listener updates worklist item status."""
3739
storage = MWLStorage(f"{tmp_dir}/test_worklist.db")
3840
listener = RelayListener(storage)
3941
relay_message = json.dumps({"accept": {"address": "wss://accept-url"}})

tests/integration/test_request_cfind_on_worklist.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def with_pacs_server(self, tmp_dir):
5454
server.stop()
5555

5656
def test_cfind_request_to_worklist_server(self):
57+
"""C-FIND request to worklist server."""
5758
ae = AE(ae_title="LOCAL_AE_TITLE")
5859
ae.add_requested_context(ModalityWorklistInformationFind)
5960
assoc = ae.associate("0.0.0.0", 4243, ae_title="MWL_SCP_AE_TITLE")
@@ -82,6 +83,7 @@ def test_cfind_request_to_worklist_server(self):
8283
assert ds is None
8384

8485
def test_cfind_with_filters_request_to_worklist_server(self):
86+
"""C-FIND with filters request to worklist server."""
8587
ae = AE(ae_title="LOCAL_AE_TITLE")
8688
ae.add_requested_context(ModalityWorklistInformationFind)
8789
assoc = ae.associate("0.0.0.0", 4243, ae_title="MWL_SCP_AE_TITLE")

tests/integration/test_send_c_store_to_gateway.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def with_pacs_server(self, tmp_dir):
2222
server.stop()
2323

2424
def test_send_dicom_series_to_gateway(self, tmp_dir):
25+
"""Send DICOM series to gateway."""
2526
number_of_instances = 5
2627
storage = PACSStorage(f"{tmp_dir}/test.db", str(tmp_dir))
2728
send_random_dicom_series(

tests/scripts/test_database.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def worklist_db(tmp_dir, monkeypatch):
2525

2626
# Tests for backup_database
2727
def test_backup_creates_file(tmp_dir):
28+
"""Backup creates file."""
2829
db_path = f"{tmp_dir}/test.db"
2930
sqlite3.connect(db_path).close()
3031

@@ -34,6 +35,7 @@ def test_backup_creates_file(tmp_dir):
3435

3536

3637
def test_backup_returns_timestamped_path(tmp_dir):
38+
"""Backup returns timestamped path."""
3739
db_path = f"{tmp_dir}/test.db"
3840
sqlite3.connect(db_path).close()
3941

@@ -43,6 +45,7 @@ def test_backup_returns_timestamped_path(tmp_dir):
4345

4446

4547
def test_backup_creates_backup_dir_if_missing(tmp_dir):
48+
"""Backup creates backup dir if missing."""
4649
db_path = f"{tmp_dir}/test.db"
4750
sqlite3.connect(db_path).close()
4851
backup_dir = f"{tmp_dir}/backups/nested"
@@ -53,12 +56,14 @@ def test_backup_creates_backup_dir_if_missing(tmp_dir):
5356

5457

5558
def test_backup_database_creates_backup(worklist_db):
59+
"""Backup database creates backup."""
5660
backup_path = backup_database(worklist_db, str(Path(worklist_db).parent / "backups"))
5761
assert Path(backup_path).exists()
5862

5963

6064
# Tests for reset_worklist_database
6165
def test_reset_worklist_database_deletes_all_rows(worklist_db):
66+
"""Reset worklist database deletes all rows."""
6267
reset_worklist_database()
6368

6469
with sqlite3.connect(worklist_db) as conn:
@@ -67,10 +72,12 @@ def test_reset_worklist_database_deletes_all_rows(worklist_db):
6772

6873

6974
def test_reset_worklist_database_returns_row_count(worklist_db):
75+
"""Reset worklist database returns row count."""
7076
assert reset_worklist_database() == 2
7177

7278

7379
def test_reset_worklist_database_returns_zero_when_empty(tmp_dir, monkeypatch):
80+
"""Reset worklist database returns zero when empty."""
7481
db_path = f"{tmp_dir}/worklist.db"
7582
with sqlite3.connect(db_path) as conn:
7683
conn.execute("CREATE TABLE worklist_items (accession_number TEXT PRIMARY KEY)")

0 commit comments

Comments
 (0)