{"id":"code-col-pytest-five-named-edge-cases","audience":"college","category":"coding","track":"coding","technique":"constraints","title":"Write exactly 5 pytest tests for your team's parser","free":false,"task":"Your group project uses the parse_duration helper below, and your team agreed on 5 edge cases to test: an empty string raises ValueError, '0s' returns 0, '1h30m' returns 5400, '90s' returns 90, and uppercase '1H' raises ValueError. Write exactly 5 plain pytest functions, one per case, importing only pytest and parse_duration, in exactly 1 code block of at most 20 lines, with under 20 words of text.","materialLabel":"Your teammate's helper (attached to your prompt automatically)","material":"# durations.py  (Python 3.11, group project: StudyTimer CLI)\nimport re\n\n_PART = re.compile(r\"(\\d+)([hms])\")\n_UNIT = {\"h\": 3600, \"m\": 60, \"s\": 1}\n\n\ndef parse_duration(text):\n    \"\"\"Turn text like '1h30m' or '45s' into a number of seconds.\"\"\"\n    if not text:\n        raise ValueError(\"empty duration\")\n    pos = 0\n    total = 0\n    for match in _PART.finditer(text):\n        if match.start() != pos:\n            raise ValueError(f\"bad duration: {text!r}\")\n        total += int(match.group(1)) * _UNIT[match.group(2)]\n        pos = match.end()\n    if pos != len(text):\n        raise ValueError(f\"bad duration: {text!r}\")\n    return total\n\n\n# Used by the CLI like this:\n#   $ studytimer --break 15m\n#   $ studytimer --focus 1h30m\n#\n# Repo layout:\n#   studytimer/\n#     durations.py\n#     cli.py\n#     tests/\n#       test_cli.py      (Maya wrote these)\n#       test_durations.py  (empty, yours)\n#\n# CI runs: pytest -q\n# Notes from standup: Leo wants to support \"1.5h\" someday, not now.","rules":[{"type":"codeBlocks","count":1},{"type":"maxCodeLines","max":20},{"type":"maxProseWords","max":20}],"mistakes":[{"title":"Getting a giant test suite","why":"Asked for tests, the AI writes 10 to 20 of them, often parametrized, covering cases your team didn't pick.","fix":"Say: \"Exactly 5 plain test functions, one per case, no parametrize.\"","checks":["r0","maxCodeLines"]},{"title":"Not listing the cases","why":"The AI picks its own edge cases unless you name the 5 your team agreed on.","fix":"List them: \"'' raises ValueError, '0s' -> 0, '1h30m' -> 5400, '90s' -> 90, '1H' raises ValueError.\"","checks":["r1"]},{"title":"Extra imports and helpers","why":"The AI may import parametrize helpers or test the future '1.5h' idea.","fix":"Add: \"Import only pytest and parse_duration from durations. Nothing else.\"","checks":["r2"]},{"title":"A tutorial around the code","why":"By default the AI explains how to run pytest and what each test does.","fix":"Add: \"Only the code block, no text.\"","checks":["codeBlocks","maxProseWords"]}],"promptHints":[{"label":"Sets the number of tests","patterns":["\\b(5|five)\\b.{0,20}test","exactly \\d"]},{"label":"Lists the agreed cases","patterns":["5400","0s","1H","90s","empty"]},{"label":"Rules out parametrize and classes","patterns":["parametri","no class","plain (function|test)"]},{"label":"Limits imports","patterns":["import only","only import","no other imports"]},{"label":"Asks for only code","patterns":["only the code","no (text|explanation)","one code block"]}],"hint":"Tests are only useful if they check what your team agreed on. Name the exact cases, how many tests, and what the file may import.","rubricLabels":["Contains exactly 5 test functions, each a plain top-level def whose name starts with test_ (no classes, no @pytest.mark.parametrize, no fixtures)","Covers exactly these cases, one per test: empty string raises ValueError, '0s' returns 0, '1h30m' returns 5400, '90s' returns 90, and '1H' raises ValueError (using pytest.raises for the two errors)","The only imports are pytest and parse_duration from durations, and there are no tests for other cases (such as '1.5h' or minutes-only strings)"]}