| """ |
| Tests for GestureScorer β the quality scoring system. |
| |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β π CONCEPT: Unit Testing with unittest β |
| β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£ |
| β β |
| β Python's built-in unittest module provides everything we need: β |
| β β |
| β β’ unittest.TestCase β Base class for test classes β |
| β β’ self.assertEqual() β Check two values are equal β |
| β β’ self.assertTrue() β Check a condition is True β |
| β β’ self.assertIsNone() β Check a value is None β |
| β β’ self.assertGreater() β Check a > b β |
| β β’ self.assertAlmostEqual() β Check floats are close (avoids rounding) β |
| β β |
| β Each test method name must start with "test_". β |
| β Each test checks ONE specific behavior. β |
| β β |
| β HOW TO RUN: β |
| β python -m unittest tests.test_scoring -v β |
| β β |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| """ |
|
|
| import unittest |
|
|
| from oop_sorting_teaching.models.gesture import GestureImage, GestureRanking |
| from oop_sorting_teaching.models.scoring import GestureScorer |
|
|
|
|
| |
| |
| |
|
|
| def make_gesture(name: str, capture_id: int, confidence: float) -> GestureImage: |
| """ |
| Factory helper for creating test gestures. |
| |
| Using a helper keeps tests short and focused on WHAT is tested, |
| not on HOW to construct objects. |
| """ |
| return GestureImage.create_from_prediction( |
| gesture_name=name, |
| capture_id=capture_id, |
| confidence=confidence, |
| ) |
|
|
|
|
| |
| |
| |
|
|
| class TestComputeScore(unittest.TestCase): |
| """ |
| Tests for GestureScorer.compute_score(). |
| |
| π OOP NOTE: |
| By inheriting from unittest.TestCase, our class gains |
| all the assertion methods (assertEqual, assertTrue, etc.) |
| without writing them ourselves. This is INHERITANCE in action! |
| """ |
|
|
| def test_basic_fist(self): |
| """Fist (complexity 1.0) at 90% confidence β score = 90.0.""" |
| gesture = make_gesture("fist", 1, confidence=0.90) |
| score = GestureScorer.compute_score(gesture) |
| self.assertEqual(score, 90.0) |
|
|
| def test_basic_ok(self): |
| """OK (complexity 1.7) at 80% confidence β score = 136.0.""" |
| gesture = make_gesture("ok", 2, confidence=0.80) |
| score = GestureScorer.compute_score(gesture) |
| self.assertEqual(score, 136.0) |
|
|
| def test_zero_confidence(self): |
| """Zero confidence should always produce a score of 0.""" |
| gesture = make_gesture("call", 3, confidence=0.0) |
| score = GestureScorer.compute_score(gesture) |
| self.assertEqual(score, 0.0) |
|
|
| def test_full_confidence_fist(self): |
| """100% confidence on fist (complexity 1.0) β exactly 100.""" |
| gesture = make_gesture("fist", 4, confidence=1.0) |
| score = GestureScorer.compute_score(gesture) |
| self.assertEqual(score, 100.0) |
|
|
| def test_mute_highest_complexity(self): |
| """Mute (complexity 2.0) at 100% confidence β 200.0.""" |
| gesture = make_gesture("mute", 5, confidence=1.0) |
| score = GestureScorer.compute_score(gesture) |
| self.assertEqual(score, 200.0) |
|
|
| def test_confidence_weight_doubles_score(self): |
| """Doubling confidence_weight should double the score.""" |
| gesture = make_gesture("fist", 6, confidence=0.50) |
| normal = GestureScorer.compute_score(gesture, confidence_weight=1.0) |
| doubled = GestureScorer.compute_score(gesture, confidence_weight=2.0) |
| |
| self.assertAlmostEqual(doubled, normal * 2, places=5) |
|
|
| def test_complexity_weight_changes_score(self): |
| """Higher complexity_weight should amplify complexity differences.""" |
| gesture = make_gesture("ok", 7, confidence=1.0) |
| |
| low_weight = GestureScorer.compute_score( |
| gesture, complexity_weight=0.5 |
| ) |
| high_weight = GestureScorer.compute_score( |
| gesture, complexity_weight=2.0 |
| ) |
| |
| self.assertGreater(high_weight, low_weight) |
|
|
| def test_score_is_rounded(self): |
| """Score should be rounded to 2 decimal places.""" |
| gesture = make_gesture("rock", 8, confidence=0.777) |
| score = GestureScorer.compute_score(gesture) |
| |
| self.assertEqual(score, 124.32) |
|
|
|
|
| |
| |
| |
|
|
| class TestApplyAndClearScores(unittest.TestCase): |
| """Tests for applying and clearing scores on gesture lists.""" |
|
|
| def test_apply_scores_sets_sort_value(self): |
| """After apply_scores, every gesture should have _sort_value set.""" |
| images = [ |
| make_gesture("fist", 1, 0.9), |
| make_gesture("ok", 2, 0.8), |
| make_gesture("call", 3, 0.7), |
| ] |
| GestureScorer.apply_scores(images) |
| for img in images: |
| self.assertIsNotNone(img._sort_value) |
|
|
| def test_apply_scores_negates(self): |
| """_sort_value should be negative (for descending sort order).""" |
| images = [make_gesture("fist", 1, 0.9)] |
| GestureScorer.apply_scores(images) |
| self.assertLess(images[0]._sort_value, 0) |
|
|
| def test_apply_scores_correct_value(self): |
| """_sort_value should equal the negated computed score.""" |
| gesture = make_gesture("fist", 1, 0.9) |
| expected_score = GestureScorer.compute_score(gesture) |
| GestureScorer.apply_scores([gesture]) |
| self.assertAlmostEqual(gesture._sort_value, -expected_score, places=5) |
|
|
| def test_clear_scores_resets_to_none(self): |
| """After clear_scores, _sort_value should be None.""" |
| images = [ |
| make_gesture("fist", 1, 0.9), |
| make_gesture("ok", 2, 0.8), |
| ] |
| GestureScorer.apply_scores(images) |
| GestureScorer.clear_scores(images) |
| for img in images: |
| self.assertIsNone(img._sort_value) |
|
|
| def test_get_score_returns_positive(self): |
| """get_score should return the positive (display) score.""" |
| gesture = make_gesture("ok", 1, 0.8) |
| GestureScorer.apply_scores([gesture]) |
| display_score = GestureScorer.get_score(gesture) |
| self.assertGreater(display_score, 0) |
| self.assertEqual(display_score, GestureScorer.compute_score(gesture)) |
|
|
| def test_get_score_returns_zero_when_no_score(self): |
| """get_score returns 0.0 when no score has been applied.""" |
| gesture = make_gesture("fist", 1, 0.9) |
| self.assertEqual(GestureScorer.get_score(gesture), 0.0) |
|
|
|
|
| |
| |
| |
|
|
| class TestFormulaDescription(unittest.TestCase): |
| """Tests for the human-readable formula string.""" |
|
|
| def test_default_formula(self): |
| """Default weights produce a readable formula string.""" |
| desc = GestureScorer.get_formula_description() |
| self.assertIn("confidence", desc) |
| self.assertIn("complexity", desc) |
| self.assertIn("1.0", desc) |
|
|
| def test_custom_weights_in_formula(self): |
| """Custom weights appear in the formula description.""" |
| desc = GestureScorer.get_formula_description(2.5, 1.5) |
| self.assertIn("2.5", desc) |
| self.assertIn("1.5", desc) |
|
|
|
|
| |
| |
| |
| |
| |
| |
|
|
| if __name__ == "__main__": |
| unittest.main() |
|
|