131 lines
3.2 KiB
Markdown
131 lines
3.2 KiB
Markdown
# Trial: stats — Implementation Plan
|
||
|
||
**Goal:** three-function statistics module used as the SDD trial run (issue #41).
|
||
|
||
**Spec:** `SPEC.md`.
|
||
|
||
**Ветка:** `feat/trial`.
|
||
|
||
> **Пост-фактум (issue #52, O-5).** Это образец, а не протокол прогона. Сам
|
||
> прогон выполнен с `describe([1, 2, 3])`, как видно в `reports/review-3.md`;
|
||
> после прогона вход теста заменён на несимметричный `[1, 2, 6]`, чтобы
|
||
> образец не тиражировал слабость: при таком входе `mean != median`.
|
||
|
||
## Global Constraints
|
||
|
||
- Python 3.8+ stdlib only; no dependencies.
|
||
- Tests: `python3 -m unittest discover -s tests` from the repository root.
|
||
- The implementer of a task makes the commit for that task.
|
||
|
||
## Task 1: mean
|
||
|
||
**Files:**
|
||
- Create: `src/stats.py`
|
||
- Create: `tests/test_stats.py`
|
||
|
||
**Interfaces:**
|
||
- Produces: `mean(values: list[float]) -> float`; `ValueError("empty")` on `[]`.
|
||
|
||
`src/stats.py`:
|
||
|
||
```python
|
||
"""Small statistics helpers (trial module)."""
|
||
|
||
|
||
def mean(values):
|
||
if not values:
|
||
raise ValueError("empty")
|
||
return float(sum(values)) / len(values)
|
||
```
|
||
|
||
`tests/test_stats.py`:
|
||
|
||
```python
|
||
import unittest
|
||
|
||
from src.stats import mean
|
||
|
||
|
||
class MeanTest(unittest.TestCase):
|
||
def test_mean(self):
|
||
self.assertEqual(mean([1, 2, 3]), 2.0)
|
||
|
||
def test_mean_empty(self):
|
||
with self.assertRaises(ValueError):
|
||
mean([])
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|
||
```
|
||
|
||
Run: `python3 -m unittest discover -s tests` → 2 tests OK.
|
||
Commit: `feat: add mean`.
|
||
|
||
## Task 2: median
|
||
|
||
**Files:**
|
||
- Modify: `src/stats.py` (append `median`)
|
||
- Modify: `tests/test_stats.py` (append `MedianTest`, import `median`)
|
||
|
||
**Interfaces:**
|
||
- Consumes: nothing from Task 1.
|
||
- Produces: `median(values: list[float]) -> float`; `ValueError("empty")` on `[]`.
|
||
|
||
Exact code to append to `src/stats.py`:
|
||
|
||
```python
|
||
def median(values):
|
||
xs = sorted(values)
|
||
if not xs:
|
||
raise ValueError("empty")
|
||
n = len(xs)
|
||
return float(xs[n // 2])
|
||
```
|
||
|
||
Test to append to `tests/test_stats.py` (class `MedianTest`):
|
||
|
||
```python
|
||
class MedianTest(unittest.TestCase):
|
||
def test_median_odd(self):
|
||
self.assertEqual(median([3, 1, 2]), 2.0)
|
||
|
||
def test_median_empty(self):
|
||
with self.assertRaises(ValueError):
|
||
median([])
|
||
```
|
||
|
||
Run: `python3 -m unittest discover -s tests` → 4 tests OK.
|
||
Commit: `feat: add median`.
|
||
|
||
## Task 3: describe
|
||
|
||
**Files:**
|
||
- Modify: `src/stats.py` (append `describe`)
|
||
- Modify: `tests/test_stats.py` (append `DescribeTest`, import `describe`)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `mean` (Task 1), `median` (Task 2).
|
||
- Produces: `describe(values: list[float]) -> dict` with keys `count`, `mean`, `median`.
|
||
|
||
Exact code to append to `src/stats.py`:
|
||
|
||
```python
|
||
def describe(values):
|
||
return {"count": len(values), "mean": mean(values), "median": median(values)}
|
||
```
|
||
|
||
Test to append (class `DescribeTest`):
|
||
|
||
```python
|
||
class DescribeTest(unittest.TestCase):
|
||
def test_describe(self):
|
||
self.assertEqual(
|
||
describe([1, 2, 6]),
|
||
{"count": 3, "mean": 3.0, "median": 2.0},
|
||
)
|
||
```
|
||
|
||
Run: `python3 -m unittest discover -s tests` → 5 tests OK.
|
||
Commit: `feat: add describe`.
|