gendesign/tradein-mvp/backend/tests/support/README.md
bot-backend 27bbd579c3
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 47s
chore(tradein/scrapers): удалить весь legacy scrapers/ каталог — final E (#2397, #2277)
Удаляет весь `app/services/scrapers/` (16 файлов, ~7100 строк) — Part D (D1-D5)
убрал всех внешних вызывающих, 0 runtime importers подтверждено grep'ом на main.

Заодно:
- удалены 7 осиротевших локальных probe/sweep-скриптов (tradein-mvp/scripts/),
  импортировавших уже-удалённые или удаляемые сейчас legacy-модули
- тест-хирургия по 65+ файлам: DELETE прямых legacy-юнит-тестов, RETARGET
  тестов, тестирующих ещё живую бизнес-логику (переключены на scraper_kit.*
  эквиваленты, включая quality-gate #781/#753/#754/#755/#773/#740), partial-delete
  golden-parity тестов, потерявших legacy-оракл
- kit save_listings/AvitoScraper/etc. требуют инжектируемые matcher/config —
  ретаргетированные тесты обновлены под новую сигнатуру (RealScraperConfig(),
  MagicMock HouseMatcher, region_code=66)

Полный pytest suite зелёный (2255 passed, 6 skipped) кроме известного флейка
#2208 (test_search_cache_hit, не связан со scrapers).
2026-07-04 15:58:15 +03:00

88 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# tests/support/ — общие test-инструменты (не сами тесты)
## parity.py — legacy → scraper_kit parity harness (issue #2304)
Инструмент для issues #2305-#2310 (миграция неймигрированных importers
`app/services/scrapers/*``scraper_kit` эквиваленты, см. audit
`Scraper_Kit_Legacy_Dependency_Audit_0703` в vault). Каждая такая миграция
должна была доказать, что kit-путь даёт ТОТ ЖЕ результат, что и legacy-путь на
одном и том же входе — для этого использовался `assert_parity`.
Легаси `app/services/scrapers/` каталог полностью удалён (#2397 финальный шаг
E, миграция завершена) — все golden-parity/legacy-vs-kit тесты, построенные на
`assert_parity`, удалены вместе с ним (kit — единственный живой путь). Harness
(`parity.py`) остаётся в дереве на случай будущих strangler-миграций
(например legacy-кода за пределами `app/services/scrapers/`).
### Быстрый старт (иллюстративный пример; `legacy_fn` — гипотетическая функция
### из будущего strangler-миграции модуля, не из уже удалённого `app/services/scrapers/`)
```python
from some_legacy_module import evaluate_via_cian as legacy_fn
from scraper_kit.providers.cian.valuation import evaluate_via_cian as kit_fn
from tests.support.parity import assert_parity
def test_cian_valuation_parity() -> None:
assert_parity(
legacy_fn=legacy_fn,
kit_fn=kit_fn,
fixtures=[
(fixture_html_1, "https://cian.ru/flat/1"),
(fixture_html_2, "https://cian.ru/flat/2"),
],
ignore_fields={"latency_ms", "fetched_at"}, # недетерминированные поля
tolerance=1e-6, # допуск для float-полей (напр. рассчитанные оценки)
)
```
### Как формировать `fixtures`
Каждый элемент списка — один тестовый вход:
- `tuple`/`list` → распаковывается как позиционные аргументы: `legacy_fn(*fixture)`;
- любое другое значение (str, dict, ...) → передаётся как единственный
позиционный аргумент: `legacy_fn(fixture)`.
Начните с 1-2 hardcoded HTML-фикстур/dict'ов (git-история
`tests/scrapers/test_avito_detail_kit_parity.py`, удалён вместе с legacy
`app/services/scrapers/` в #2397, содержит референсный пример).
DB-фикстуры НЕ нужны для чистых parse/compute-функций — используйте их
только если сама legacy/kit-функция реально требует Session.
### Почему нельзя просто `==`
legacy- и kit-версии одного и того же dataclass (напр. `DetailEnrichment`,
`CianValuationResult`) — это РАЗНЫЕ Python-классы (живут в разных модулях),
даже если поля идентичны. Дефолтный `dataclass.__eq__` сначала проверяет
`other.__class__ is self.__class__` — для двух разных классов это всегда
`False`, ДАЖЕ когда все значения полей совпадают. `assert_parity` / `compare_outputs`
нормализуют оба вывода в dict/list/scalar (через `dataclasses.fields()` /
`.model_dump()` рекурсивно) и сравнивают СТРУКТУРНО, по именам полей — эта
проблема класс-идентичности не мешает.
### ignore_fields vs tolerance
- `ignore_fields={"latency_ms", "fetched_at", ...}` — поле целиком исключается
из сравнения на ЛЮБОМ уровне вложенности. Используйте для полей, у которых
даже приблизительное совпадение не гарантировано (timestamps, request-id).
- `tolerance=1e-6` — числовой (`int`/`float`, НЕ `bool`) допуск через
`math.isclose(rel_tol=tolerance, abs_tol=tolerance)`. Используйте для
float-полей, где legacy/kit могут давать чуть разное значение из-за
порядка операций с плавающей точкой (не для timestamps/datetime — там
используйте `ignore_fields`).
### При мисматче
`assert_parity` кидает `ParityMismatchError` (подкласс `AssertionError`) со
списком ВСЕХ различающихся полей: путь до поля + значение legacy + значение
kit. Не просто "not equal" — сразу видно, что чинить.
### Не входит в scope harness'а
- Он НЕ загружает DB-фикстуры сам — если legacy/kit функция требует
`Session`, передавайте mock/session в fixture-tuple как обычно.
Live network/DB в parity-тестах избегайте — они должны быть
detereministic offline unit-тестами.
- Он НЕ мигрирует сами importers — это делает каждый sub-issue #2305-#2310
отдельно (тесты для конкретной пары legacy/kit функций пишет тот sub-issue).