Skip to content

01 — Overview

What this suite tests

eufy_vacuum is a Home Assistant custom integration built on an adapter pattern: brand-specific entity wiring lives in adapters/, and a large brand-agnostic core (the manager, the job pipeline, learning, mapping, battery, rooms, themes, setup) sits on top. The test suite targets that core plus the HA integration seams — config flow, services, platforms, listeners.

The goal is regression safety for refactors. The core is being split into subsystem packages over time (see docs/dev/), and the suite exists so those moves can happen without silently breaking behavior.

The three layers

Layer Directory Needs hass? What it covers
Unit tests/unit/ No (mostly) Pure functions and isolated class methods — timestamp parsing, battery metrics math, learning estimator/finalizer helpers, room-field resolution. Fast, no I/O.
Integration tests/integration/ Yes Anything that touches the manager, the HA service registry, platforms, or the persistent store. Uses the in-memory hass from pytest-homeassistant-custom-component.
Adapter tests/adapters/ No Brand-specific pure logic, now spanning two brands — Eufy (model catalog, discovery, segmentor, room attribution) and Roborock (roborock/test_adapter.py) — plus the brand-agnostic conformance harness. Kept on its own path; counted in the coverage number — we always test the adapters we ship.

Rule of thumb: if the thing under test is a pure function, write a unit test — it is faster, has no shared-state pitfalls, and reads more clearly. Reach for an integration test only when you need the manager, a real service call, or the store.

Directory layout

tests/
  conftest.py                 # root fixtures: hass enablement, config entries
  __init__.py
  unit/                       # pure-function tests
    test_timestamp_utils.py
    test_battery_metrics.py
    test_learning_estimator.py
    test_learning_job_finalizer.py
    ...
  integration/
    conftest.py               # manager + manager_with_services + seeding helpers
    test_config_flow.py
    test_manager_setup.py
    test_services_rooms.py
    test_learning_services.py
    test_button_entity.py
    test_listeners_*.py
    test_setup_*.py
    test_themes_*.py
    ...
  adapters/
    conftest.py               # adapter-suite fixtures
    test_adapter_contract.py  # brand-agnostic conformance harness
    eufy/                     # concrete-adapter tests (counted in coverage %)
      test_model_catalog.py
      test_discovery.py
      test_lifecycle.py
      test_buttons_entities.py
      test_segmentor.py
      test_segmentor_splitters.py
      test_job_segmenter_config.py
      test_room_attribution.py
    roborock/                 # second concrete adapter (S6-class)
      test_adapter.py

Toolchain

Declared in requirements_test.txt:

Package Why
pytest>=9.0 runner
pytest-asyncio>=1.3 async tests; asyncio_mode = auto means async def test_* just works, no decorator
pytest-cov coverage + branch coverage
pytest-homeassistant-custom-component>=0.13.332 the in-memory HA harness — provides the hass and enable_custom_integrations fixtures

Config lives in two files:

  • pytest.iniasyncio_mode = auto, testpaths = tests/unit tests/integration, and the coverage addopts (term-missing + branch). A default run does not write HTML; the canonical htmlcov/ is produced only by an explicit --cov-report=html:htmlcov.
  • .coveragerc — coverage source is custom_components/eufy_vacuum (no omit — the eufy adapter is counted too); exclude_lines drops TYPE_CHECKING, __repr__, NotImplementedError, pragma: no cover, bare ... stub bodies (^\s*\.\.\.\s*$), and @overload-decorated signatures (@(typing\.)?overload).

Note that testpaths deliberately excludes tests/adapters — run that path explicitly when you want it (see 02).

Coverage status

Coverage: 95.2% statement (93% combined with --cov-branch, which the default addopts enables), across the brand-agnostic core and the Eufy adapter — the adapter is now counted in the number (see subsystems/15-adapters). Most subsystems sit in the 92–98% band; the most visible thin spot is the CV segmentor (91%). The per-subsystem breakdown lives in subsystems/README.

Coverage is a guide, not a target — the suite favors precision (each test maps to a named behavior, see 04) over chasing the last few percent. The remaining misses are, by design, one of:

  • # pragma: no cover — pure log-only / best-effort except blocks (I/O writes, listener teardown). Never used where the failure path escapes into a returned/persisted/user-visible value.
  • Honest misses (not tested, not pragma'd) — defensive continue / return [] guards (real control flow) and async_setup_entry boot wiring that only runs under a full integration boot.

See subsystems/README for the full convention. 95% is not pursued: reaching it on this codebase would mean either pragma-bombing real control flow or writing tests that exist only to move a number.