Загрузка данных
сцен с 8 степ
with allure.step("Этап 8. Извлечение строк данных из отчёта"):
report_state.actual_data_rows = rejection_report_utils.iter_rejection_report_rows(
report_state.actual_worksheet
)
report_state.actual_monitored_tag_rows = rejection_report_utils.filter_rows_by_monitored_tags(
report_state.actual_data_rows,
RejectionSensorTag,
)
allure.attach(
rejection_report_utils.format_rejection_rows_for_allure(report_state.actual_monitored_tag_rows),
name="Строки отчёта по тегам RejectionSensorTag",
attachment_type=allure.attachment_type.TEXT,
)
with allure.step("Подготовка данных шапки xlsx для проверки"):
title_info = report_state.actual_title_info
report_state.actual_header_column_headers = report_utils.get_report_column_headers(
report_state.actual_worksheet,
headers_row=RejectedReportConst.REPORT_COLUMN_HEADERS_ROW,
)
report_state.actual_header_period_start = title_info.period_start
report_state.actual_header_period_end = title_info.period_end
report_state.actual_header_contains_expected_title = (
rejection_report_utils.report_header_contains_expected_title(title_info.raw_title)
)
with allure.step("Подготовка данных для проверки строк отчёта по RejectionTestCase"):
report_state.actual_case_checks = rejection_report_utils.prepare_rejection_report_case_checks(
report_state.actual_monitored_tag_rows,
cfg.rejection_cases,
imitator_start_time,
)
with allure.step("Проверка первой строки шапки xlsx-отчёта"):
StepCheck("Лист xlsx открыт", "worksheet").actual(report_state.actual_worksheet).is_not_none()
with SoftAssertions() as soft_failures:
StepCheck(
"Первая строка шапки содержит заголовок отчёта об отбракованных входных данных",
"report_title",
soft_failures,
).actual(report_state.actual_header_contains_expected_title).expected(True).equal_to()
StepCheck(
"Время начала периода в первой строке шапки совпадает с фильтром запроса (+-1 мин)",
"period_start",
soft_failures,
).actual(report_state.actual_header_period_start).is_between(period_start_lo, period_start_hi)
StepCheck(
"Время конца периода в первой строке шапки совпадает с фильтром запроса (+-1 мин)",
"period_end",
soft_failures,
).actual(report_state.actual_header_period_end).is_between(period_end_lo, period_end_hi)
StepCheck(
"Названия колонок во второй строке шапки отчёта",
"column_headers",
soft_failures,
).actual(
report_state.actual_header_column_headers
).expected(RejectedReportConst.EXPECTED_COLUMN_HEADERS).equal_to()
with allure.step("Проверка строк отчёта по каждому RejectionTestCase из конфигурации набора"):
with SoftAssertions() as soft_failures:
for case_check in report_state.actual_case_checks:
StepCheck(
f"В отчёте найдена отбраковка для {case_check.case_label} в интервале времени "
f"{case_check.window_start} - {case_check.window_end}",
RejectedReportConst.COL_TAG,
soft_failures,
).actual(case_check.row_found).is_true_with_details(
expected_text=(
f"найдена строка с тегом {case_check.tag_description} "
f"и событием '{case_check.report_event}'"
),
actual_text=case_check.found_row_summary,
)
if not case_check.row_found:
continue
StepCheck(
f"Для {case_check.case_label} время получения отбраковки в допустимом диапазоне",
RejectedReportConst.COL_DATETIME,
soft_failures,
).actual(case_check.datetime_in_window).is_true_with_details(
expected_text=(
f"дата и время в диапазоне {case_check.window_start} — {case_check.window_end}"
),
actual_text=case_check.datetime_actual_text,
)
StepCheck(
f"Для {case_check.case_label} суммарная продолжительность отбраковки "
f"({case_check.expected_duration_text}) совпадает",
RejectedReportConst.COL_DURATION,
soft_failures,
).actual(case_check.actual_duration_seconds).expected(
case_check.expected_duration_seconds
).equal_to()
StepCheck(
f"Для {case_check.case_label} участок трубопровода в колонке "
f"'{RejectedReportConst.COL_OBJECT}' не пустой",
RejectedReportConst.COL_OBJECT,
soft_failures,
).actual(case_check.pipe_section).is_not_empty()
StepCheck(
f"Для {case_check.case_label} после последней точки в колонке "
f"'{RejectedReportConst.COL_OBJECT}' указан сигнал '{case_check.expected_signal_suffix}'",
RejectedReportConst.COL_OBJECT,
soft_failures,
).actual(case_check.actual_signal_suffix).expected(
case_check.expected_signal_suffix
).equal_to()
except Exception:
with allure.step("Прикрепление xlsx отчёта к Allure при падении теста"):
report utils xslsx
@dataclass
class RejectionReportCaseCheck:
"""Подготовленные данные для проверки одного RejectionTestCase в xlsx-отчёте."""
case_label: str
tag_description: str
report_event: str
window_start: datetime
window_end: datetime
row_found: bool
found_row_summary: str
datetime_in_window: bool = False
datetime_actual_text: str = "(пусто)"
actual_duration_seconds: int = 0
expected_duration_seconds: int = 0
expected_duration_text: str = ""
pipe_section: str = ""
actual_signal_suffix: str = ""
expected_signal_suffix: str = ""
def prepare_rejection_report_case_checks(
monitored_rows: Iterable[RejectionReportRow],
rejection_cases: Iterable[RejectionTestCase],
imitator_start_time: datetime,
) -> List[RejectionReportCaseCheck]:
"""Собирает все вычисленные значения для проверки строк отчёта по кейсам набора."""
case_checks: List[RejectionReportCaseCheck] = []
for rejection_case in rejection_cases:
report_event = expected_event_to_report_event(rejection_case.expected_event)
window_start, window_end = get_case_time_window(imitator_start_time, rejection_case)
case_label = f"события '{report_event}' - {rejection_case.sensor.description}"
expected_signal_suffix = report_signal_suffix_by_expected_name(rejection_case.expected_signal_name)
raw_case_rows = filter_rows_for_rejection_case(monitored_rows, rejection_case, imitator_start_time)
merged_case_rows = merge_rejection_rows(raw_case_rows)
primary_row = select_primary_merged_row(merged_case_rows)
if primary_row is None:
case_checks.append(
RejectionReportCaseCheck(
case_label=case_label,
tag_description=rejection_case.sensor.description,
report_event=report_event,
window_start=window_start,
window_end=window_end,
row_found=False,
found_row_summary="строка не найдена",
expected_signal_suffix=expected_signal_suffix,
)
)
continue
merge_key = build_merge_key(primary_row)
expected_duration_seconds = sum_duration_for_merge_key(raw_case_rows, merge_key)
pipe_section, actual_signal_suffix = split_object_column(primary_row.object_value)
datetime_in_window = is_datetime_within_closed_interval(
primary_row.datetime_value,
window_start,
window_end,
)
case_checks.append(
RejectionReportCaseCheck(
case_label=case_label,
tag_description=rejection_case.sensor.description,
report_event=report_event,
window_start=window_start,
window_end=window_end,
row_found=True,
found_row_summary=(
f"{primary_row.tag_value} | {primary_row.event_value} | {primary_row.datetime_value}"
),
datetime_in_window=datetime_in_window,
datetime_actual_text=str(primary_row.datetime_value) if primary_row.datetime_value else "(пусто)",
actual_duration_seconds=primary_row.duration_seconds,
expected_duration_seconds=expected_duration_seconds,
expected_duration_text=format_duration_seconds(expected_duration_seconds),
pipe_section=pipe_section,
actual_signal_suffix=actual_signal_suffix,
expected_signal_suffix=expected_signal_suffix,
)
)
return case_checks
def expected_event_to_report_event(expected_event: str) -> str:
модели тестов
actual_monitored_tag_rows: list[RejectionReportRow] = field(default_factory=list)
actual_header_column_headers: list[str] = field(default_factory=list)
actual_header_period_start: Optional[datetime] = None
actual_header_period_end: Optional[datetime] = None
actual_header_contains_expected_title: bool = False
actual_case_checks: list = field(default_factory=list)