简化 Autocal 界面测试并减少等待开销

用轻量曲面和独立页面验证普通交互,收拢窗口清理,并让后台测试任务获得执行时间。保留原有 90 项用例,113 项相关测试及代码检查通过。
This commit is contained in:
ykcheungab
2026-10-15 21:17:28 +08:00
parent a3aeca1d12
commit 31f6ef3881
4 changed files with 609 additions and 715 deletions
+4 -2
View File
@@ -2,11 +2,13 @@
import time import time
from PySide6.QtTest import QTest from PySide6.QtCore import QCoreApplication
def wait_review(widget): def wait_review(widget):
deadline = time.monotonic() + 10 deadline = time.monotonic() + 10
while widget._job is not None and time.monotonic() < deadline: while widget._job is not None and time.monotonic() < deadline:
QTest.qWait(10) QCoreApplication.processEvents()
# Release the GIL so the Python recording worker can finish its I/O.
time.sleep(.001)
assert widget._job is None, "background review did not finish" assert widget._job is None, "background review did not finish"
File diff suppressed because it is too large Load Diff
+28 -14
View File
@@ -16,6 +16,22 @@ from orisys_autocal.record_overview import load_overview, overview_rows, read_pl
from orisys_autocal.session_review import review_session from orisys_autocal.session_review import review_session
@pytest.fixture
def overview_dialog(qt_app):
"""Keep chart ownership local to the tests that exercise the overview UI."""
from orisys_autocal.ui.record_overview_dialog import RecordOverviewDialog
dialogs = []
def create(directory, **kwargs):
dialog = RecordOverviewDialog(directory, **kwargs)
dialogs.append(dialog)
return dialog
yield create
for dialog in reversed(dialogs):
dialog.close()
dialog.deleteLater()
@pytest.fixture @pytest.fixture
def overview_session(repeated_session): def overview_session(repeated_session):
root, selections = repeated_session root, selections = repeated_session
@@ -105,14 +121,13 @@ def test_overview_requires_all_planned_hold_windows_for_reviewed_status(overview
assert overview_rows(review)[0]["status"] == "reviewed" assert overview_rows(review)[0]["status"] == "reviewed"
def test_overview_gui_plan_click_trial_filter_and_saved_roundtrip(qt_app, overview_session, monkeypatch, tmp_path): def test_overview_gui_plan_click_trial_filter_and_saved_roundtrip(qt_app, overview_session, monkeypatch, tmp_path, overview_dialog):
from orisys_autocal.ui import record_overview_dialog from orisys_autocal.ui import record_overview_dialog
from orisys_autocal.ui.session_review_dialog import SessionReviewDialog from orisys_autocal.ui.session_review_dialog import SessionReviewDialog
root, selections, plan_path = overview_session root, selections, plan_path = overview_session
dialog = record_overview_dialog.RecordOverviewDialog(root) dialog = overview_dialog(
dialog.review = review_session(root, selections=selections) root, review=review_session(root, selections=selections))
dialog._refresh()
monkeypatch.setattr(record_overview_dialog.QFileDialog, "getOpenFileName", lambda *args: (str(plan_path), "")) monkeypatch.setattr(record_overview_dialog.QFileDialog, "getOpenFileName", lambda *args: (str(plan_path), ""))
dialog.findChild(QAction, "overview_import_plan").trigger() dialog.findChild(QAction, "overview_import_plan").trigger()
assert dialog.table.rowCount() == 4 assert dialog.table.rowCount() == 4
@@ -165,7 +180,6 @@ def test_overview_gui_plan_click_trial_filter_and_saved_roundtrip(qt_app, overvi
dialog.findChild(QAction, "overview_load").trigger() dialog.findChild(QAction, "overview_load").trigger()
wait_review(dialog) wait_review(dialog)
assert dialog.table.rowCount() == 4 assert dialog.table.rowCount() == 4
dialog.close()
def test_calibration_review_action_opens_records_tab(make_window, overview_session, monkeypatch): def test_calibration_review_action_opens_records_tab(make_window, overview_session, monkeypatch):
@@ -184,16 +198,17 @@ def test_calibration_review_action_opens_records_tab(make_window, overview_sessi
window.close() window.close()
def test_records_tab_browse_error_and_reload(make_window, overview_session, monkeypatch, qt_app, tmp_path): def test_records_tab_browse_error_and_reload(overview_session, monkeypatch, qt_app, tmp_path):
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
from PySide6.QtTest import QTest from PySide6.QtTest import QTest
from orisys_autocal.ui.record_overview_dialog import QFileDialog from PySide6.QtWidgets import QVBoxLayout, QWidget
from orisys_autocal.ui.record_overview_dialog import QFileDialog, RecordOverviewPage
root, _, _ = overview_session root, _, _ = overview_session
window = make_window() window = QWidget()
window.resize(1600, 1000) window.resize(1600, 1000)
window.workspace.set_current_key("records") page = RecordOverviewPage(window)
page = window.workspace.page("records") QVBoxLayout(window).addWidget(page)
assert page.overview is None and page.path_label.text() assert page.overview is None and page.path_label.text()
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args: str(root)) monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args: str(root))
page.open_button.click() page.open_button.click()
@@ -217,10 +232,10 @@ def test_records_tab_browse_error_and_reload(make_window, overview_session, monk
assert page.overview.save_button.isVisible() assert page.overview.save_button.isVisible()
assert window.grab().save(str(tmp_path / "records-tab-small.png")) assert window.grab().save(str(tmp_path / "records-tab-small.png"))
window.close() window.close()
window.deleteLater()
def test_operator_outline_identity_incomplete_inspection_and_equal_scale(qt_app, overview_session, tmp_path): def test_operator_outline_identity_incomplete_inspection_and_equal_scale(qt_app, overview_session, tmp_path, overview_dialog):
from orisys_autocal.ui.record_overview_dialog import RecordOverviewDialog
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
root, _, _ = overview_session root, _, _ = overview_session
@@ -232,7 +247,7 @@ def test_operator_outline_identity_incomplete_inspection_and_equal_scale(qt_app,
} }
manifest["trials"][0].update(status="incomplete", reason="operator stopped") manifest["trials"][0].update(status="incomplete", reason="operator stopped")
write_json(root / "session.json", manifest) write_json(root / "session.json", manifest)
dialog = RecordOverviewDialog(root) dialog = overview_dialog(root)
assert "F-TEST" in dialog.identity.text() and "finger" in dialog.identity.text() assert "F-TEST" in dialog.identity.text() and "finger" in dialog.identity.text()
assert dialog.outline_state == "snapshot" assert dialog.outline_state == "snapshot"
assert dialog.trials.currentData() == "trial-001" assert dialog.trials.currentData() == "trial-001"
@@ -250,4 +265,3 @@ def test_operator_outline_identity_incomplete_inspection_and_equal_scale(qt_app,
assert (x.max() - x.min()) / area.width() == pytest.approx((y.max() - y.min()) / area.height(), rel=.02) assert (x.max() - x.min()) / area.width() == pytest.approx((y.max() - y.min()) / area.height(), rel=.02)
assert len(dialog.uv_chart.series()) == 5 assert len(dialog.uv_chart.series()) == 5
assert dialog.grab().save(str(tmp_path / "operator-incomplete.png")) assert dialog.grab().save(str(tmp_path / "operator-incomplete.png"))
dialog.close()
+23 -16
View File
@@ -19,6 +19,22 @@ def _csv(path, rows):
writer.writerows(rows) writer.writerows(rows)
@pytest.fixture
def review_dialog(qt_app):
"""Give each test fresh dialogs and release their chart trees afterwards."""
from orisys_autocal.ui.session_review_dialog import SessionReviewDialog
dialogs = []
def create(directory, **kwargs):
dialog = SessionReviewDialog(directory, **kwargs)
dialogs.append(dialog)
return dialog
yield create
for dialog in reversed(dialogs):
dialog.close()
dialog.deleteLater()
@pytest.fixture @pytest.fixture
def session(tmp_path): def session(tmp_path):
manifest = { manifest = {
@@ -106,10 +122,8 @@ def test_review_refuses_mismatched_identity_and_paths_outside_session(session):
review_session(session) review_session(session)
def test_review_dialog_shows_saved_windows_charts_and_new_export(qt_app, session, tmp_path): def test_review_dialog_shows_saved_windows_charts_and_new_export(qt_app, session, tmp_path, review_dialog):
from orisys_autocal.ui.session_review_dialog import SessionReviewDialog dialog = review_dialog(session)
dialog = SessionReviewDialog(session)
assert dialog.sites.count() == 2 assert dialog.sites.count() == 2
assert dialog.table.rowCount() == 5 assert dialog.table.rowCount() == 5
assert dialog.pages.currentIndex() == 0 and dialog.pages.count() == 2 assert dialog.pages.currentIndex() == 0 and dialog.pages.count() == 2
@@ -127,7 +141,6 @@ def test_review_dialog_shows_saved_windows_charts_and_new_export(qt_app, session
dialog.show() dialog.show()
qt_app.processEvents() qt_app.processEvents()
assert dialog.grab().save(str(tmp_path / "review.png")) assert dialog.grab().save(str(tmp_path / "review.png"))
dialog.close()
def test_reviewed_windows_roundtrip_and_reject_changed_inputs(session): def test_reviewed_windows_roundtrip_and_reject_changed_inputs(session):
@@ -168,10 +181,10 @@ def test_invalid_review_selection_does_not_become_verified(session):
}}) }})
def test_operator_confirms_windows_in_dialog_and_reloads_them(qt_app, session, monkeypatch): def test_operator_confirms_windows_in_dialog_and_reloads_them(session, monkeypatch, review_dialog):
from orisys_autocal.ui import session_review_dialog from orisys_autocal.ui import session_review_dialog
dialog = session_review_dialog.SessionReviewDialog(session) dialog = review_dialog(session)
for row in (0, 1): for row in (0, 1):
dialog.table.selectRow(row) dialog.table.selectRow(row)
dialog.end_s.setValue(2) dialog.end_s.setValue(2)
@@ -182,14 +195,12 @@ def test_operator_confirms_windows_in_dialog_and_reloads_them(qt_app, session, m
dialog.save_button.click() dialog.save_button.click()
wait_review(dialog) wait_review(dialog)
saved = next(session.glob("derived/*/review.json")) saved = next(session.glob("derived/*/review.json"))
fresh = session_review_dialog.SessionReviewDialog(session) fresh = review_dialog(session)
monkeypatch.setattr(session_review_dialog.QFileDialog, "getOpenFileName", monkeypatch.setattr(session_review_dialog.QFileDialog, "getOpenFileName",
lambda *args: (str(saved), "")) lambda *args: (str(saved), ""))
fresh.load_button.click() fresh.load_button.click()
wait_review(fresh) wait_review(fresh)
assert fresh.review["sites"] == dialog.review["sites"] assert fresh.review["sites"] == dialog.review["sites"]
dialog.close()
fresh.close()
def test_baseline_image_matches_first_reviewed_zero_frame(session): def test_baseline_image_matches_first_reviewed_zero_frame(session):
@@ -301,15 +312,12 @@ def test_quality_ignores_unconfirmed_data_and_unmatched_loads(repeated_session):
assert quality["hysteresis"] == [] assert quality["hysteresis"] == []
def test_quality_dialog_is_reachable_and_shows_validation_tables(qt_app, repeated_session, monkeypatch, tmp_path): def test_quality_dialog_is_reachable_and_shows_validation_tables(qt_app, repeated_session, monkeypatch, tmp_path, review_dialog):
from PySide6.QtWidgets import QTableWidget from PySide6.QtWidgets import QTableWidget
from orisys_autocal.ui.session_review_dialog import SessionReviewDialog
from orisys_autocal.ui.quality_review_dialog import QualityReviewDialog from orisys_autocal.ui.quality_review_dialog import QualityReviewDialog
directory, selections = repeated_session directory, selections = repeated_session
review = SessionReviewDialog(directory) review = review_dialog(directory, review=review_session(directory, selections=selections))
review.review = review_session(directory, selections=selections)
review._show_site()
opened = [] opened = []
monkeypatch.setattr(QualityReviewDialog, "exec", lambda dialog: opened.append(dialog)) monkeypatch.setattr(QualityReviewDialog, "exec", lambda dialog: opened.append(dialog))
review.quality_button.click() review.quality_button.click()
@@ -328,4 +336,3 @@ def test_quality_dialog_is_reachable_and_shows_validation_tables(qt_app, repeate
qt_app.processEvents() qt_app.processEvents()
assert dialog.grab().save(str(tmp_path / "validation.png")) assert dialog.grab().save(str(tmp_path / "validation.png"))
dialog.close() dialog.close()
review.close()