Nexent traditional Python unit tests
Paths below are relative to the repository root.
These tests are a permanently maintained suite kept separate from the requirement-driven D1-D5 system. Historical documents may call it Legacy UT; that does not mean planned removal. Do not assign formal D1-D5 Case IDs to UT, bind it under test-e2e/cases/, or count it in the generated functional baseline. Use nexent-test-assets for formal Cases.
Isolated validation, arithmetic, local branches and error conversion belong primarily here, including when adding tests after code is already developed. Read existing inputs/assertions before adding another test; reuse adequate coverage or strengthen missing variants in its owning suite. Do not copy/wrap UT as formal D1 solely for a Feature link, Case count or report entry. New formal D1 requires distinct component-contract evidence with real internal collaboration through nexent-test-assets; an API/runtime boundary may legitimately verify the same rule again. Preserve existing D1 rather than bulk deleting or relabeling overlaps. For formal test backfill, use nexent-test-assets' test-backfill mode; UT results remain separate.
For every product-code change, assess applicable unit behavior and document reuse, strengthen, add or a justified non-behavior exemption. New or changed behavior requires success, boundary and error coverage. Bugfixes require a regression assertion that detects the defect; demonstrate failure before the fix where feasible. Existing adequate coverage may be reused without artificial file edits. Derive expected behavior from requirements/contracts, not merely the implementation. Relevant local UT verification is mandatory before a product-code PR; CI independently runs its configured UT set. Failures, collection errors and unavailable execution are not passes. UT does not replace required component/API/runtime/journey/risk acceptance.
- Identify the unit and behavior. Inspect neighboring tests,
test/conftest.py, and test/pytest.ini before changing fixture/import setup.
- Use pytest exclusively, pytest assertions, fixtures, and
pytest-mock. Files/functions start with test_; test classes start with Test. Keep files below 500 lines or split by feature using test_<module>_<feature>.py; split package directories include __init__.py.
- Import the unit and necessary test helpers. Mock collaborators rather than exercising external interfaces/clients/services. Patch the fully qualified lookup site determined from actual imports, not the dependency's definition module.
For example, if backend.services.example_service imports fetch using from provider import fetch, patch backend.services.example_service.fetch. If the runtime loads the module as services.example_service, use that actual path. Do not copy an unrelated service path from an example.
- Isolate external I/O and APIs. Reset mutable state with fixtures; use
autouse=True when every test in a scope requires it. Do not mock away the behavior being asserted.
- Cover changed success/error flows and boundaries with specific observable assertions. Use
side_effect for collaborator errors, @pytest.mark.parametrize for variants, and @pytest.mark.asyncio for async tests. Async collaborators need awaitable mocks.
- Order imports as standard library, third-party, then project; write comments/docstrings in English.
- Run narrow tests from the root with a working backend environment, such as
pytest test/backend/apps/test_agent_app.py -v. Broaden when needed; python test/run_all_test.py is the existing broad runner.
Report commands, selected tests, results, reuse rationale and environment blockers in the change's verification record, separately from formal Case IDs. Do not recreate the complete historical suite for each change. Python coverage uses backend/sdk; frontend coverage remains separate. Do not impose a new threshold or edit CI from this skill without authorization. Unit-test isolation does not replace live-service functional checks or real-model acceptance.