import contextlib import io import json import runpy import shutil import subprocess import sys import tempfile import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] TEMPLATE = ROOT / "classroom_data/templates/week21" class Lesson21TemplateTests(unittest.TestCase): def setUp(self): self.lesson = runpy.run_path(str(TEMPLATE / "main.py")) self.rows = self.lesson["load_rows"](TEMPLATE / "data/students_by_county.csv") def run_starter(self, county="新北市", completed=False): with tempfile.TemporaryDirectory() as directory: project = Path(directory) / "project" shutil.copytree(TEMPLATE, project, ignore=shutil.ignore_patterns("__pycache__")) source = (project / "main.py").read_text(encoding="utf-8") source = source.replace('COUNTY = "新北市"', f'COUNTY = "{county}"') if completed: source = source.replace("change = None", "change = after - before") (project / "main.py").write_text(source, encoding="utf-8") result = subprocess.run([sys.executable, "-B", "main.py"], cwd=project, capture_output=True, text=True, timeout=10) self.assertEqual(result.returncode, 0, result.stderr) return result.stdout def test_lesson_is_enabled_without_changing_existing_workspace(self): course = json.loads((ROOT / "classroom_data/course.json").read_text(encoding="utf-8")) lessons = {item["id"]: item for item in course["weeks"]} self.assertTrue(lessons["week21"]["template"]) self.assertEqual(lessons["week21"]["workspace_id"], "week19") self.assertEqual(lessons["week20"]["workspace_id"], "week19") self.assertTrue((TEMPLATE / "REPORT.md").is_file()) self.assertTrue((TEMPLATE / "DATA_SOURCE.md").is_file()) def test_starter_runs_before_student_fills_in_the_calculation(self): output = self.run_starter() self.assertIn("讀入 242 列資料", output) self.assertIn("201,970 人", output) self.assertIn("194,670 人", output) self.assertIn("請完成 change", output) self.assertNotIn("增減人數:", output) def test_completed_examples_match_the_lesson_slides(self): self.assertIn("增減人數:-7,300 人", self.run_starter(completed=True)) self.assertIn("增減人數:+4,060 人", self.run_starter("臺中市", completed=True)) def test_rankings_use_the_supplied_dataset(self): results = self.lesson["count_by_county"](self.rows, "114", "國小[人]") self.assertEqual(results[:3], [("新北市", 194670), ("臺中市", 161332), ("桃園市", 132964)]) def test_unknown_counts_are_not_zero_and_zero_chart_does_not_divide(self): for value in ("", "-", "未知", None): self.assertIsNone(self.lesson["parse_count"](value)) self.assertEqual(self.lesson["parse_count"]("0"), 0) self.assertEqual(self.lesson["parse_count"](" 1,234 "), 1234) self.assertIsNone(self.lesson["get_count"](self.rows, "999", "新北市", "國小[人]")) with contextlib.redirect_stdout(io.StringIO()): self.lesson["print_text_chart"]([("甲", 0), ("乙", 0)]) self.assertIn("找不到完整的兩筆人數", self.run_starter("不存在的縣市", completed=True)) if __name__ == "__main__": unittest.main()