|
| 1 | +--- |
| 2 | +description: Angry-Tests-aligned pytest style, naming, and mandatory debug loop for agents |
| 3 | +alwaysApply: true |
| 4 | +--- |
| 5 | + |
| 6 | +# Test authoring and debugging (Angry Tests) |
| 7 | + |
| 8 | +Philosophy is aligned with Yegor Bugayenko’s [Angry Tests](https://www.yegor256.com/angry-tests.html) and related posts: [On the Layout of Tests](https://www.yegor256.com/2023/01/19/layout-of-tests.html), [single-statement / single-assertion tests](https://www.yegor256.com/2017/05/17/single-statement-unit-tests.html), and [unit testing anti-patterns](https://www.yegor256.com/2018/12/11/unit-testing-anti-patterns.html). Adaptations below are **pytest-specific** for this repo. |
| 9 | + |
| 10 | +Cross-links: proof obligations in [testing.mdc](testing.mdc); Conda commands in [conda-environment.mdc](conda-environment.mdc); general code style in [standards.mdc](standards.mdc). |
| 11 | + |
| 12 | +## Mindset |
| 13 | + |
| 14 | +- Tests are a **safety net**: a test that turns red after a change did its job—it localized a mistake. Fix **production code** or **update the test deliberately** when the contract changed; do not weaken assertions to “make green” without a stated reason. |
| 15 | +- Prefer tests that **pinpoint** the broken unit or scenario so the failure message and test name narrow the search space (layout-of-tests). |
| 16 | + |
| 17 | +## Naming and file placement |
| 18 | + |
| 19 | +- Prefer **`tests/test_<module_basename>.py`** mapping to a single primary **system under test** (SUT), e.g. `test_job_command.py` for `yt_framework.operations.job_command`. |
| 20 | +- For flows that **do not** map 1:1 to one module, use **`tests/integration/`** (or `tests/it/`) and **scenario-oriented** module names (IT-style), still with clear docstrings. |
| 21 | +- Test function names describe **behavior or rule**, not `test1` / `test_foo` (layout-of-tests). |
| 22 | + |
| 23 | +## Assertions (one logical outcome) |
| 24 | + |
| 25 | +- Aim for **one logical outcome per test**: one main `assert`, or one `pytest.raises(...)` block, or one structured equality check. If you need two checks, split the test unless they are inseparable duplicates of the same outcome. |
| 26 | +- Use **`match=`** on `pytest.raises` when the message is part of the contract. |
| 27 | +- Use **`assert expr, "short reason"`** when the default pytest output would be obscure (layout-of-tests: descriptive failures). |
| 28 | + |
| 29 | +## Structure: Arrange / Act / Assert |
| 30 | + |
| 31 | +- Keep tests **short**. Visible three phases: build inputs → call the API → assert. |
| 32 | +- **Shared setup:** Prefer **fake objects** or small factories that live next to production code (or clearly named helpers) over opaque shared fixtures. Avoid “god” fixtures and cross-test coupling ([anti-patterns](https://www.yegor256.com/2018/12/11/unit-testing-anti-patterns.html)). |
| 33 | +- If you add **`tests/support/`**, keep helpers **explicit and minimal**; document what SUT they serve. |
| 34 | + |
| 35 | +## Mocks |
| 36 | + |
| 37 | +- Use **`unittest.mock` / `patch` sparingly**. This codebase is YT/S3-heavy: prefer **real pure logic**, **fakes**, **dev-mode** boundaries, or narrow integration tests over mocking large client surfaces unless there is no cheaper option. |
| 38 | + |
| 39 | +## Mandatory fix loop (agents) |
| 40 | + |
| 41 | +When a test fails or you are debugging tests, follow this loop **in order**; do not skip straight to refactors. |
| 42 | + |
| 43 | +1. **Reproduce:** `conda run -n yt-framework -- pytest <path>::<test_name> -xvs` (or `-k` with a unique substring). Confirm the failure is stable. |
| 44 | +2. **Read:** Study the **assertion output**, **exception message**, and **traceback top**—identify the failing line in test and production code. |
| 45 | +3. **Locate SUT:** Open the production module that owns the behavior; confirm whether the test expectation or the code is wrong. |
| 46 | +4. **Hypothesis:** State one sentence: e.g. “Off-by-one in path normalization” or “Test encodes old API.” |
| 47 | +5. **Minimal change:** Apply the **smallest** edit that addresses that hypothesis only (standards: root-cause, no drive-by rewrites). |
| 48 | +6. **Re-run:** Same single test, then full `conda run -n yt-framework -- pytest`. |
| 49 | +7. **If still red:** Do **not** stack unrelated edits. Return to step 2 with fresh output. If the failure is environmental (Conda, missing env), fix the environment or document `--no-verify` only as an emergency (see CONTRIBUTING). |
| 50 | + |
| 51 | +Proof of done: cite pytest output per [testing.mdc](testing.mdc). |
| 52 | + |
| 53 | +## Project conventions |
| 54 | + |
| 55 | +- Run pytest through **`conda run -n yt-framework --`** when verifying locally ([conda-environment.mdc](conda-environment.mdc)). |
| 56 | +- Test code: type hints and Black like production ([standards.mdc](standards.mdc)). |
| 57 | +- For breadth of coverage and repo status, see [.cursor/artifacts/project-details/testing-readiness-report.md](../artifacts/project-details/testing-readiness-report.md). |
0 commit comments