Files
claude-skills-reference/tests/test_rice_prioritizer.py
Claude 36678cd00d feat(tests): add comprehensive test suite with 1493 tests across 4 phases
Phase 1 - Foundation:
- Add pytest, pyproject.toml, requirements-dev.txt
- 593 smoke tests (324 syntax + 269 argparse --help) for all Python scripts
- Harden CI: remove || true from compileall, expand to all 9 directories, add pytest step

Phase 2 - Calculator unit tests:
- RICE prioritizer: formula, prioritization, portfolio analysis, roadmap
- DCF valuation: WACC, cash flow projections, terminal value, sensitivity
- Financial ratios: profitability, liquidity, leverage, efficiency, valuation
- Campaign ROI: metrics, benchmarks, portfolio summary
- Funnel analyzer: stage metrics, bottleneck detection, segment comparison
- OKR tracker: numeric/percentage/milestone/boolean KR scoring, status

Phase 3 - Parser and compliance tests:
- SEO checker: HTML parsing, scoring, heading hierarchy, alt text, word count
- Commit linter: conventional commit regex, lint report, file input
- GDPR compliance: pattern detection, file scanning, project analysis

Phase 4 - Integration tests:
- 671 skill integrity tests: frontmatter, H1 headings, scripts dirs, references

Bug fixes found by tests:
- Fix duplicate --reason argparse arg in document_version_control.py

https://claude.ai/code/session_01MsVmZoAsPvLv7rAGDBGTbL
2026-03-30 19:54:00 +00:00

144 lines
5.3 KiB
Python

"""Unit tests for the RICE Prioritizer."""
import sys
import os
import pytest
sys.path.insert(0, os.path.join(
os.path.dirname(__file__), "..", "product-team", "product-manager-toolkit", "scripts"
))
from rice_prioritizer import RICECalculator
@pytest.fixture
def calc():
return RICECalculator()
class TestCalculateRice:
"""Test the core RICE formula: (Reach * Impact * Confidence) / Effort."""
def test_basic_calculation(self, calc):
# reach=1000, impact=high(2.0), confidence=high(100/100=1.0), effort=m(5)
# = (1000 * 2.0 * 1.0) / 5 = 400.0
assert calc.calculate_rice(1000, "high", "high", "m") == 400.0
def test_massive_impact(self, calc):
# reach=500, impact=massive(3.0), confidence=medium(0.8), effort=s(3)
# = (500 * 3.0 * 0.8) / 3 = 400.0
assert calc.calculate_rice(500, "massive", "medium", "s") == 400.0
def test_minimal_impact(self, calc):
# reach=1000, impact=minimal(0.25), confidence=low(0.5), effort=xs(1)
# = (1000 * 0.25 * 0.5) / 1 = 125.0
assert calc.calculate_rice(1000, "minimal", "low", "xs") == 125.0
def test_zero_reach(self, calc):
assert calc.calculate_rice(0, "high", "high", "m") == 0.0
def test_case_insensitive(self, calc):
assert calc.calculate_rice(1000, "HIGH", "HIGH", "M") == 400.0
def test_unknown_impact_defaults_to_one(self, calc):
# Unknown impact maps to 1.0
# reach=1000, impact=1.0, confidence=high(1.0), effort=m(5)
# = (1000 * 1.0 * 1.0) / 5 = 200.0
assert calc.calculate_rice(1000, "unknown", "high", "m") == 200.0
def test_xl_effort(self, calc):
# reach=1300, impact=medium(1.0), confidence=high(1.0), effort=xl(13)
# = (1300 * 1.0 * 1.0) / 13 = 100.0
assert calc.calculate_rice(1300, "medium", "high", "xl") == 100.0
@pytest.mark.parametrize("impact,expected_score", [
("massive", 3.0),
("high", 2.0),
("medium", 1.0),
("low", 0.5),
("minimal", 0.25),
])
def test_impact_map(self, calc, impact, expected_score):
# reach=100, confidence=high(1.0), effort=xs(1) -> score = 100 * impact
result = calc.calculate_rice(100, impact, "high", "xs")
assert result == round(100 * expected_score, 2)
class TestPrioritizeFeatures:
"""Test feature sorting by RICE score."""
def test_sorts_descending(self, calc):
features = [
{"name": "low", "reach": 100, "impact": "low", "confidence": "low", "effort": "xl"},
{"name": "high", "reach": 10000, "impact": "massive", "confidence": "high", "effort": "xs"},
]
result = calc.prioritize_features(features)
assert result[0]["name"] == "high"
assert result[1]["name"] == "low"
def test_adds_rice_score(self, calc):
features = [{"name": "test", "reach": 1000, "impact": "high", "confidence": "high", "effort": "m"}]
result = calc.prioritize_features(features)
assert "rice_score" in result[0]
assert result[0]["rice_score"] == 400.0
def test_empty_list(self, calc):
assert calc.prioritize_features([]) == []
def test_defaults_for_missing_fields(self, calc):
features = [{"name": "sparse"}]
result = calc.prioritize_features(features)
assert result[0]["rice_score"] == 0.0 # reach defaults to 0
class TestAnalyzePortfolio:
"""Test portfolio analysis metrics."""
def test_empty_features(self, calc):
assert calc.analyze_portfolio([]) == {}
def test_counts_quick_wins(self, calc):
features = [
{"name": "qw", "reach": 1000, "impact": "high", "confidence": "high", "effort": "xs", "rice_score": 100},
{"name": "big", "reach": 1000, "impact": "high", "confidence": "high", "effort": "xl", "rice_score": 50},
]
result = calc.analyze_portfolio(features)
assert result["quick_wins"] == 1
assert result["big_bets"] == 1
assert result["total_features"] == 2
def test_total_effort(self, calc):
features = [
{"name": "a", "effort": "m", "rice_score": 10}, # 5 months
{"name": "b", "effort": "s", "rice_score": 20}, # 3 months
]
result = calc.analyze_portfolio(features)
assert result["total_effort_months"] == 8
class TestGenerateRoadmap:
"""Test roadmap generation with capacity constraints."""
def test_single_quarter(self, calc):
features = [
{"name": "a", "effort": "s", "rice_score": 100}, # 3 months
{"name": "b", "effort": "s", "rice_score": 50}, # 3 months
]
roadmap = calc.generate_roadmap(features, team_capacity=10)
assert len(roadmap) == 1
assert len(roadmap[0]["features"]) == 2
assert roadmap[0]["capacity_used"] == 6
def test_overflow_to_next_quarter(self, calc):
features = [
{"name": "a", "effort": "l", "rice_score": 100}, # 8 months
{"name": "b", "effort": "l", "rice_score": 50}, # 8 months
]
roadmap = calc.generate_roadmap(features, team_capacity=10)
assert len(roadmap) == 2
assert roadmap[0]["features"][0]["name"] == "a"
assert roadmap[1]["features"][0]["name"] == "b"
def test_empty_features(self, calc):
assert calc.generate_roadmap([], team_capacity=10) == []