{"id":"code-col-py-unittest-four-boundaries","audience":"college","category":"coding","track":"coding","technique":"constraints","title":"Write 4 unittest boundary tests when pytest isn't allowed","free":false,"task":"Your club's gradebook script runs on a lab machine where only the standard library is installed, so tests must use unittest, not pytest. Write one TestCase class named TestLetter with exactly 4 test methods: letter(90) == 'A', letter(89) == 'B', letter(0) == 'F', and letter(101) raises ValueError (with assertRaises). Import only unittest and letter from grades, and end with the unittest.main() guard. Exactly 1 code block of at most 18 lines, under 20 words of text.","materialLabel":"The function (attached to your prompt automatically)","material":"# grades.py  (Chess Club ladder: turns match scores into letter tiers)\n# Python 3.10 on the student lab Linux box (no pip, standard library only)\n\ndef letter(score):\n    \"\"\"Map a 0-100 score to a ladder tier letter.\"\"\"\n    if not 0 <= score <= 100:\n        raise ValueError(f\"score out of range: {score}\")\n    if score >= 90:\n        return \"A\"\n    if score >= 80:\n        return \"B\"\n    if score >= 70:\n        return \"C\"\n    return \"F\"\n\n\n# How it's used in ladder.py:\n#   for player in players:\n#       player.tier = letter(player.score)\n#\n# Run tests on the lab box with:\n#   python3 -m unittest test_grades.py\n#\n# Thoughts for later (not built yet): a D tier at 60, and +/- tiers.\n# Last week letter(100.5) slipped through on Jamal's laptop because he\n# had an old copy without the range check.","rules":[{"type":"codeBlocks","count":1},{"type":"maxCodeLines","max":18},{"type":"maxProseWords","max":20}],"mistakes":[{"title":"Getting pytest","why":"pytest is the AI's default, and the lab box can't install it.","fix":"Add: \"Standard library only: unittest.TestCase, no pytest.\"","checks":["r0"]},{"title":"Too many tests","why":"The AI tests every tier and the future D tier too.","fix":"Say: \"Exactly 4 test methods: 90 -> A, 89 -> B, 0 -> F, 101 raises ValueError.\"","checks":["r1","maxCodeLines"]},{"title":"Missing the runner guard","why":"Without unittest.main(), running the file directly does nothing.","fix":"Add: \"Import only unittest and letter; end with the unittest.main() guard.\"","checks":["r2"]},{"title":"An explanation of testing","why":"The AI explains how unittest works.","fix":"Add: \"Only the code block.\"","checks":["codeBlocks","maxProseWords"]}],"promptHints":[{"label":"Names the framework","patterns":["unittest","no pytest","standard library"]},{"label":"Sets the number of tests","patterns":["\\b(4|four)\\b.{0,20}test"]},{"label":"Lists the boundary cases","patterns":["\\b90\\b","\\b89\\b","\\b101\\b"]},{"label":"Limits imports","patterns":["only import","import only","from grades import"]},{"label":"Asks for the main guard","patterns":["unittest\\.main","__main__"]}],"hint":"The test framework depends on what's installed where the tests run. Tell the AI that, plus the exact cases and how the file should end.","rubricLabels":["Uses unittest only: one class TestLetter(unittest.TestCase), with no pytest import, plain assert statements or pytest.raises","Has exactly 4 test methods covering exactly letter(90) == 'A', letter(89) == 'B', letter(0) == 'F' and letter(101) raising ValueError via assertRaises","The only imports are import unittest and from grades import letter, and the file ends with if __name__ == \"__main__\": unittest.main()"]}