[{"data":1,"prerenderedAt":964},["ShallowReactive",2],{"blog-\u002Fblog\u002Fpython-pytest-explained":3},{"id":4,"title":5,"body":6,"description":950,"difficulty":951,"extension":952,"framework":953,"frameworkSlug":58,"meta":954,"navigation":106,"order":66,"path":955,"qaPath":956,"seo":957,"stem":958,"subtopic":959,"topic":960,"topicSlug":961,"updated":962,"__hash__":963},"blog\u002Fblog\u002Fpython-pytest-explained.md","Python pytest Explained — assert, Fixtures, Parametrize, and Testing Exceptions",{"type":7,"value":8,"toc":940},"minimark",[9,14,35,39,53,171,206,213,217,231,345,356,360,371,429,452,456,462,566,574,578,585,737,744,748,751,839,883,887,936],[10,11,13],"h2",{"id":12},"python-pytest-explained","Python pytest, explained",[15,16,17,21,22,25,26,29,30,34],"p",{},[18,19,20],"code",{},"pytest"," is the de facto standard for testing Python. It wins over the stdlib ",[18,23,24],{},"unittest"," by\nbeing far less boilerplate: plain ",[18,27,28],{},"assert"," statements, function-based tests, and a powerful\n",[31,32,33],"strong",{},"fixture"," system. Here are the essentials that cover the vast majority of real test suites.",[10,36,38],{"id":37},"tests-are-just-functions-with-assert","Tests are just functions with assert",[15,40,41,42,45,46,48,49,52],{},"A pytest test is a function named ",[18,43,44],{},"test_*"," that uses a bare ",[18,47,28],{},". pytest rewrites\nassertions to show ",[31,50,51],{},"exactly"," what went wrong — no special assert methods needed.",[54,55,60],"pre",{"className":56,"code":57,"language":58,"meta":59,"style":59},"language-python shiki shiki-themes github-light github-dark","# test_math.py\ndef add(a, b):\n    return a + b\n\ndef test_add():\n    assert add(2, 3) == 5\n    assert add(-1, 1) == 0\n","python","",[18,61,62,71,86,101,108,119,147],{"__ignoreMap":59},[63,64,67],"span",{"class":65,"line":66},"line",1,[63,68,70],{"class":69},"sJ8bj","# test_math.py\n",[63,72,74,78,82],{"class":65,"line":73},2,[63,75,77],{"class":76},"szBVR","def",[63,79,81],{"class":80},"sScJk"," add",[63,83,85],{"class":84},"sVt8B","(a, b):\n",[63,87,89,92,95,98],{"class":65,"line":88},3,[63,90,91],{"class":76},"    return",[63,93,94],{"class":84}," a ",[63,96,97],{"class":76},"+",[63,99,100],{"class":84}," b\n",[63,102,104],{"class":65,"line":103},4,[63,105,107],{"emptyLinePlaceholder":106},true,"\n",[63,109,111,113,116],{"class":65,"line":110},5,[63,112,77],{"class":76},[63,114,115],{"class":80}," test_add",[63,117,118],{"class":84},"():\n",[63,120,122,125,128,132,135,138,141,144],{"class":65,"line":121},6,[63,123,124],{"class":76},"    assert",[63,126,127],{"class":84}," add(",[63,129,131],{"class":130},"sj4cs","2",[63,133,134],{"class":84},", ",[63,136,137],{"class":130},"3",[63,139,140],{"class":84},") ",[63,142,143],{"class":76},"==",[63,145,146],{"class":130}," 5\n",[63,148,150,152,154,157,160,162,164,166,168],{"class":65,"line":149},7,[63,151,124],{"class":76},[63,153,127],{"class":84},[63,155,156],{"class":76},"-",[63,158,159],{"class":130},"1",[63,161,134],{"class":84},[63,163,159],{"class":130},[63,165,140],{"class":84},[63,167,143],{"class":76},[63,169,170],{"class":130}," 0\n",[54,172,176],{"className":173,"code":174,"language":175,"meta":59,"style":59},"language-bash shiki shiki-themes github-light github-dark","pytest                 # auto-discovers test_*.py and test_* functions\npytest -v              # verbose, one line per test\npytest test_math.py::test_add   # run one test\n","bash",[18,177,178,185,195],{"__ignoreMap":59},[63,179,180,182],{"class":65,"line":66},[63,181,20],{"class":80},[63,183,184],{"class":69},"                 # auto-discovers test_*.py and test_* functions\n",[63,186,187,189,192],{"class":65,"line":73},[63,188,20],{"class":80},[63,190,191],{"class":130}," -v",[63,193,194],{"class":69},"              # verbose, one line per test\n",[63,196,197,199,203],{"class":65,"line":88},[63,198,20],{"class":80},[63,200,202],{"class":201},"sZZnC"," test_math.py::test_add",[63,204,205],{"class":69},"   # run one test\n",[15,207,208,209,212],{},"On failure pytest prints the actual values (",[18,210,211],{},"assert 6 == 5","), so you rarely need a custom\nmessage.",[10,214,216],{"id":215},"fixtures-for-setup-and-teardown","Fixtures for setup and teardown",[15,218,219,220,222,223,226,227,230],{},"A ",[31,221,33],{}," provides reusable setup. Declare it with ",[18,224,225],{},"@pytest.fixture"," and request it by\nnaming it as a test argument — pytest injects the return value. Anything after ",[18,228,229],{},"yield"," is\nteardown.",[54,232,234],{"className":56,"code":233,"language":58,"meta":59,"style":59},"import pytest\n\n@pytest.fixture\ndef db():\n    conn = connect(\":memory:\")     # setup\n    yield conn                     # value handed to the test\n    conn.close()                   # teardown, runs after the test\n\ndef test_insert(db):               # pytest injects the fixture\n    db.execute(\"INSERT ...\")\n    assert db.count() == 1\n",[18,235,236,244,248,253,262,282,293,301,306,320,332],{"__ignoreMap":59},[63,237,238,241],{"class":65,"line":66},[63,239,240],{"class":76},"import",[63,242,243],{"class":84}," pytest\n",[63,245,246],{"class":65,"line":73},[63,247,107],{"emptyLinePlaceholder":106},[63,249,250],{"class":65,"line":88},[63,251,252],{"class":80},"@pytest.fixture\n",[63,254,255,257,260],{"class":65,"line":103},[63,256,77],{"class":76},[63,258,259],{"class":80}," db",[63,261,118],{"class":84},[63,263,264,267,270,273,276,279],{"class":65,"line":110},[63,265,266],{"class":84},"    conn ",[63,268,269],{"class":76},"=",[63,271,272],{"class":84}," connect(",[63,274,275],{"class":201},"\":memory:\"",[63,277,278],{"class":84},")     ",[63,280,281],{"class":69},"# setup\n",[63,283,284,287,290],{"class":65,"line":121},[63,285,286],{"class":76},"    yield",[63,288,289],{"class":84}," conn                     ",[63,291,292],{"class":69},"# value handed to the test\n",[63,294,295,298],{"class":65,"line":149},[63,296,297],{"class":84},"    conn.close()                   ",[63,299,300],{"class":69},"# teardown, runs after the test\n",[63,302,304],{"class":65,"line":303},8,[63,305,107],{"emptyLinePlaceholder":106},[63,307,309,311,314,317],{"class":65,"line":308},9,[63,310,77],{"class":76},[63,312,313],{"class":80}," test_insert",[63,315,316],{"class":84},"(db):               ",[63,318,319],{"class":69},"# pytest injects the fixture\n",[63,321,323,326,329],{"class":65,"line":322},10,[63,324,325],{"class":84},"    db.execute(",[63,327,328],{"class":201},"\"INSERT ...\"",[63,330,331],{"class":84},")\n",[63,333,335,337,340,342],{"class":65,"line":334},11,[63,336,124],{"class":76},[63,338,339],{"class":84}," db.count() ",[63,341,143],{"class":76},[63,343,344],{"class":130}," 1\n",[15,346,347,348,351,352,355],{},"This replaces ",[18,349,350],{},"setUp","\u002F",[18,353,354],{},"tearDown"," with composable, explicit dependencies — a test only gets\nthe fixtures it actually names.",[10,357,359],{"id":358},"fixture-scope","Fixture scope",[15,361,362,363,366,367,370],{},"By default a fixture runs ",[31,364,365],{},"per test"," (",[18,368,369],{},"function"," scope). For expensive setup (a database, a\nserver), widen the scope so it's created once and reused.",[54,372,374],{"className":56,"code":373,"language":58,"meta":59,"style":59},"@pytest.fixture(scope=\"module\")    # created once per test module\ndef server():\n    s = start_server()\n    yield s\n    s.stop()\n",[18,375,376,398,407,417,424],{"__ignoreMap":59},[63,377,378,380,383,387,389,392,395],{"class":65,"line":66},[63,379,225],{"class":80},[63,381,382],{"class":84},"(",[63,384,386],{"class":385},"s4XuR","scope",[63,388,269],{"class":76},[63,390,391],{"class":201},"\"module\"",[63,393,394],{"class":84},")    ",[63,396,397],{"class":69},"# created once per test module\n",[63,399,400,402,405],{"class":65,"line":73},[63,401,77],{"class":76},[63,403,404],{"class":80}," server",[63,406,118],{"class":84},[63,408,409,412,414],{"class":65,"line":88},[63,410,411],{"class":84},"    s ",[63,413,269],{"class":76},[63,415,416],{"class":84}," start_server()\n",[63,418,419,421],{"class":65,"line":103},[63,420,286],{"class":76},[63,422,423],{"class":84}," s\n",[63,425,426],{"class":65,"line":110},[63,427,428],{"class":84},"    s.stop()\n",[15,430,431,432,134,434,134,437,134,440,443,444,447,448,451],{},"Scopes are ",[18,433,369],{},[18,435,436],{},"class",[18,438,439],{},"module",[18,441,442],{},"package",", and ",[18,445,446],{},"session",". Put shared fixtures in a\n",[18,449,450],{},"conftest.py"," and pytest makes them available to all tests in that directory tree\nautomatically.",[10,453,455],{"id":454},"parametrize-for-table-driven-tests","Parametrize for table-driven tests",[15,457,458,461],{},[18,459,460],{},"@pytest.mark.parametrize"," runs the same test across many inputs, each reported as a separate\ncase — far better than a loop, which stops at the first failure.",[54,463,465],{"className":56,"code":464,"language":58,"meta":59,"style":59},"import pytest\n\n@pytest.mark.parametrize(\"value, expected\", [\n    (2, 4),\n    (3, 9),\n    (-4, 16),\n])\ndef test_square(value, expected):\n    assert value ** 2 == expected\n",[18,466,467,473,477,489,504,517,532,537,547],{"__ignoreMap":59},[63,468,469,471],{"class":65,"line":66},[63,470,240],{"class":76},[63,472,243],{"class":84},[63,474,475],{"class":65,"line":73},[63,476,107],{"emptyLinePlaceholder":106},[63,478,479,481,483,486],{"class":65,"line":88},[63,480,460],{"class":80},[63,482,382],{"class":84},[63,484,485],{"class":201},"\"value, expected\"",[63,487,488],{"class":84},", [\n",[63,490,491,494,496,498,501],{"class":65,"line":103},[63,492,493],{"class":84},"    (",[63,495,131],{"class":130},[63,497,134],{"class":84},[63,499,500],{"class":130},"4",[63,502,503],{"class":84},"),\n",[63,505,506,508,510,512,515],{"class":65,"line":110},[63,507,493],{"class":84},[63,509,137],{"class":130},[63,511,134],{"class":84},[63,513,514],{"class":130},"9",[63,516,503],{"class":84},[63,518,519,521,523,525,527,530],{"class":65,"line":121},[63,520,493],{"class":84},[63,522,156],{"class":76},[63,524,500],{"class":130},[63,526,134],{"class":84},[63,528,529],{"class":130},"16",[63,531,503],{"class":84},[63,533,534],{"class":65,"line":149},[63,535,536],{"class":84},"])\n",[63,538,539,541,544],{"class":65,"line":303},[63,540,77],{"class":76},[63,542,543],{"class":80}," test_square",[63,545,546],{"class":84},"(value, expected):\n",[63,548,549,551,554,557,560,563],{"class":65,"line":308},[63,550,124],{"class":76},[63,552,553],{"class":84}," value ",[63,555,556],{"class":76},"**",[63,558,559],{"class":130}," 2",[63,561,562],{"class":76}," ==",[63,564,565],{"class":84}," expected\n",[15,567,568,569,573],{},"Each tuple becomes an independent test, so you see ",[570,571,572],"em",{},"all"," failures at once with the exact\ninput that broke.",[10,575,577],{"id":576},"testing-that-code-raises","Testing that code raises",[15,579,580,581,584],{},"Use ",[18,582,583],{},"pytest.raises"," as a context manager to assert an exception is raised — and inspect it.",[54,586,588],{"className":56,"code":587,"language":58,"meta":59,"style":59},"import pytest\n\ndef test_divide_by_zero():\n    with pytest.raises(ZeroDivisionError):\n        1 \u002F 0\n\ndef test_message():\n    with pytest.raises(ValueError, match=\"invalid\"):   # regex on the message\n        int(\"abc\")\n    # or capture it:\n    with pytest.raises(ValueError) as exc:\n        raise ValueError(\"boom\")\n    assert \"boom\" in str(exc.value)\n",[18,589,590,596,600,609,623,633,637,646,671,683,688,704,720],{"__ignoreMap":59},[63,591,592,594],{"class":65,"line":66},[63,593,240],{"class":76},[63,595,243],{"class":84},[63,597,598],{"class":65,"line":73},[63,599,107],{"emptyLinePlaceholder":106},[63,601,602,604,607],{"class":65,"line":88},[63,603,77],{"class":76},[63,605,606],{"class":80}," test_divide_by_zero",[63,608,118],{"class":84},[63,610,611,614,617,620],{"class":65,"line":103},[63,612,613],{"class":76},"    with",[63,615,616],{"class":84}," pytest.raises(",[63,618,619],{"class":130},"ZeroDivisionError",[63,621,622],{"class":84},"):\n",[63,624,625,628,631],{"class":65,"line":110},[63,626,627],{"class":130},"        1",[63,629,630],{"class":76}," \u002F",[63,632,170],{"class":130},[63,634,635],{"class":65,"line":121},[63,636,107],{"emptyLinePlaceholder":106},[63,638,639,641,644],{"class":65,"line":149},[63,640,77],{"class":76},[63,642,643],{"class":80}," test_message",[63,645,118],{"class":84},[63,647,648,650,652,655,657,660,662,665,668],{"class":65,"line":303},[63,649,613],{"class":76},[63,651,616],{"class":84},[63,653,654],{"class":130},"ValueError",[63,656,134],{"class":84},[63,658,659],{"class":385},"match",[63,661,269],{"class":76},[63,663,664],{"class":201},"\"invalid\"",[63,666,667],{"class":84},"):   ",[63,669,670],{"class":69},"# regex on the message\n",[63,672,673,676,678,681],{"class":65,"line":308},[63,674,675],{"class":130},"        int",[63,677,382],{"class":84},[63,679,680],{"class":201},"\"abc\"",[63,682,331],{"class":84},[63,684,685],{"class":65,"line":322},[63,686,687],{"class":69},"    # or capture it:\n",[63,689,690,692,694,696,698,701],{"class":65,"line":334},[63,691,613],{"class":76},[63,693,616],{"class":84},[63,695,654],{"class":130},[63,697,140],{"class":84},[63,699,700],{"class":76},"as",[63,702,703],{"class":84}," exc:\n",[63,705,707,710,713,715,718],{"class":65,"line":706},12,[63,708,709],{"class":76},"        raise",[63,711,712],{"class":130}," ValueError",[63,714,382],{"class":84},[63,716,717],{"class":201},"\"boom\"",[63,719,331],{"class":84},[63,721,723,725,728,731,734],{"class":65,"line":722},13,[63,724,124],{"class":76},[63,726,727],{"class":201}," \"boom\"",[63,729,730],{"class":76}," in",[63,732,733],{"class":130}," str",[63,735,736],{"class":84},"(exc.value)\n",[15,738,739,740,743],{},"The ",[18,741,742],{},"match="," argument checks the exception message against a regex in one line.",[10,745,747],{"id":746},"markers-and-useful-flags","Markers and useful flags",[15,749,750],{},"Markers tag tests for selection or special handling, and pytest has flags that speed up the\nedit-test loop.",[54,752,754],{"className":56,"code":753,"language":58,"meta":59,"style":59},"import pytest\n\n@pytest.mark.slow\ndef test_big_job(): ...\n\n@pytest.mark.skip(reason=\"not implemented\")\ndef test_future(): ...\n\n@pytest.mark.xfail            # expected to fail\ndef test_known_bug(): ...\n",[18,755,756,762,766,771,784,788,805,816,820,828],{"__ignoreMap":59},[63,757,758,760],{"class":65,"line":66},[63,759,240],{"class":76},[63,761,243],{"class":84},[63,763,764],{"class":65,"line":73},[63,765,107],{"emptyLinePlaceholder":106},[63,767,768],{"class":65,"line":88},[63,769,770],{"class":80},"@pytest.mark.slow\n",[63,772,773,775,778,781],{"class":65,"line":103},[63,774,77],{"class":76},[63,776,777],{"class":80}," test_big_job",[63,779,780],{"class":84},"(): ",[63,782,783],{"class":130},"...\n",[63,785,786],{"class":65,"line":110},[63,787,107],{"emptyLinePlaceholder":106},[63,789,790,793,795,798,800,803],{"class":65,"line":121},[63,791,792],{"class":80},"@pytest.mark.skip",[63,794,382],{"class":84},[63,796,797],{"class":385},"reason",[63,799,269],{"class":76},[63,801,802],{"class":201},"\"not implemented\"",[63,804,331],{"class":84},[63,806,807,809,812,814],{"class":65,"line":149},[63,808,77],{"class":76},[63,810,811],{"class":80}," test_future",[63,813,780],{"class":84},[63,815,783],{"class":130},[63,817,818],{"class":65,"line":303},[63,819,107],{"emptyLinePlaceholder":106},[63,821,822,825],{"class":65,"line":308},[63,823,824],{"class":80},"@pytest.mark.xfail",[63,826,827],{"class":69},"            # expected to fail\n",[63,829,830,832,835,837],{"class":65,"line":322},[63,831,77],{"class":76},[63,833,834],{"class":80}," test_known_bug",[63,836,780],{"class":84},[63,838,783],{"class":130},[54,840,842],{"className":173,"code":841,"language":175,"meta":59,"style":59},"pytest -k \"add and not slow\"   # run by name expression\npytest -m slow                 # run only tests marked slow\npytest -x --lf                 # stop at first failure, rerun last-failed\n",[18,843,844,857,870],{"__ignoreMap":59},[63,845,846,848,851,854],{"class":65,"line":66},[63,847,20],{"class":80},[63,849,850],{"class":130}," -k",[63,852,853],{"class":201}," \"add and not slow\"",[63,855,856],{"class":69},"   # run by name expression\n",[63,858,859,861,864,867],{"class":65,"line":73},[63,860,20],{"class":80},[63,862,863],{"class":130}," -m",[63,865,866],{"class":201}," slow",[63,868,869],{"class":69},"                 # run only tests marked slow\n",[63,871,872,874,877,880],{"class":65,"line":88},[63,873,20],{"class":80},[63,875,876],{"class":130}," -x",[63,878,879],{"class":130}," --lf",[63,881,882],{"class":69},"                 # stop at first failure, rerun last-failed\n",[10,884,886],{"id":885},"recap","Recap",[15,888,889,890,892,893,897,898,366,901,903,904,906,907,909,910,912,913,917,918,922,923,925,926,134,929,443,932,935],{},"pytest tests are plain ",[18,891,44],{}," functions using bare ",[31,894,895],{},[18,896,28],{}," with rich failure output.\n",[31,899,900],{},"Fixtures",[18,902,225],{},", request by argument name, ",[18,905,229],{}," for teardown) provide\ncomposable setup, and ",[31,908,386],{}," plus ",[18,911,450],{}," control sharing. Use\n",[31,914,915],{},[18,916,460],{}," for table-driven tests that report every case independently,\nand ",[31,919,920],{},[18,921,583],{}," (with ",[18,924,742],{},") to assert and inspect exceptions. Markers and flags\nlike ",[18,927,928],{},"-k",[18,930,931],{},"-x",[18,933,934],{},"--lf"," make running the right tests fast.",[937,938,939],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":59,"searchDepth":73,"depth":73,"links":941},[942,943,944,945,946,947,948,949],{"id":12,"depth":73,"text":13},{"id":37,"depth":73,"text":38},{"id":215,"depth":73,"text":216},{"id":358,"depth":73,"text":359},{"id":454,"depth":73,"text":455},{"id":576,"depth":73,"text":577},{"id":746,"depth":73,"text":747},{"id":885,"depth":73,"text":886},"How to write tests with pytest — plain assert with rich introspection, fixtures for setup and teardown, parametrize for table-driven tests, and pytest.raises for asserting exceptions.","medium","md","Python",{},"\u002Fblog\u002Fpython-pytest-explained","\u002Fpython\u002Ftesting\u002Fpytest",{"title":5,"description":950},"blog\u002Fpython-pytest-explained","pytest Essentials","Testing","testing","2026-06-19","bBHCC9u8fGx5LVDi43saRKMqN08JXC4K_1FxCpMwYSc",1782244092280]