[{"data":1,"prerenderedAt":2878},["ShallowReactive",2],{"page-\u002Fpython\u002F24-testing":3},{"id":4,"title":5,"body":6,"description":31,"extension":2872,"meta":2873,"navigation":52,"path":2874,"seo":2875,"stem":2876,"__hash__":2877},"content\u002Fpython\u002F24-testing.md","24 — Testing",{"type":7,"value":8,"toc":2849},"minimark",[9,13,22,297,322,339,346,490,515,543,597,620,624,813,838,843,1030,1060,1067,1252,1272,1276,1441,1467,1478,1521,1690,1739,1752,1913,1922,1926,1976,1998,2055,2071,2075,2172,2248,2260,2264,2336,2340,2415,2419,2422,2593,2783,2787,2845],[10,11,5],"h1",{"id":12},"_24-testing",[14,15,17,21],"h2",{"id":16},"unittest-the-standard-librarys-built-in-framework",[18,19,20],"code",{},"unittest",": The Standard Library's Built-In Framework",[23,24,26],"code-wrapper",{"language":25},"python",[27,28,32],"pre",{"className":29,"code":30,"language":25,"meta":31,"style":31},"language-python shiki shiki-themes github-light github-dark","import unittest\n\ndef add(a, b):\n    return a + b\n\nclass TestAdd(unittest.TestCase):\n    def setUp(self):\n        print(\"running before each test method\")\n\n    def test_positive_numbers(self):\n        self.assertEqual(add(2, 3), 5)\n\n    def test_negative_numbers(self):\n        self.assertEqual(add(-1, -1), -2)\n\n    def test_type_error_on_bad_input(self):\n        with self.assertRaises(TypeError):\n            add(\"2\", 3)\n\nif __name__ == \"__main__\":\n    unittest.main()\n","",[18,33,34,47,54,67,82,87,110,122,138,143,153,179,184,194,221,226,236,253,268,273,291],{"__ignoreMap":31},[35,36,39,43],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"svdQ7","import",[35,44,46],{"class":45},"ssxIu"," unittest\n",[35,48,50],{"class":37,"line":49},2,[35,51,53],{"emptyLinePlaceholder":52},true,"\n",[35,55,57,60,64],{"class":37,"line":56},3,[35,58,59],{"class":41},"def",[35,61,63],{"class":62},"sIsaT"," add",[35,65,66],{"class":45},"(a, b):\n",[35,68,70,73,76,79],{"class":37,"line":69},4,[35,71,72],{"class":41},"    return",[35,74,75],{"class":45}," a ",[35,77,78],{"class":41},"+",[35,80,81],{"class":45}," b\n",[35,83,85],{"class":37,"line":84},5,[35,86,53],{"emptyLinePlaceholder":52},[35,88,90,93,96,99,101,104,107],{"class":37,"line":89},6,[35,91,92],{"class":41},"class",[35,94,95],{"class":62}," TestAdd",[35,97,98],{"class":45},"(",[35,100,20],{"class":62},[35,102,103],{"class":45},".",[35,105,106],{"class":62},"TestCase",[35,108,109],{"class":45},"):\n",[35,111,113,116,119],{"class":37,"line":112},7,[35,114,115],{"class":41},"    def",[35,117,118],{"class":62}," setUp",[35,120,121],{"class":45},"(self):\n",[35,123,125,129,131,135],{"class":37,"line":124},8,[35,126,128],{"class":127},"snvgF","        print",[35,130,98],{"class":45},[35,132,134],{"class":133},"sJ6F3","\"running before each test method\"",[35,136,137],{"class":45},")\n",[35,139,141],{"class":37,"line":140},9,[35,142,53],{"emptyLinePlaceholder":52},[35,144,146,148,151],{"class":37,"line":145},10,[35,147,115],{"class":41},[35,149,150],{"class":62}," test_positive_numbers",[35,152,121],{"class":45},[35,154,156,159,162,165,168,171,174,177],{"class":37,"line":155},11,[35,157,158],{"class":127},"        self",[35,160,161],{"class":45},".assertEqual(add(",[35,163,164],{"class":127},"2",[35,166,167],{"class":45},", ",[35,169,170],{"class":127},"3",[35,172,173],{"class":45},"), ",[35,175,176],{"class":127},"5",[35,178,137],{"class":45},[35,180,182],{"class":37,"line":181},12,[35,183,53],{"emptyLinePlaceholder":52},[35,185,187,189,192],{"class":37,"line":186},13,[35,188,115],{"class":41},[35,190,191],{"class":62}," test_negative_numbers",[35,193,121],{"class":45},[35,195,197,199,201,204,207,209,211,213,215,217,219],{"class":37,"line":196},14,[35,198,158],{"class":127},[35,200,161],{"class":45},[35,202,203],{"class":41},"-",[35,205,206],{"class":127},"1",[35,208,167],{"class":45},[35,210,203],{"class":41},[35,212,206],{"class":127},[35,214,173],{"class":45},[35,216,203],{"class":41},[35,218,164],{"class":127},[35,220,137],{"class":45},[35,222,224],{"class":37,"line":223},15,[35,225,53],{"emptyLinePlaceholder":52},[35,227,229,231,234],{"class":37,"line":228},16,[35,230,115],{"class":41},[35,232,233],{"class":62}," test_type_error_on_bad_input",[35,235,121],{"class":45},[35,237,239,242,245,248,251],{"class":37,"line":238},17,[35,240,241],{"class":41},"        with",[35,243,244],{"class":127}," self",[35,246,247],{"class":45},".assertRaises(",[35,249,250],{"class":127},"TypeError",[35,252,109],{"class":45},[35,254,256,259,262,264,266],{"class":37,"line":255},18,[35,257,258],{"class":45},"            add(",[35,260,261],{"class":133},"\"2\"",[35,263,167],{"class":45},[35,265,170],{"class":127},[35,267,137],{"class":45},[35,269,271],{"class":37,"line":270},19,[35,272,53],{"emptyLinePlaceholder":52},[35,274,276,279,282,285,288],{"class":37,"line":275},20,[35,277,278],{"class":41},"if",[35,280,281],{"class":127}," __name__",[35,283,284],{"class":41}," ==",[35,286,287],{"class":133}," \"__main__\"",[35,289,290],{"class":45},":\n",[35,292,294],{"class":37,"line":293},21,[35,295,296],{"class":45},"    unittest.main()\n",[23,298,300],{"language":299},"bash",[27,301,304],{"className":302,"code":303,"language":299,"meta":31,"style":31},"language-bash shiki shiki-themes github-light github-dark","python -m unittest test_add.py -v\n",[18,305,306],{"__ignoreMap":31},[35,307,308,310,313,316,319],{"class":37,"line":38},[35,309,25],{"class":62},[35,311,312],{"class":127}," -m",[35,314,315],{"class":133}," unittest",[35,317,318],{"class":133}," test_add.py",[35,320,321],{"class":127}," -v\n",[323,324,325,327,328,330,331,334,335,338],"p",{},[18,326,20],{}," (inspired by Java's JUnit) requires subclassing ",[18,329,106],{},", naming test methods ",[18,332,333],{},"test_*",", and using ",[18,336,337],{},"self.assert*"," methods for checks. It ships with every Python installation — no dependency to add — which matters for tooling that must run in constrained environments, but its class-based, Java-flavored API is more verbose than the alternative most production Python codebases actually use.",[14,340,342,345],{"id":341},"pytest-the-de-facto-standard-in-practice",[18,343,344],{},"pytest",": The De Facto Standard in Practice",[23,347,348],{"language":25},[27,349,351],{"className":29,"code":350,"language":25,"meta":31,"style":31},"def add(a, b):\n    return a + b\n\ndef test_positive_numbers():\n    assert add(2, 3) == 5\n\ndef test_negative_numbers():\n    assert add(-1, -1) == -2\n\ndef test_type_error_on_bad_input():\n    import pytest\n    with pytest.raises(TypeError):\n        add(\"2\", 3)\n",[18,352,353,361,371,375,384,407,411,419,445,449,457,465,477],{"__ignoreMap":31},[35,354,355,357,359],{"class":37,"line":38},[35,356,59],{"class":41},[35,358,63],{"class":62},[35,360,66],{"class":45},[35,362,363,365,367,369],{"class":37,"line":49},[35,364,72],{"class":41},[35,366,75],{"class":45},[35,368,78],{"class":41},[35,370,81],{"class":45},[35,372,373],{"class":37,"line":56},[35,374,53],{"emptyLinePlaceholder":52},[35,376,377,379,381],{"class":37,"line":69},[35,378,59],{"class":41},[35,380,150],{"class":62},[35,382,383],{"class":45},"():\n",[35,385,386,389,392,394,396,398,401,404],{"class":37,"line":84},[35,387,388],{"class":41},"    assert",[35,390,391],{"class":45}," add(",[35,393,164],{"class":127},[35,395,167],{"class":45},[35,397,170],{"class":127},[35,399,400],{"class":45},") ",[35,402,403],{"class":41},"==",[35,405,406],{"class":127}," 5\n",[35,408,409],{"class":37,"line":89},[35,410,53],{"emptyLinePlaceholder":52},[35,412,413,415,417],{"class":37,"line":112},[35,414,59],{"class":41},[35,416,191],{"class":62},[35,418,383],{"class":45},[35,420,421,423,425,427,429,431,433,435,437,439,442],{"class":37,"line":124},[35,422,388],{"class":41},[35,424,391],{"class":45},[35,426,203],{"class":41},[35,428,206],{"class":127},[35,430,167],{"class":45},[35,432,203],{"class":41},[35,434,206],{"class":127},[35,436,400],{"class":45},[35,438,403],{"class":41},[35,440,441],{"class":41}," -",[35,443,444],{"class":127},"2\n",[35,446,447],{"class":37,"line":140},[35,448,53],{"emptyLinePlaceholder":52},[35,450,451,453,455],{"class":37,"line":145},[35,452,59],{"class":41},[35,454,233],{"class":62},[35,456,383],{"class":45},[35,458,459,462],{"class":37,"line":155},[35,460,461],{"class":41},"    import",[35,463,464],{"class":45}," pytest\n",[35,466,467,470,473,475],{"class":37,"line":181},[35,468,469],{"class":41},"    with",[35,471,472],{"class":45}," pytest.raises(",[35,474,250],{"class":127},[35,476,109],{"class":45},[35,478,479,482,484,486,488],{"class":37,"line":186},[35,480,481],{"class":45},"        add(",[35,483,261],{"class":133},[35,485,167],{"class":45},[35,487,170],{"class":127},[35,489,137],{"class":45},[23,491,492],{"language":299},[27,493,495],{"className":302,"code":494,"language":299,"meta":31,"style":31},"pip install pytest\npytest test_add.py -v\n",[18,496,497,507],{"__ignoreMap":31},[35,498,499,502,505],{"class":37,"line":38},[35,500,501],{"class":62},"pip",[35,503,504],{"class":133}," install",[35,506,464],{"class":133},[35,508,509,511,513],{"class":37,"line":49},[35,510,344],{"class":62},[35,512,318],{"class":133},[35,514,321],{"class":127},[323,516,517,518,521,522,525,526,528,529,533,534,536,537,539,540,542],{},"No class required, no ",[18,519,520],{},"self.assertEqual"," — plain ",[18,523,524],{},"assert"," statements, with ",[18,527,344],{}," rewriting assertion failures to show rich diffs of the actual vs expected values (down to which specific list element differed, or which dict keys mismatched) without any special assertion methods to memorize. ",[530,531,532],"strong",{},"Best practice",": for new projects, use ",[18,535,344],{}," — it's a strict superset in capability (it can even run ",[18,538,20],{},"-style tests unchanged) and is the ecosystem default for a reason: fixtures, parametrization, and plugins (covered below) that ",[18,541,20],{}," has no equivalent for.",[23,544,545],{"language":25},[27,546,548],{"className":29,"code":547,"language":25,"meta":31,"style":31},"def test_list_equality():\n    assert [1, 2, 3] == [1, 2, 4]\n",[18,549,550,559],{"__ignoreMap":31},[35,551,552,554,557],{"class":37,"line":38},[35,553,59],{"class":41},[35,555,556],{"class":62}," test_list_equality",[35,558,383],{"class":45},[35,560,561,563,566,568,570,572,574,576,579,581,583,585,587,589,591,594],{"class":37,"line":49},[35,562,388],{"class":41},[35,564,565],{"class":45}," [",[35,567,206],{"class":127},[35,569,167],{"class":45},[35,571,164],{"class":127},[35,573,167],{"class":45},[35,575,170],{"class":127},[35,577,578],{"class":45},"] ",[35,580,403],{"class":41},[35,582,565],{"class":45},[35,584,206],{"class":127},[35,586,167],{"class":45},[35,588,164],{"class":127},[35,590,167],{"class":45},[35,592,593],{"class":127},"4",[35,595,596],{"class":45},"]\n",[23,598,599],{"language":299},[27,600,602],{"className":302,"code":601,"language":299,"meta":31,"style":31},"# pytest's failure output shows exactly which element differs:\n# E       assert [1, 2, 3] == [1, 2, 4]\n# E         At index 2 diff: 3 != 4\n",[18,603,604,610,615],{"__ignoreMap":31},[35,605,606],{"class":37,"line":38},[35,607,609],{"class":608},"sdCPZ","# pytest's failure output shows exactly which element differs:\n",[35,611,612],{"class":37,"line":49},[35,613,614],{"class":608},"# E       assert [1, 2, 3] == [1, 2, 4]\n",[35,616,617],{"class":37,"line":56},[35,618,619],{"class":608},"# E         At index 2 diff: 3 != 4\n",[14,621,623],{"id":622},"fixtures-reusable-setup-and-teardown","Fixtures: Reusable Setup and Teardown",[23,625,626],{"language":25},[27,627,629],{"className":29,"code":628,"language":25,"meta":31,"style":31},"import pytest\n\n@pytest.fixture\ndef sample_users():\n    print(\"\\nsetting up users\")\n    users = [{\"id\": 1, \"name\": \"Ada\"}, {\"id\": 2, \"name\": \"Grace\"}]\n    yield users               # everything before yield is setup; after yield is teardown\n    print(\"\\ntearing down users\")\n\ndef test_user_count(sample_users):\n    assert len(sample_users) == 2\n\ndef test_first_user_name(sample_users):\n    assert sample_users[0][\"name\"] == \"Ada\"\n",[18,630,631,637,641,646,655,673,723,734,749,753,763,778,782,791],{"__ignoreMap":31},[35,632,633,635],{"class":37,"line":38},[35,634,42],{"class":41},[35,636,464],{"class":45},[35,638,639],{"class":37,"line":49},[35,640,53],{"emptyLinePlaceholder":52},[35,642,643],{"class":37,"line":56},[35,644,645],{"class":62},"@pytest.fixture\n",[35,647,648,650,653],{"class":37,"line":69},[35,649,59],{"class":41},[35,651,652],{"class":62}," sample_users",[35,654,383],{"class":45},[35,656,657,660,662,665,668,671],{"class":37,"line":84},[35,658,659],{"class":127},"    print",[35,661,98],{"class":45},[35,663,664],{"class":133},"\"",[35,666,667],{"class":127},"\\n",[35,669,670],{"class":133},"setting up users\"",[35,672,137],{"class":45},[35,674,675,678,681,684,687,690,692,694,697,699,702,705,707,709,711,713,715,717,720],{"class":37,"line":89},[35,676,677],{"class":45},"    users ",[35,679,680],{"class":41},"=",[35,682,683],{"class":45}," [{",[35,685,686],{"class":133},"\"id\"",[35,688,689],{"class":45},": ",[35,691,206],{"class":127},[35,693,167],{"class":45},[35,695,696],{"class":133},"\"name\"",[35,698,689],{"class":45},[35,700,701],{"class":133},"\"Ada\"",[35,703,704],{"class":45},"}, {",[35,706,686],{"class":133},[35,708,689],{"class":45},[35,710,164],{"class":127},[35,712,167],{"class":45},[35,714,696],{"class":133},[35,716,689],{"class":45},[35,718,719],{"class":133},"\"Grace\"",[35,721,722],{"class":45},"}]\n",[35,724,725,728,731],{"class":37,"line":112},[35,726,727],{"class":41},"    yield",[35,729,730],{"class":45}," users               ",[35,732,733],{"class":608},"# everything before yield is setup; after yield is teardown\n",[35,735,736,738,740,742,744,747],{"class":37,"line":124},[35,737,659],{"class":127},[35,739,98],{"class":45},[35,741,664],{"class":133},[35,743,667],{"class":127},[35,745,746],{"class":133},"tearing down users\"",[35,748,137],{"class":45},[35,750,751],{"class":37,"line":140},[35,752,53],{"emptyLinePlaceholder":52},[35,754,755,757,760],{"class":37,"line":145},[35,756,59],{"class":41},[35,758,759],{"class":62}," test_user_count",[35,761,762],{"class":45},"(sample_users):\n",[35,764,765,767,770,773,775],{"class":37,"line":155},[35,766,388],{"class":41},[35,768,769],{"class":127}," len",[35,771,772],{"class":45},"(sample_users) ",[35,774,403],{"class":41},[35,776,777],{"class":127}," 2\n",[35,779,780],{"class":37,"line":181},[35,781,53],{"emptyLinePlaceholder":52},[35,783,784,786,789],{"class":37,"line":186},[35,785,59],{"class":41},[35,787,788],{"class":62}," test_first_user_name",[35,790,762],{"class":45},[35,792,793,795,798,801,804,806,808,810],{"class":37,"line":196},[35,794,388],{"class":41},[35,796,797],{"class":45}," sample_users[",[35,799,800],{"class":127},"0",[35,802,803],{"class":45},"][",[35,805,696],{"class":133},[35,807,578],{"class":45},[35,809,403],{"class":41},[35,811,812],{"class":133}," \"Ada\"\n",[323,814,815,816,818,819,822,823,826,827,830,831,833,834,837],{},"A fixture is requested simply by naming it as a test function's parameter — ",[18,817,344],{}," matches the parameter name to a fixture function and injects its ",[18,820,821],{},"yield","-ed value automatically, no explicit ",[18,824,825],{},"setUp","\u002F",[18,828,829],{},"tearDown"," boilerplate class needed. The code after ",[18,832,821],{}," runs as teardown, exactly like a generator-based context manager (chapter 19) — and indeed, ",[18,835,836],{},"@pytest.fixture"," functions are literally generators under the hood.",[839,840,842],"h3",{"id":841},"fixture-scope-controlling-how-often-setupteardown-runs","Fixture scope: controlling how often setup\u002Fteardown runs",[23,844,845],{"language":25},[27,846,848],{"className":29,"code":847,"language":25,"meta":31,"style":31},"import pytest\n\n@pytest.fixture(scope=\"function\")   # default — runs fresh for EVERY test function\ndef fresh_connection():\n    print(\"opening connection\")\n    yield \"connection\"\n    print(\"closing connection\")\n\n@pytest.fixture(scope=\"module\")     # runs ONCE per test file, shared across all tests in it\ndef expensive_database():\n    print(\"setting up database (expensive!)\")\n    yield \"db\"\n    print(\"tearing down database\")\n\n@pytest.fixture(scope=\"session\")    # runs ONCE for the entire test run, across all files\ndef global_config():\n    return {\"env\": \"test\"}\n",[18,849,850,856,860,881,890,901,908,919,923,942,951,962,969,980,984,1003,1012],{"__ignoreMap":31},[35,851,852,854],{"class":37,"line":38},[35,853,42],{"class":41},[35,855,464],{"class":45},[35,857,858],{"class":37,"line":49},[35,859,53],{"emptyLinePlaceholder":52},[35,861,862,864,866,870,872,875,878],{"class":37,"line":56},[35,863,836],{"class":62},[35,865,98],{"class":45},[35,867,869],{"class":868},"sCrzJ","scope",[35,871,680],{"class":41},[35,873,874],{"class":133},"\"function\"",[35,876,877],{"class":45},")   ",[35,879,880],{"class":608},"# default — runs fresh for EVERY test function\n",[35,882,883,885,888],{"class":37,"line":69},[35,884,59],{"class":41},[35,886,887],{"class":62}," fresh_connection",[35,889,383],{"class":45},[35,891,892,894,896,899],{"class":37,"line":84},[35,893,659],{"class":127},[35,895,98],{"class":45},[35,897,898],{"class":133},"\"opening connection\"",[35,900,137],{"class":45},[35,902,903,905],{"class":37,"line":89},[35,904,727],{"class":41},[35,906,907],{"class":133}," \"connection\"\n",[35,909,910,912,914,917],{"class":37,"line":112},[35,911,659],{"class":127},[35,913,98],{"class":45},[35,915,916],{"class":133},"\"closing connection\"",[35,918,137],{"class":45},[35,920,921],{"class":37,"line":124},[35,922,53],{"emptyLinePlaceholder":52},[35,924,925,927,929,931,933,936,939],{"class":37,"line":140},[35,926,836],{"class":62},[35,928,98],{"class":45},[35,930,869],{"class":868},[35,932,680],{"class":41},[35,934,935],{"class":133},"\"module\"",[35,937,938],{"class":45},")     ",[35,940,941],{"class":608},"# runs ONCE per test file, shared across all tests in it\n",[35,943,944,946,949],{"class":37,"line":145},[35,945,59],{"class":41},[35,947,948],{"class":62}," expensive_database",[35,950,383],{"class":45},[35,952,953,955,957,960],{"class":37,"line":155},[35,954,659],{"class":127},[35,956,98],{"class":45},[35,958,959],{"class":133},"\"setting up database (expensive!)\"",[35,961,137],{"class":45},[35,963,964,966],{"class":37,"line":181},[35,965,727],{"class":41},[35,967,968],{"class":133}," \"db\"\n",[35,970,971,973,975,978],{"class":37,"line":186},[35,972,659],{"class":127},[35,974,98],{"class":45},[35,976,977],{"class":133},"\"tearing down database\"",[35,979,137],{"class":45},[35,981,982],{"class":37,"line":196},[35,983,53],{"emptyLinePlaceholder":52},[35,985,986,988,990,992,994,997,1000],{"class":37,"line":223},[35,987,836],{"class":62},[35,989,98],{"class":45},[35,991,869],{"class":868},[35,993,680],{"class":41},[35,995,996],{"class":133},"\"session\"",[35,998,999],{"class":45},")    ",[35,1001,1002],{"class":608},"# runs ONCE for the entire test run, across all files\n",[35,1004,1005,1007,1010],{"class":37,"line":228},[35,1006,59],{"class":41},[35,1008,1009],{"class":62}," global_config",[35,1011,383],{"class":45},[35,1013,1014,1016,1019,1022,1024,1027],{"class":37,"line":238},[35,1015,72],{"class":41},[35,1017,1018],{"class":45}," {",[35,1020,1021],{"class":133},"\"env\"",[35,1023,689],{"class":45},[35,1025,1026],{"class":133},"\"test\"",[35,1028,1029],{"class":45},"}\n",[323,1031,1032,1035,1036,1039,1040,1043,1044,1048,1049,1052,1053,1055,1056,1059],{},[530,1033,1034],{},"The gotcha",": a ",[18,1037,1038],{},"scope=\"module\""," or ",[18,1041,1042],{},"scope=\"session\""," fixture is shared ",[1045,1046,1047],"em",{},"mutable state"," across multiple tests — if one test mutates the yielded object, later tests in the same scope see the mutation, which can produce order-dependent test failures that only appear when tests run in a particular sequence (or in parallel with ",[18,1050,1051],{},"pytest-xdist","). ",[530,1054,532],{},": default to ",[18,1057,1058],{},"scope=\"function\""," unless setup is genuinely expensive (a real database connection, a Docker container) and the tests using it don't mutate shared state in ways that leak between them.",[14,1061,1063,1066],{"id":1062},"parametrize-one-test-function-many-inputs",[18,1064,1065],{},"parametrize",": One Test Function, Many Inputs",[23,1068,1069],{"language":25},[27,1070,1072],{"className":29,"code":1071,"language":25,"meta":31,"style":31},"import pytest\n\ndef is_palindrome(s):\n    cleaned = s.lower().replace(\" \", \"\")\n    return cleaned == cleaned[::-1]\n\n@pytest.mark.parametrize(\"input_str,expected\", [\n    (\"racecar\", True),\n    (\"hello\", False),\n    (\"\", True),                    # edge case: empty string is trivially a palindrome\n    (\"A man a plan a canal Panama\", True),\n    (\"a\", True),                     # edge case: single character\n])\ndef test_is_palindrome(input_str, expected):\n    assert is_palindrome(input_str) == expected\n",[18,1073,1074,1080,1084,1094,1114,1132,1136,1149,1165,1179,1195,1208,1225,1230,1240],{"__ignoreMap":31},[35,1075,1076,1078],{"class":37,"line":38},[35,1077,42],{"class":41},[35,1079,464],{"class":45},[35,1081,1082],{"class":37,"line":49},[35,1083,53],{"emptyLinePlaceholder":52},[35,1085,1086,1088,1091],{"class":37,"line":56},[35,1087,59],{"class":41},[35,1089,1090],{"class":62}," is_palindrome",[35,1092,1093],{"class":45},"(s):\n",[35,1095,1096,1099,1101,1104,1107,1109,1112],{"class":37,"line":69},[35,1097,1098],{"class":45},"    cleaned ",[35,1100,680],{"class":41},[35,1102,1103],{"class":45}," s.lower().replace(",[35,1105,1106],{"class":133},"\" \"",[35,1108,167],{"class":45},[35,1110,1111],{"class":133},"\"\"",[35,1113,137],{"class":45},[35,1115,1116,1118,1121,1123,1126,1128,1130],{"class":37,"line":84},[35,1117,72],{"class":41},[35,1119,1120],{"class":45}," cleaned ",[35,1122,403],{"class":41},[35,1124,1125],{"class":45}," cleaned[::",[35,1127,203],{"class":41},[35,1129,206],{"class":127},[35,1131,596],{"class":45},[35,1133,1134],{"class":37,"line":89},[35,1135,53],{"emptyLinePlaceholder":52},[35,1137,1138,1141,1143,1146],{"class":37,"line":112},[35,1139,1140],{"class":62},"@pytest.mark.parametrize",[35,1142,98],{"class":45},[35,1144,1145],{"class":133},"\"input_str,expected\"",[35,1147,1148],{"class":45},", [\n",[35,1150,1151,1154,1157,1159,1162],{"class":37,"line":124},[35,1152,1153],{"class":45},"    (",[35,1155,1156],{"class":133},"\"racecar\"",[35,1158,167],{"class":45},[35,1160,1161],{"class":127},"True",[35,1163,1164],{"class":45},"),\n",[35,1166,1167,1169,1172,1174,1177],{"class":37,"line":140},[35,1168,1153],{"class":45},[35,1170,1171],{"class":133},"\"hello\"",[35,1173,167],{"class":45},[35,1175,1176],{"class":127},"False",[35,1178,1164],{"class":45},[35,1180,1181,1183,1185,1187,1189,1192],{"class":37,"line":145},[35,1182,1153],{"class":45},[35,1184,1111],{"class":133},[35,1186,167],{"class":45},[35,1188,1161],{"class":127},[35,1190,1191],{"class":45},"),                    ",[35,1193,1194],{"class":608},"# edge case: empty string is trivially a palindrome\n",[35,1196,1197,1199,1202,1204,1206],{"class":37,"line":155},[35,1198,1153],{"class":45},[35,1200,1201],{"class":133},"\"A man a plan a canal Panama\"",[35,1203,167],{"class":45},[35,1205,1161],{"class":127},[35,1207,1164],{"class":45},[35,1209,1210,1212,1215,1217,1219,1222],{"class":37,"line":181},[35,1211,1153],{"class":45},[35,1213,1214],{"class":133},"\"a\"",[35,1216,167],{"class":45},[35,1218,1161],{"class":127},[35,1220,1221],{"class":45},"),                     ",[35,1223,1224],{"class":608},"# edge case: single character\n",[35,1226,1227],{"class":37,"line":186},[35,1228,1229],{"class":45},"])\n",[35,1231,1232,1234,1237],{"class":37,"line":196},[35,1233,59],{"class":41},[35,1235,1236],{"class":62}," test_is_palindrome",[35,1238,1239],{"class":45},"(input_str, expected):\n",[35,1241,1242,1244,1247,1249],{"class":37,"line":223},[35,1243,388],{"class":41},[35,1245,1246],{"class":45}," is_palindrome(input_str) ",[35,1248,403],{"class":41},[35,1250,1251],{"class":45}," expected\n",[323,1253,1254,1255,1257,1258,1260,1261,1264,1265,1267,1268,1271],{},"Without ",[18,1256,1065],{},", testing the same logic against multiple inputs means either five nearly-identical test functions (violating DRY) or one test function with five separate ",[18,1259,524],{}," statements — the latter stops at the ",[1045,1262,1263],{},"first"," failure, hiding whether the other four cases also fail. ",[18,1266,1065],{}," runs each input as its own independent test (",[18,1269,1270],{},"test_is_palindrome[racecar-True]",", etc.), so a failure on one input doesn't hide failures on the others, and the failing case is immediately visible by name in the test report.",[14,1273,1275],{"id":1274},"mocking-isolating-code-from-its-dependencies","Mocking: Isolating Code From Its Dependencies",[23,1277,1278],{"language":25},[27,1279,1281],{"className":29,"code":1280,"language":25,"meta":31,"style":31},"from unittest.mock import Mock, patch\nimport requests\n\ndef get_user_name(user_id):\n    response = requests.get(f\"https:\u002F\u002Fapi.example.com\u002Fusers\u002F{user_id}\")\n    return response.json()[\"name\"]\n\ndef test_get_user_name():\n    with patch(\"requests.get\") as mock_get:\n        mock_get.return_value.json.return_value = {\"name\": \"Ada\"}\n        result = get_user_name(1)\n        assert result == \"Ada\"\n        mock_get.assert_called_once_with(\"https:\u002F\u002Fapi.example.com\u002Fusers\u002F1\")\n",[18,1282,1283,1296,1303,1307,1317,1346,1357,1361,1370,1388,1405,1419,1431],{"__ignoreMap":31},[35,1284,1285,1288,1291,1293],{"class":37,"line":38},[35,1286,1287],{"class":41},"from",[35,1289,1290],{"class":45}," unittest.mock ",[35,1292,42],{"class":41},[35,1294,1295],{"class":45}," Mock, patch\n",[35,1297,1298,1300],{"class":37,"line":49},[35,1299,42],{"class":41},[35,1301,1302],{"class":45}," requests\n",[35,1304,1305],{"class":37,"line":56},[35,1306,53],{"emptyLinePlaceholder":52},[35,1308,1309,1311,1314],{"class":37,"line":69},[35,1310,59],{"class":41},[35,1312,1313],{"class":62}," get_user_name",[35,1315,1316],{"class":45},"(user_id):\n",[35,1318,1319,1322,1324,1327,1330,1333,1336,1339,1342,1344],{"class":37,"line":84},[35,1320,1321],{"class":45},"    response ",[35,1323,680],{"class":41},[35,1325,1326],{"class":45}," requests.get(",[35,1328,1329],{"class":41},"f",[35,1331,1332],{"class":133},"\"https:\u002F\u002Fapi.example.com\u002Fusers\u002F",[35,1334,1335],{"class":127},"{",[35,1337,1338],{"class":45},"user_id",[35,1340,1341],{"class":127},"}",[35,1343,664],{"class":133},[35,1345,137],{"class":45},[35,1347,1348,1350,1353,1355],{"class":37,"line":89},[35,1349,72],{"class":41},[35,1351,1352],{"class":45}," response.json()[",[35,1354,696],{"class":133},[35,1356,596],{"class":45},[35,1358,1359],{"class":37,"line":112},[35,1360,53],{"emptyLinePlaceholder":52},[35,1362,1363,1365,1368],{"class":37,"line":124},[35,1364,59],{"class":41},[35,1366,1367],{"class":62}," test_get_user_name",[35,1369,383],{"class":45},[35,1371,1372,1374,1377,1380,1382,1385],{"class":37,"line":140},[35,1373,469],{"class":41},[35,1375,1376],{"class":45}," patch(",[35,1378,1379],{"class":133},"\"requests.get\"",[35,1381,400],{"class":45},[35,1383,1384],{"class":41},"as",[35,1386,1387],{"class":45}," mock_get:\n",[35,1389,1390,1393,1395,1397,1399,1401,1403],{"class":37,"line":145},[35,1391,1392],{"class":45},"        mock_get.return_value.json.return_value ",[35,1394,680],{"class":41},[35,1396,1018],{"class":45},[35,1398,696],{"class":133},[35,1400,689],{"class":45},[35,1402,701],{"class":133},[35,1404,1029],{"class":45},[35,1406,1407,1410,1412,1415,1417],{"class":37,"line":155},[35,1408,1409],{"class":45},"        result ",[35,1411,680],{"class":41},[35,1413,1414],{"class":45}," get_user_name(",[35,1416,206],{"class":127},[35,1418,137],{"class":45},[35,1420,1421,1424,1427,1429],{"class":37,"line":181},[35,1422,1423],{"class":41},"        assert",[35,1425,1426],{"class":45}," result ",[35,1428,403],{"class":41},[35,1430,812],{"class":133},[35,1432,1433,1436,1439],{"class":37,"line":186},[35,1434,1435],{"class":45},"        mock_get.assert_called_once_with(",[35,1437,1438],{"class":133},"\"https:\u002F\u002Fapi.example.com\u002Fusers\u002F1\"",[35,1440,137],{"class":45},[323,1442,1443,1446,1447,1450,1451,1454,1455,1458,1459,1462,1463,1466],{},[18,1444,1445],{},"patch(\"requests.get\")"," temporarily replaces ",[18,1448,1449],{},"requests.get"," with a ",[18,1452,1453],{},"Mock"," object for the duration of the ",[18,1456,1457],{},"with"," block, restoring the real function afterward automatically — this is how tests avoid making real network calls (slow, flaky, dependent on external services) while still verifying the code under test calls its dependencies correctly. ",[18,1460,1461],{},"mock_get.assert_called_once_with(...)"," verifies not just the ",[1045,1464,1465],{},"return value"," handling but that the mocked function was invoked with the exact expected arguments.",[839,1468,1470,1471,1474,1475],{"id":1469},"the-critical-gotcha-patch-the-name-where-its-used-not-where-its-defined","The critical gotcha: patch the name where it's ",[1045,1472,1473],{},"used",", not where it's ",[1045,1476,1477],{},"defined",[23,1479,1480],{"language":25},[27,1481,1483],{"className":29,"code":1482,"language":25,"meta":31,"style":31},"# mymodule.py\nimport requests\n\ndef fetch():\n    return requests.get(\"https:\u002F\u002Fapi.example.com\").json()\n",[18,1484,1485,1490,1496,1500,1509],{"__ignoreMap":31},[35,1486,1487],{"class":37,"line":38},[35,1488,1489],{"class":608},"# mymodule.py\n",[35,1491,1492,1494],{"class":37,"line":49},[35,1493,42],{"class":41},[35,1495,1302],{"class":45},[35,1497,1498],{"class":37,"line":56},[35,1499,53],{"emptyLinePlaceholder":52},[35,1501,1502,1504,1507],{"class":37,"line":69},[35,1503,59],{"class":41},[35,1505,1506],{"class":62}," fetch",[35,1508,383],{"class":45},[35,1510,1511,1513,1515,1518],{"class":37,"line":84},[35,1512,72],{"class":41},[35,1514,1326],{"class":45},[35,1516,1517],{"class":133},"\"https:\u002F\u002Fapi.example.com\"",[35,1519,1520],{"class":45},").json()\n",[23,1522,1523],{"language":25},[27,1524,1526],{"className":29,"code":1525,"language":25,"meta":31,"style":31},"# WRONG — patches requests.get globally in the `requests` module's own namespace,\n# but if mymodule did `from requests import get`, this patch would silently miss it\nfrom unittest.mock import patch\nimport mymodule\n\ndef test_fetch_wrong():\n    with patch(\"requests.get\") as mock_get:      # works ONLY because mymodule uses `requests.get(...)`\n        mock_get.return_value.json.return_value = {\"ok\": True}\n        assert mymodule.fetch() == {\"ok\": True}\n\n# CORRECT, more robust form — patch the reference as seen from the CONSUMING module's namespace\ndef test_fetch_correct():\n    with patch(\"mymodule.requests.get\") as mock_get:\n        mock_get.return_value.json.return_value = {\"ok\": True}\n        assert mymodule.fetch() == {\"ok\": True}\n",[18,1527,1528,1533,1538,1549,1556,1560,1569,1587,1604,1623,1627,1632,1641,1656,1672],{"__ignoreMap":31},[35,1529,1530],{"class":37,"line":38},[35,1531,1532],{"class":608},"# WRONG — patches requests.get globally in the `requests` module's own namespace,\n",[35,1534,1535],{"class":37,"line":49},[35,1536,1537],{"class":608},"# but if mymodule did `from requests import get`, this patch would silently miss it\n",[35,1539,1540,1542,1544,1546],{"class":37,"line":56},[35,1541,1287],{"class":41},[35,1543,1290],{"class":45},[35,1545,42],{"class":41},[35,1547,1548],{"class":45}," patch\n",[35,1550,1551,1553],{"class":37,"line":69},[35,1552,42],{"class":41},[35,1554,1555],{"class":45}," mymodule\n",[35,1557,1558],{"class":37,"line":84},[35,1559,53],{"emptyLinePlaceholder":52},[35,1561,1562,1564,1567],{"class":37,"line":89},[35,1563,59],{"class":41},[35,1565,1566],{"class":62}," test_fetch_wrong",[35,1568,383],{"class":45},[35,1570,1571,1573,1575,1577,1579,1581,1584],{"class":37,"line":112},[35,1572,469],{"class":41},[35,1574,1376],{"class":45},[35,1576,1379],{"class":133},[35,1578,400],{"class":45},[35,1580,1384],{"class":41},[35,1582,1583],{"class":45}," mock_get:      ",[35,1585,1586],{"class":608},"# works ONLY because mymodule uses `requests.get(...)`\n",[35,1588,1589,1591,1593,1595,1598,1600,1602],{"class":37,"line":124},[35,1590,1392],{"class":45},[35,1592,680],{"class":41},[35,1594,1018],{"class":45},[35,1596,1597],{"class":133},"\"ok\"",[35,1599,689],{"class":45},[35,1601,1161],{"class":127},[35,1603,1029],{"class":45},[35,1605,1606,1608,1611,1613,1615,1617,1619,1621],{"class":37,"line":140},[35,1607,1423],{"class":41},[35,1609,1610],{"class":45}," mymodule.fetch() ",[35,1612,403],{"class":41},[35,1614,1018],{"class":45},[35,1616,1597],{"class":133},[35,1618,689],{"class":45},[35,1620,1161],{"class":127},[35,1622,1029],{"class":45},[35,1624,1625],{"class":37,"line":145},[35,1626,53],{"emptyLinePlaceholder":52},[35,1628,1629],{"class":37,"line":155},[35,1630,1631],{"class":608},"# CORRECT, more robust form — patch the reference as seen from the CONSUMING module's namespace\n",[35,1633,1634,1636,1639],{"class":37,"line":181},[35,1635,59],{"class":41},[35,1637,1638],{"class":62}," test_fetch_correct",[35,1640,383],{"class":45},[35,1642,1643,1645,1647,1650,1652,1654],{"class":37,"line":186},[35,1644,469],{"class":41},[35,1646,1376],{"class":45},[35,1648,1649],{"class":133},"\"mymodule.requests.get\"",[35,1651,400],{"class":45},[35,1653,1384],{"class":41},[35,1655,1387],{"class":45},[35,1657,1658,1660,1662,1664,1666,1668,1670],{"class":37,"line":196},[35,1659,1392],{"class":45},[35,1661,680],{"class":41},[35,1663,1018],{"class":45},[35,1665,1597],{"class":133},[35,1667,689],{"class":45},[35,1669,1161],{"class":127},[35,1671,1029],{"class":45},[35,1673,1674,1676,1678,1680,1682,1684,1686,1688],{"class":37,"line":223},[35,1675,1423],{"class":41},[35,1677,1610],{"class":45},[35,1679,403],{"class":41},[35,1681,1018],{"class":45},[35,1683,1597],{"class":133},[35,1685,689],{"class":45},[35,1687,1161],{"class":127},[35,1689,1029],{"class":45},[323,1691,1692,1694,1695,1698,1699,1702,1703,1706,1707,1710,1711,1714,1715,1717,1718,1720,1721,1724,1725,1728,1729,1731,1732,1734,1735,1738],{},[18,1693,1445],{}," and ",[18,1696,1697],{},"patch(\"mymodule.requests.get\")"," happen to behave the same here only because ",[18,1700,1701],{},"mymodule"," imported the ",[18,1704,1705],{},"requests"," ",[1045,1708,1709],{},"module"," (so ",[18,1712,1713],{},"mymodule.requests"," is the same object as the top-level ",[18,1716,1705],{}," module) — but if ",[18,1719,1701],{}," had instead written ",[18,1722,1723],{},"from requests import get",", only ",[18,1726,1727],{},"patch(\"mymodule.get\")"," would work, since ",[18,1730,1701],{}," would hold its own independent name binding to the original function, unaffected by patching ",[18,1733,1449],{}," elsewhere. ",[530,1736,1737],{},"The rule to memorize",": always patch the name as looked up from the module under test, not from where the object was originally defined.",[839,1740,1742,1744,1745,1748,1749],{"id":1741},"mock-vs-magicmock-and-side_effect",[18,1743,1453],{}," vs ",[18,1746,1747],{},"MagicMock",", and ",[18,1750,1751],{},"side_effect",[23,1753,1754],{"language":25},[27,1755,1757],{"className":29,"code":1756,"language":25,"meta":31,"style":31},"from unittest.mock import Mock, MagicMock\n\nplain_mock = Mock()\n# print(len(plain_mock))   # TypeError: object of type 'Mock' has no len() — dunder methods NOT auto-mocked\n\nmagic_mock = MagicMock()\nprint(len(magic_mock))       # 0 — MagicMock pre-configures common dunder methods automatically\n\nflaky_mock = Mock(side_effect=[ValueError(\"first call fails\"), \"success\"])\ntry:\n    flaky_mock()\nexcept ValueError as e:\n    print(f\"caught: {e}\")\nprint(flaky_mock())   # \"success\" — side_effect with a list yields each value\u002Fexception in sequence\n",[18,1758,1759,1770,1774,1784,1789,1793,1803,1819,1823,1855,1862,1867,1881,1903],{"__ignoreMap":31},[35,1760,1761,1763,1765,1767],{"class":37,"line":38},[35,1762,1287],{"class":41},[35,1764,1290],{"class":45},[35,1766,42],{"class":41},[35,1768,1769],{"class":45}," Mock, MagicMock\n",[35,1771,1772],{"class":37,"line":49},[35,1773,53],{"emptyLinePlaceholder":52},[35,1775,1776,1779,1781],{"class":37,"line":56},[35,1777,1778],{"class":45},"plain_mock ",[35,1780,680],{"class":41},[35,1782,1783],{"class":45}," Mock()\n",[35,1785,1786],{"class":37,"line":69},[35,1787,1788],{"class":608},"# print(len(plain_mock))   # TypeError: object of type 'Mock' has no len() — dunder methods NOT auto-mocked\n",[35,1790,1791],{"class":37,"line":84},[35,1792,53],{"emptyLinePlaceholder":52},[35,1794,1795,1798,1800],{"class":37,"line":89},[35,1796,1797],{"class":45},"magic_mock ",[35,1799,680],{"class":41},[35,1801,1802],{"class":45}," MagicMock()\n",[35,1804,1805,1808,1810,1813,1816],{"class":37,"line":112},[35,1806,1807],{"class":127},"print",[35,1809,98],{"class":45},[35,1811,1812],{"class":127},"len",[35,1814,1815],{"class":45},"(magic_mock))       ",[35,1817,1818],{"class":608},"# 0 — MagicMock pre-configures common dunder methods automatically\n",[35,1820,1821],{"class":37,"line":124},[35,1822,53],{"emptyLinePlaceholder":52},[35,1824,1825,1828,1830,1833,1835,1837,1840,1843,1845,1848,1850,1853],{"class":37,"line":140},[35,1826,1827],{"class":45},"flaky_mock ",[35,1829,680],{"class":41},[35,1831,1832],{"class":45}," Mock(",[35,1834,1751],{"class":868},[35,1836,680],{"class":41},[35,1838,1839],{"class":45},"[",[35,1841,1842],{"class":127},"ValueError",[35,1844,98],{"class":45},[35,1846,1847],{"class":133},"\"first call fails\"",[35,1849,173],{"class":45},[35,1851,1852],{"class":133},"\"success\"",[35,1854,1229],{"class":45},[35,1856,1857,1860],{"class":37,"line":145},[35,1858,1859],{"class":41},"try",[35,1861,290],{"class":45},[35,1863,1864],{"class":37,"line":155},[35,1865,1866],{"class":45},"    flaky_mock()\n",[35,1868,1869,1872,1875,1878],{"class":37,"line":181},[35,1870,1871],{"class":41},"except",[35,1873,1874],{"class":127}," ValueError",[35,1876,1877],{"class":41}," as",[35,1879,1880],{"class":45}," e:\n",[35,1882,1883,1885,1887,1889,1892,1894,1897,1899,1901],{"class":37,"line":186},[35,1884,659],{"class":127},[35,1886,98],{"class":45},[35,1888,1329],{"class":41},[35,1890,1891],{"class":133},"\"caught: ",[35,1893,1335],{"class":127},[35,1895,1896],{"class":45},"e",[35,1898,1341],{"class":127},[35,1900,664],{"class":133},[35,1902,137],{"class":45},[35,1904,1905,1907,1910],{"class":37,"line":196},[35,1906,1807],{"class":127},[35,1908,1909],{"class":45},"(flaky_mock())   ",[35,1911,1912],{"class":608},"# \"success\" — side_effect with a list yields each value\u002Fexception in sequence\n",[323,1914,1915,1917,1918,1921],{},[18,1916,1751],{}," is more flexible than ",[18,1919,1920],{},"return_value",": assigning a list makes successive calls return (or raise, for exception instances) each item in order — the standard way to test retry logic, where the first N calls should fail and a later one should succeed.",[14,1923,1925],{"id":1924},"coverage-measuring-whats-actually-tested","Coverage: Measuring What's Actually Tested",[23,1927,1928],{"language":299},[27,1929,1931],{"className":302,"code":1930,"language":299,"meta":31,"style":31},"pip install pytest-cov\npytest --cov=mypackage --cov-report=term-missing\n\n# Name                 Stmts   Miss  Cover   Missing\n# ----------------------------------------------------\n# mypackage\u002Futils.py       42      3    93%   57-59\n# mypackage\u002Fmodels.py      88     20    77%   102-121\n",[18,1932,1933,1942,1952,1956,1961,1966,1971],{"__ignoreMap":31},[35,1934,1935,1937,1939],{"class":37,"line":38},[35,1936,501],{"class":62},[35,1938,504],{"class":133},[35,1940,1941],{"class":133}," pytest-cov\n",[35,1943,1944,1946,1949],{"class":37,"line":49},[35,1945,344],{"class":62},[35,1947,1948],{"class":127}," --cov=mypackage",[35,1950,1951],{"class":127}," --cov-report=term-missing\n",[35,1953,1954],{"class":37,"line":56},[35,1955,53],{"emptyLinePlaceholder":52},[35,1957,1958],{"class":37,"line":69},[35,1959,1960],{"class":608},"# Name                 Stmts   Miss  Cover   Missing\n",[35,1962,1963],{"class":37,"line":84},[35,1964,1965],{"class":608},"# ----------------------------------------------------\n",[35,1967,1968],{"class":37,"line":89},[35,1969,1970],{"class":608},"# mypackage\u002Futils.py       42      3    93%   57-59\n",[35,1972,1973],{"class":37,"line":112},[35,1974,1975],{"class":608},"# mypackage\u002Fmodels.py      88     20    77%   102-121\n",[323,1977,1978,1979,1982,1983,1986,1987,1990,1991,1039,1994,1997],{},"Coverage reports which lines executed during the test run — the ",[18,1980,1981],{},"Missing"," column pinpoints exact line numbers never hit by any test, a direct to-do list for gaps. ",[530,1984,1985],{},"The critical caveat",": 100% coverage means every line ",[1045,1988,1989],{},"executed",", not that every ",[1045,1992,1993],{},"behavior",[1045,1995,1996],{},"edge case"," was verified — a test can execute a line without meaningfully asserting on its output, producing high coverage numbers that mask weak tests. Coverage is a lower bound on testing quality, not a target to chase for its own sake.",[23,1999,2000],{"language":25},[27,2001,2003],{"className":29,"code":2002,"language":25,"meta":31,"style":31},"def divide(a, b):\n    return a \u002F b\n\ndef test_divide():\n    divide(10, 2)      # executes the line — counts toward \"100% coverage\" — but asserts NOTHING!\n",[18,2004,2005,2014,2024,2028,2037],{"__ignoreMap":31},[35,2006,2007,2009,2012],{"class":37,"line":38},[35,2008,59],{"class":41},[35,2010,2011],{"class":62}," divide",[35,2013,66],{"class":45},[35,2015,2016,2018,2020,2022],{"class":37,"line":49},[35,2017,72],{"class":41},[35,2019,75],{"class":45},[35,2021,826],{"class":41},[35,2023,81],{"class":45},[35,2025,2026],{"class":37,"line":56},[35,2027,53],{"emptyLinePlaceholder":52},[35,2029,2030,2032,2035],{"class":37,"line":69},[35,2031,59],{"class":41},[35,2033,2034],{"class":62}," test_divide",[35,2036,383],{"class":45},[35,2038,2039,2042,2045,2047,2049,2052],{"class":37,"line":84},[35,2040,2041],{"class":45},"    divide(",[35,2043,2044],{"class":127},"10",[35,2046,167],{"class":45},[35,2048,164],{"class":127},[35,2050,2051],{"class":45},")      ",[35,2053,2054],{"class":608},"# executes the line — counts toward \"100% coverage\" — but asserts NOTHING!\n",[323,2056,2057,2058,2061,2062,2064,2065,2067,2068,103],{},"This test achieves full line coverage of ",[18,2059,2060],{},"divide"," while verifying absolutely nothing about its correctness — it wouldn't fail even if ",[18,2063,2060],{}," were rewritten to always return ",[18,2066,800],{},". A real test needs an assertion tied to the expected behavior: ",[18,2069,2070],{},"assert divide(10, 2) == 5",[14,2072,2074],{"id":2073},"structuring-a-real-test-suite","Structuring a Real Test Suite",[23,2076,2077],{"language":299},[27,2078,2080],{"className":302,"code":2079,"language":299,"meta":31,"style":31},"myproject\u002F\n├── src\u002F\n│   └── mypackage\u002F\n│       ├── __init__.py\n│       └── orders.py\n├── tests\u002F\n│   ├── conftest.py           # shared fixtures, auto-discovered by pytest — no import needed\n│   ├── test_orders.py\n│   └── test_integration.py\n└── pyproject.toml\n",[18,2081,2082,2087,2095,2106,2116,2126,2133,2146,2155,2164],{"__ignoreMap":31},[35,2083,2084],{"class":37,"line":38},[35,2085,2086],{"class":62},"myproject\u002F\n",[35,2088,2089,2092],{"class":37,"line":49},[35,2090,2091],{"class":62},"├──",[35,2093,2094],{"class":133}," src\u002F\n",[35,2096,2097,2100,2103],{"class":37,"line":56},[35,2098,2099],{"class":62},"│",[35,2101,2102],{"class":133},"   └──",[35,2104,2105],{"class":133}," mypackage\u002F\n",[35,2107,2108,2110,2113],{"class":37,"line":69},[35,2109,2099],{"class":62},[35,2111,2112],{"class":133},"       ├──",[35,2114,2115],{"class":133}," __init__.py\n",[35,2117,2118,2120,2123],{"class":37,"line":84},[35,2119,2099],{"class":62},[35,2121,2122],{"class":133},"       └──",[35,2124,2125],{"class":133}," orders.py\n",[35,2127,2128,2130],{"class":37,"line":89},[35,2129,2091],{"class":62},[35,2131,2132],{"class":133}," tests\u002F\n",[35,2134,2135,2137,2140,2143],{"class":37,"line":112},[35,2136,2099],{"class":62},[35,2138,2139],{"class":133},"   ├──",[35,2141,2142],{"class":133}," conftest.py",[35,2144,2145],{"class":608},"           # shared fixtures, auto-discovered by pytest — no import needed\n",[35,2147,2148,2150,2152],{"class":37,"line":124},[35,2149,2099],{"class":62},[35,2151,2139],{"class":133},[35,2153,2154],{"class":133}," test_orders.py\n",[35,2156,2157,2159,2161],{"class":37,"line":140},[35,2158,2099],{"class":62},[35,2160,2102],{"class":133},[35,2162,2163],{"class":133}," test_integration.py\n",[35,2165,2166,2169],{"class":37,"line":145},[35,2167,2168],{"class":62},"└──",[35,2170,2171],{"class":133}," pyproject.toml\n",[23,2173,2174],{"language":25},[27,2175,2177],{"className":29,"code":2176,"language":25,"meta":31,"style":31},"# tests\u002Fconftest.py — fixtures here are automatically available to every test file in this directory\nimport pytest\n\n@pytest.fixture\ndef sample_order():\n    return {\"id\": 1, \"items\": [\"book\", \"pen\"], \"total\": 25.50}\n",[18,2178,2179,2184,2190,2194,2198,2207],{"__ignoreMap":31},[35,2180,2181],{"class":37,"line":38},[35,2182,2183],{"class":608},"# tests\u002Fconftest.py — fixtures here are automatically available to every test file in this directory\n",[35,2185,2186,2188],{"class":37,"line":49},[35,2187,42],{"class":41},[35,2189,464],{"class":45},[35,2191,2192],{"class":37,"line":56},[35,2193,53],{"emptyLinePlaceholder":52},[35,2195,2196],{"class":37,"line":69},[35,2197,645],{"class":62},[35,2199,2200,2202,2205],{"class":37,"line":84},[35,2201,59],{"class":41},[35,2203,2204],{"class":62}," sample_order",[35,2206,383],{"class":45},[35,2208,2209,2211,2213,2215,2217,2219,2221,2224,2227,2230,2232,2235,2238,2241,2243,2246],{"class":37,"line":89},[35,2210,72],{"class":41},[35,2212,1018],{"class":45},[35,2214,686],{"class":133},[35,2216,689],{"class":45},[35,2218,206],{"class":127},[35,2220,167],{"class":45},[35,2222,2223],{"class":133},"\"items\"",[35,2225,2226],{"class":45},": [",[35,2228,2229],{"class":133},"\"book\"",[35,2231,167],{"class":45},[35,2233,2234],{"class":133},"\"pen\"",[35,2236,2237],{"class":45},"], ",[35,2239,2240],{"class":133},"\"total\"",[35,2242,689],{"class":45},[35,2244,2245],{"class":127},"25.50",[35,2247,1029],{"class":45},[323,2249,2250,2253,2254,2256,2257,2259],{},[18,2251,2252],{},"conftest.py"," is ",[18,2255,344],{},"'s convention for fixtures shared across multiple test files — no explicit import is needed; ",[18,2258,344],{}," discovers and injects it automatically into any test in the same directory (or subdirectories), based purely on the fixture's function name matching a test parameter name.",[14,2261,2263],{"id":2262},"tips-tricks","💡 Tips & Tricks",[2265,2266,2267,2285,2302,2311,2321],"ul",{},[2268,2269,2270,2273,2274,2277,2278,826,2281,2284],"li",{},[530,2271,2272],{},"Idiom",": name tests descriptively as full sentences (",[18,2275,2276],{},"test_withdraw_raises_when_balance_insufficient","), not ",[18,2279,2280],{},"test_1",[18,2282,2283],{},"test_withdraw"," — a failing test's name should communicate what broke without needing to open the file.",[2268,2286,2287,689,2290,2293,2294,2297,2298,2301],{},[530,2288,2289],{},"Debug",[18,2291,2292],{},"pytest -x"," stops at the first failure (useful when iterating on a fix); ",[18,2295,2296],{},"pytest --lf"," reruns only the tests that failed last time; ",[18,2299,2300],{},"pytest -k \"palindrome\""," runs only tests whose name matches a substring — all far faster than rerunning a whole suite while debugging.",[2268,2303,2304,2306,2307,2310],{},[530,2305,2272],{},": use ",[18,2308,2309],{},"pytest.mark.parametrize"," the moment you find yourself copy-pasting a test function and changing only the input\u002Fexpected values — that repetition is exactly what parametrization exists to eliminate.",[2268,2312,2313,2316,2317,2320],{},[530,2314,2315],{},"Safety",": never let a test suite depend on execution order or on state left behind by another test — ",[18,2318,2319],{},"pytest-randomly"," (a plugin that shuffles test order every run) is a good way to surface hidden order-dependencies before they cause a flaky CI failure.",[2268,2322,2323,689,2326,2328,2329,2332,2333,2335],{},[530,2324,2325],{},"Performance",[18,2327,1051],{}," (",[18,2330,2331],{},"pytest -n auto",") runs tests in parallel across CPU cores — valuable for large suites, but it makes order-dependent and shared-mutable-fixture bugs (see ",[18,2334,1038],{}," above) surface immediately, which is a feature, not a bug, of running it.",[14,2337,2339],{"id":2338},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[2265,2341,2342,2348,2363,2393,2399],{},[2268,2343,2344,2347],{},[530,2345,2346],{},"A test that executes a line without asserting on its result contributes to coverage percentage while verifying nothing"," — high coverage numbers can hide a suite that would not catch a real regression; always tie an assertion to the specific behavior being tested.",[2268,2349,2350,2356,2357,2359,2360,2362],{},[530,2351,2352,2355],{},[18,2353,2354],{},"patch()"," must target the name as imported\u002Fused in the module under test, not where the original object is defined"," — ",[18,2358,1723],{}," in the tested module means ",[18,2361,1445],{}," silently does nothing, while the test still appears to \"pass\" because the code path making a real network call may coincidentally succeed or the test never noticed the mock wasn't applied.",[2268,2364,2365,2382,2383,2386,2387,2389,2390,2392],{},[530,2366,2367,2370,2371,167,2374,2377,2378,2381],{},[18,2368,2369],{},"Mock()"," does not auto-mock dunder methods (",[18,2372,2373],{},"__len__",[18,2375,2376],{},"__iter__",", etc.) — only ",[18,2379,2380],{},"MagicMock()"," does"," — calling ",[18,2384,2385],{},"len()"," on a plain ",[18,2388,1453],{}," raises ",[18,2391,250],{},", a surprising failure for code expecting a duck-typed collection-like object.",[2268,2394,2395,2398],{},[530,2396,2397],{},"Module- or session-scoped fixtures that yield a mutable object are shared across every test in that scope"," — one test mutating the fixture's value can silently affect a later test's starting state, producing failures that depend on test execution order and vanish when that single test is run in isolation.",[2268,2400,2401,2410,2411,2414],{},[530,2402,2403,826,2406,2409],{},[18,2404,2405],{},"assertRaises",[18,2407,2408],{},"pytest.raises"," blocks that are too broad (wrapping many lines) can pass even when the exception was raised by the wrong line"," — narrow the ",[18,2412,2413],{},"with pytest.raises(...)"," block to just the call expected to raise, so a test doesn't accidentally \"pass\" due to an unrelated bug earlier in the block.",[14,2416,2418],{"id":2417},"spot-the-bug","🧠 Spot the Bug",[323,2420,2421],{},"A test suite mocks an external payment API to verify that a failed charge is retried once before giving up. The test passes, but the retry logic has a real bug that reaches production. Find the bug in the test.",[23,2423,2424],{"language":25},[27,2425,2427],{"className":29,"code":2426,"language":25,"meta":31,"style":31},"from unittest.mock import patch\n\ndef charge_with_retry(amount):\n    for attempt in range(2):\n        try:\n            return payment_api.charge(amount)\n        except PaymentError:\n            continue\n    raise PaymentError(\"all retries exhausted\")\n\ndef test_retry_succeeds_on_second_attempt():\n    with patch(\"mymodule.payment_api.charge\") as mock_charge:\n        mock_charge.return_value = {\"status\": \"success\"}\n        result = charge_with_retry(100)\n        assert result == {\"status\": \"success\"}\n",[18,2428,2429,2439,2443,2453,2473,2480,2488,2496,2501,2514,2518,2527,2543,2561,2575],{"__ignoreMap":31},[35,2430,2431,2433,2435,2437],{"class":37,"line":38},[35,2432,1287],{"class":41},[35,2434,1290],{"class":45},[35,2436,42],{"class":41},[35,2438,1548],{"class":45},[35,2440,2441],{"class":37,"line":49},[35,2442,53],{"emptyLinePlaceholder":52},[35,2444,2445,2447,2450],{"class":37,"line":56},[35,2446,59],{"class":41},[35,2448,2449],{"class":62}," charge_with_retry",[35,2451,2452],{"class":45},"(amount):\n",[35,2454,2455,2458,2461,2464,2467,2469,2471],{"class":37,"line":69},[35,2456,2457],{"class":41},"    for",[35,2459,2460],{"class":45}," attempt ",[35,2462,2463],{"class":41},"in",[35,2465,2466],{"class":127}," range",[35,2468,98],{"class":45},[35,2470,164],{"class":127},[35,2472,109],{"class":45},[35,2474,2475,2478],{"class":37,"line":84},[35,2476,2477],{"class":41},"        try",[35,2479,290],{"class":45},[35,2481,2482,2485],{"class":37,"line":89},[35,2483,2484],{"class":41},"            return",[35,2486,2487],{"class":45}," payment_api.charge(amount)\n",[35,2489,2490,2493],{"class":37,"line":112},[35,2491,2492],{"class":41},"        except",[35,2494,2495],{"class":45}," PaymentError:\n",[35,2497,2498],{"class":37,"line":124},[35,2499,2500],{"class":41},"            continue\n",[35,2502,2503,2506,2509,2512],{"class":37,"line":140},[35,2504,2505],{"class":41},"    raise",[35,2507,2508],{"class":45}," PaymentError(",[35,2510,2511],{"class":133},"\"all retries exhausted\"",[35,2513,137],{"class":45},[35,2515,2516],{"class":37,"line":145},[35,2517,53],{"emptyLinePlaceholder":52},[35,2519,2520,2522,2525],{"class":37,"line":155},[35,2521,59],{"class":41},[35,2523,2524],{"class":62}," test_retry_succeeds_on_second_attempt",[35,2526,383],{"class":45},[35,2528,2529,2531,2533,2536,2538,2540],{"class":37,"line":181},[35,2530,469],{"class":41},[35,2532,1376],{"class":45},[35,2534,2535],{"class":133},"\"mymodule.payment_api.charge\"",[35,2537,400],{"class":45},[35,2539,1384],{"class":41},[35,2541,2542],{"class":45}," mock_charge:\n",[35,2544,2545,2548,2550,2552,2555,2557,2559],{"class":37,"line":186},[35,2546,2547],{"class":45},"        mock_charge.return_value ",[35,2549,680],{"class":41},[35,2551,1018],{"class":45},[35,2553,2554],{"class":133},"\"status\"",[35,2556,689],{"class":45},[35,2558,1852],{"class":133},[35,2560,1029],{"class":45},[35,2562,2563,2565,2567,2570,2573],{"class":37,"line":196},[35,2564,1409],{"class":45},[35,2566,680],{"class":41},[35,2568,2569],{"class":45}," charge_with_retry(",[35,2571,2572],{"class":127},"100",[35,2574,137],{"class":45},[35,2576,2577,2579,2581,2583,2585,2587,2589,2591],{"class":37,"line":223},[35,2578,1423],{"class":41},[35,2580,1426],{"class":45},[35,2582,403],{"class":41},[35,2584,1018],{"class":45},[35,2586,2554],{"class":133},[35,2588,689],{"class":45},[35,2590,1852],{"class":133},[35,2592,1029],{"class":45},[2594,2595,2596,2600,2639,2645,2757],"details",{},[2597,2598,2599],"summary",{},"Answer",[323,2601,2602,2603,2606,2607,2610,2611,2614,2615,2618,2619,2622,2623,2626,2627,2630,2631,2634,2635,2638],{},"The test only configures ",[18,2604,2605],{},"mock_charge.return_value"," — a single fixed return value used for ",[1045,2608,2609],{},"every"," call — so it never actually exercises the retry path at all. ",[18,2612,2613],{},"payment_api.charge"," succeeds on the very first attempt in this test, meaning the ",[18,2616,2617],{},"for attempt in range(2)"," loop, the ",[18,2620,2621],{},"except PaymentError"," branch, and the \"retry after failure\" behavior are completely untested, despite the test's name (",[18,2624,2625],{},"test_retry_succeeds_on_second_attempt",") claiming otherwise. A real bug — e.g., ",[18,2628,2629],{},"range(2)"," accidentally being ",[18,2632,2633],{},"range(1)"," (no retry at all), or the loop not actually calling ",[18,2636,2637],{},"charge"," again on retry — would pass this test just as easily as correct code, because the test never forces a first-attempt failure to observe whether a second attempt happens.",[323,2640,2641,2642,2644],{},"The fix uses ",[18,2643,1751],{}," with a list to simulate an actual failure followed by success, which genuinely exercises the retry path:",[23,2646,2647],{"language":25},[27,2648,2650],{"className":29,"code":2649,"language":25,"meta":31,"style":31},"from unittest.mock import patch\n\ndef test_retry_succeeds_on_second_attempt():\n    with patch(\"mymodule.payment_api.charge\") as mock_charge:\n        mock_charge.side_effect = [PaymentError(\"declined\"), {\"status\": \"success\"}]\n        result = charge_with_retry(100)\n        assert result == {\"status\": \"success\"}\n        assert mock_charge.call_count == 2   # explicitly verifies a retry actually happened\n",[18,2651,2652,2662,2666,2674,2688,2712,2724,2742],{"__ignoreMap":31},[35,2653,2654,2656,2658,2660],{"class":37,"line":38},[35,2655,1287],{"class":41},[35,2657,1290],{"class":45},[35,2659,42],{"class":41},[35,2661,1548],{"class":45},[35,2663,2664],{"class":37,"line":49},[35,2665,53],{"emptyLinePlaceholder":52},[35,2667,2668,2670,2672],{"class":37,"line":56},[35,2669,59],{"class":41},[35,2671,2524],{"class":62},[35,2673,383],{"class":45},[35,2675,2676,2678,2680,2682,2684,2686],{"class":37,"line":69},[35,2677,469],{"class":41},[35,2679,1376],{"class":45},[35,2681,2535],{"class":133},[35,2683,400],{"class":45},[35,2685,1384],{"class":41},[35,2687,2542],{"class":45},[35,2689,2690,2693,2695,2698,2701,2704,2706,2708,2710],{"class":37,"line":84},[35,2691,2692],{"class":45},"        mock_charge.side_effect ",[35,2694,680],{"class":41},[35,2696,2697],{"class":45}," [PaymentError(",[35,2699,2700],{"class":133},"\"declined\"",[35,2702,2703],{"class":45},"), {",[35,2705,2554],{"class":133},[35,2707,689],{"class":45},[35,2709,1852],{"class":133},[35,2711,722],{"class":45},[35,2713,2714,2716,2718,2720,2722],{"class":37,"line":89},[35,2715,1409],{"class":45},[35,2717,680],{"class":41},[35,2719,2569],{"class":45},[35,2721,2572],{"class":127},[35,2723,137],{"class":45},[35,2725,2726,2728,2730,2732,2734,2736,2738,2740],{"class":37,"line":112},[35,2727,1423],{"class":41},[35,2729,1426],{"class":45},[35,2731,403],{"class":41},[35,2733,1018],{"class":45},[35,2735,2554],{"class":133},[35,2737,689],{"class":45},[35,2739,1852],{"class":133},[35,2741,1029],{"class":45},[35,2743,2744,2746,2749,2751,2754],{"class":37,"line":124},[35,2745,1423],{"class":41},[35,2747,2748],{"class":45}," mock_charge.call_count ",[35,2750,403],{"class":41},[35,2752,2753],{"class":127}," 2",[35,2755,2756],{"class":608},"   # explicitly verifies a retry actually happened\n",[323,2758,2759,2762,2763,2765,2766,2768,2769,826,2772,2775,2776,1039,2779,2782],{},[530,2760,2761],{},"The lesson",": a mock configured with a single ",[18,2764,1920],{}," cannot verify branching or retry logic that depends on different outcomes across multiple calls — use ",[18,2767,1751],{}," with a sequence, and assert on ",[18,2770,2771],{},"call_count",[18,2773,2774],{},"call_args",", whenever the behavior under test depends on ",[1045,2777,2778],{},"how many times",[1045,2780,2781],{},"with what"," a dependency was called, not just the final return value.",[14,2784,2786],{"id":2785},"key-takeaways","Key Takeaways",[2265,2788,2789,2803,2812,2820,2827,2839],{},[2268,2790,2791,2793,2794,2796,2797,2799,2800,2802],{},[18,2792,344],{}," is the ecosystem-standard framework — plain ",[18,2795,524],{}," statements with rich failure diffs, fixtures, and parametrization that ",[18,2798,20],{}," has no equivalent for, while still being able to run ",[18,2801,20],{},"-style tests unchanged.",[2268,2804,2805,2806,2808,2809,2811],{},"Fixtures use ",[18,2807,821],{}," to split setup (before) from teardown (after), and are injected by matching a test function's parameter name — default to ",[18,2810,1058],{}," unless setup is genuinely expensive and safe to share.",[2268,2813,2814,2816,2817,2819],{},[18,2815,2309],{}," runs one test function against many inputs as independent test cases, so one failing case doesn't hide others the way a single test function with multiple bare ",[18,2818,524],{}," statements would.",[2268,2821,2822,2823,2826],{},"Always patch the name as looked up from the module under test (",[18,2824,2825],{},"patch(\"mymodule.dependency\")","), not from where it was originally defined — this is the single most common mocking mistake.",[2268,2828,2829,2831,2832,2834,2835,1039,2837,103],{},[18,2830,1453],{}," does not auto-support dunder methods; use ",[18,2833,1747],{}," when the code under test relies on protocol methods like ",[18,2836,2373],{},[18,2838,2376],{},[2268,2840,2841,2842,2844],{},"Coverage percentage measures executed lines, not verified behavior — a test can achieve full coverage while asserting nothing meaningful; use ",[18,2843,1751],{}," and explicit call assertions to genuinely exercise branching and retry logic.",[2846,2847,2848],"style",{},"html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}",{"title":31,"searchDepth":49,"depth":49,"links":2850},[2851,2853,2855,2858,2860,2866,2867,2868,2869,2870,2871],{"id":16,"depth":49,"text":2852},"unittest: The Standard Library's Built-In Framework",{"id":341,"depth":49,"text":2854},"pytest: The De Facto Standard in Practice",{"id":622,"depth":49,"text":623,"children":2856},[2857],{"id":841,"depth":56,"text":842},{"id":1062,"depth":49,"text":2859},"parametrize: One Test Function, Many Inputs",{"id":1274,"depth":49,"text":1275,"children":2861},[2862,2864],{"id":1469,"depth":56,"text":2863},"The critical gotcha: patch the name where it's used, not where it's defined",{"id":1741,"depth":56,"text":2865},"Mock vs MagicMock, and side_effect",{"id":1924,"depth":49,"text":1925},{"id":2073,"depth":49,"text":2074},{"id":2262,"depth":49,"text":2263},{"id":2338,"depth":49,"text":2339},{"id":2417,"depth":49,"text":2418},{"id":2785,"depth":49,"text":2786},"md",{},"\u002Fpython\u002F24-testing",{"title":5,"description":31},"python\u002F24-testing","-2xBcfTivnKYv7AyjdnEU9HkA9dccQzRANV6gaskc7Y",1789924651810]