[{"data":1,"prerenderedAt":2807},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fgenerate-realistic-test-data-for-excel-reports-with-faker":3,"surround:\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fgenerate-realistic-test-data-for-excel-reports-with-faker":2772,"ancestors:\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fgenerate-realistic-test-data-for-excel-reports-with-faker":2781,"children:\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fgenerate-realistic-test-data-for-excel-reports-with-faker":2786,"related:\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fgenerate-realistic-test-data-for-excel-reports-with-faker":2787},{"id":4,"title":5,"body":6,"dateModified":2739,"datePublished":2739,"description":2740,"extension":2741,"faq":2742,"keyword":2751,"kind":2752,"meta":2753,"navigation":109,"path":2761,"seo":2762,"slug":2765,"stem":2766,"topics":2767,"type":2770,"__hash__":2771},"docs\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fgenerate-realistic-test-data-for-excel-reports-with-faker\u002Findex.md","Generate Realistic Test Data for Excel Reports with Faker",{"type":7,"value":8,"toc":2727},"minimark",[9,29,34,68,71,75,81,236,255,259,266,598,637,825,829,832,1358,1375,1379,1382,1831,1857,2149,2153,2156,2411,2418,2523,2537,2616,2620,2655,2659,2662,2666,2672,2678,2684,2690,2694,2723],[10,11,12,13,17,18,22,23,28],"p",{},"Report code is usually developed against last month's real export — which cannot go into the repository, cannot be shared with a contractor, and does not contain the awkward cases that break things in production. A small generator that produces ",[14,15,16],"em",{},"realistic"," fake workbooks fixes all three: names and addresses that look like the real thing, amounts and dates in plausible ranges, sheets that reference each other correctly, and, crucially, the messiness real exports have — blank cells, stray spaces, numbers stored as text. With the ",[19,20,21],"code",{},"Faker"," library and pandas, such a generator is a page of code, and seeding makes every run produce the same file so tests are repeatable. This page builds one for a customers-and-orders workbook and wires it into pytest. It belongs to ",[24,25,27],"a",{"href":26},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002F","Testing and Packaging Excel Automation Scripts",".",[30,31,33],"h2",{"id":32},"prerequisites","Prerequisites",[35,36,41],"pre",{"className":37,"code":38,"language":39,"meta":40,"style":40},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install faker pandas openpyxl pytest\n","bash","",[19,42,43],{"__ignoreMap":40},[44,45,48,52,56,59,62,65],"span",{"class":46,"line":47},"line",1,[44,49,51],{"class":50},"sMTad","pip",[44,53,55],{"class":54},"srMev"," install",[44,57,58],{"class":54}," faker",[44,60,61],{"class":54}," pandas",[44,63,64],{"class":54}," openpyxl",[44,66,67],{"class":54}," pytest\n",[10,69,70],{},"Faker generates fake names, addresses, companies, dates, numbers and much more, in dozens of locales. The examples were run with Faker 40; exact generated values can change between Faker versions, so pin the version in the project if tests compare generated values literally.",[30,72,74],{"id":73},"step-1-a-seeded-multi-locale-generator","Step 1 — A seeded, multi-locale generator",[10,76,77,78,80],{},"Create one ",[19,79,21],{}," instance for the generator and seed it, so the same seed always yields the same data. Passing several locales mixes names and addresses the way an international customer list does:",[35,82,86],{"className":83,"code":84,"language":85,"meta":40,"style":40},"language-python shiki shiki-themes github-light github-dark-high-contrast","from faker import Faker\n\nfake = Faker([\"en_GB\", \"fr_FR\", \"de_DE\", \"sv_SE\"])\nFaker.seed(20261002)\n\nfor _ in range(4):\n    print(f\"{fake.company():32} {fake.name():24} {fake.city()}\")\n","python",[19,87,88,104,111,145,158,163,187],{"__ignoreMap":40},[44,89,90,94,98,101],{"class":46,"line":47},[44,91,93],{"class":92},"s-kum","from",[44,95,97],{"class":96},"skGVy"," faker ",[44,99,100],{"class":92},"import",[44,102,103],{"class":96}," Faker\n",[44,105,107],{"class":46,"line":106},2,[44,108,110],{"emptyLinePlaceholder":109},true,"\n",[44,112,114,117,120,123,126,129,132,134,137,139,142],{"class":46,"line":113},3,[44,115,116],{"class":96},"fake ",[44,118,119],{"class":92},"=",[44,121,122],{"class":96}," Faker([",[44,124,125],{"class":54},"\"en_GB\"",[44,127,128],{"class":96},", ",[44,130,131],{"class":54},"\"fr_FR\"",[44,133,128],{"class":96},[44,135,136],{"class":54},"\"de_DE\"",[44,138,128],{"class":96},[44,140,141],{"class":54},"\"sv_SE\"",[44,143,144],{"class":96},"])\n",[44,146,148,151,155],{"class":46,"line":147},4,[44,149,150],{"class":96},"Faker.seed(",[44,152,154],{"class":153},"sP0c6","20261002",[44,156,157],{"class":96},")\n",[44,159,161],{"class":46,"line":160},5,[44,162,110],{"emptyLinePlaceholder":109},[44,164,166,169,172,175,178,181,184],{"class":46,"line":165},6,[44,167,168],{"class":92},"for",[44,170,171],{"class":96}," _ ",[44,173,174],{"class":92},"in",[44,176,177],{"class":153}," range",[44,179,180],{"class":96},"(",[44,182,183],{"class":153},"4",[44,185,186],{"class":96},"):\n",[44,188,190,193,195,198,201,205,208,211,214,217,220,223,225,227,230,232,234],{"class":46,"line":189},7,[44,191,192],{"class":153},"    print",[44,194,180],{"class":96},[44,196,197],{"class":92},"f",[44,199,200],{"class":54},"\"",[44,202,204],{"class":203},"sSjpA","{",[44,206,207],{"class":96},"fake.company()",[44,209,210],{"class":92},":32",[44,212,213],{"class":203},"}",[44,215,216],{"class":203}," {",[44,218,219],{"class":96},"fake.name()",[44,221,222],{"class":92},":24",[44,224,213],{"class":203},[44,226,216],{"class":203},[44,228,229],{"class":96},"fake.city()",[44,231,213],{"class":203},[44,233,200],{"class":54},[44,235,157],{"class":96},[10,237,238,239,242,243,246,247,250,251,254],{},"The output mixes British, French, German and Swedish companies, people and towns — complete with accents and characters such as ",[19,240,241],{},"ö"," and ",[19,244,245],{},"ß"," that catch encoding bugs. ",[19,248,249],{},"Faker.seed()"," seeds the shared random generator used by every instance; for independent generators in the same process, use ",[19,252,253],{},"fake.seed_instance(n)"," on each instead. Run the block twice and it prints the same rows, which is what makes generated files usable as test fixtures.",[30,256,258],{"id":257},"step-2-a-custom-provider-for-your-own-codes","Step 2 — A custom provider for your own codes",[10,260,261,262,265],{},"Real data has identifiers with formats Faker does not know — product SKUs, account codes, region codes. A provider is a small class whose methods become new ",[19,263,264],{},"fake.\u003Cmethod>()"," calls:",[35,267,269],{"className":83,"code":268,"language":85,"meta":40,"style":40},"from faker.providers import BaseProvider\n\nclass ShopProvider(BaseProvider):\n    regions = (\"North\", \"South\", \"East\", \"West\")\n    categories = {\"Tools\": (8, 240), \"Garden\": (4, 120), \"Safety\": (2, 60)}\n\n    def sku(self):\n        return self.bothify(\"??-####\", letters=\"ABCDEFGHJKLMNPRSTUVWXYZ\")\n\n    def region(self):\n        return self.random_element(self.regions)\n\n    def category_and_price(self):\n        cat = self.random_element(list(self.categories))\n        lo, hi = self.categories[cat]\n        return cat, round(self.generator.pyfloat(min_value=lo, max_value=hi, right_digits=2), 2)\n\nfor factory in fake.factories:          # a multi-locale Faker has one generator per locale\n    factory.add_provider(ShopProvider)\nprint(fake.sku(), fake.region(), fake.category_and_price())\n",[19,270,271,283,287,303,333,389,393,405,433,438,448,464,469,479,501,514,561,566,583,589],{"__ignoreMap":40},[44,272,273,275,278,280],{"class":46,"line":47},[44,274,93],{"class":92},[44,276,277],{"class":96}," faker.providers ",[44,279,100],{"class":92},[44,281,282],{"class":96}," BaseProvider\n",[44,284,285],{"class":46,"line":106},[44,286,110],{"emptyLinePlaceholder":109},[44,288,289,292,295,297,301],{"class":46,"line":113},[44,290,291],{"class":92},"class",[44,293,294],{"class":50}," ShopProvider",[44,296,180],{"class":96},[44,298,300],{"class":299},"sHg5J","BaseProvider",[44,302,186],{"class":96},[44,304,305,308,310,313,316,318,321,323,326,328,331],{"class":46,"line":147},[44,306,307],{"class":96},"    regions ",[44,309,119],{"class":92},[44,311,312],{"class":96}," (",[44,314,315],{"class":54},"\"North\"",[44,317,128],{"class":96},[44,319,320],{"class":54},"\"South\"",[44,322,128],{"class":96},[44,324,325],{"class":54},"\"East\"",[44,327,128],{"class":96},[44,329,330],{"class":54},"\"West\"",[44,332,157],{"class":96},[44,334,335,338,340,342,345,348,351,353,356,359,362,364,366,368,371,373,376,378,381,383,386],{"class":46,"line":160},[44,336,337],{"class":96},"    categories ",[44,339,119],{"class":92},[44,341,216],{"class":96},[44,343,344],{"class":54},"\"Tools\"",[44,346,347],{"class":96},": (",[44,349,350],{"class":153},"8",[44,352,128],{"class":96},[44,354,355],{"class":153},"240",[44,357,358],{"class":96},"), ",[44,360,361],{"class":54},"\"Garden\"",[44,363,347],{"class":96},[44,365,183],{"class":153},[44,367,128],{"class":96},[44,369,370],{"class":153},"120",[44,372,358],{"class":96},[44,374,375],{"class":54},"\"Safety\"",[44,377,347],{"class":96},[44,379,380],{"class":153},"2",[44,382,128],{"class":96},[44,384,385],{"class":153},"60",[44,387,388],{"class":96},")}\n",[44,390,391],{"class":46,"line":165},[44,392,110],{"emptyLinePlaceholder":109},[44,394,395,398,402],{"class":46,"line":189},[44,396,397],{"class":92},"    def",[44,399,401],{"class":400},"s_Opv"," sku",[44,403,404],{"class":96},"(self):\n",[44,406,408,411,414,417,420,422,426,428,431],{"class":46,"line":407},8,[44,409,410],{"class":92},"        return",[44,412,413],{"class":153}," self",[44,415,416],{"class":96},".bothify(",[44,418,419],{"class":54},"\"??-####\"",[44,421,128],{"class":96},[44,423,425],{"class":424},"sa561","letters",[44,427,119],{"class":92},[44,429,430],{"class":54},"\"ABCDEFGHJKLMNPRSTUVWXYZ\"",[44,432,157],{"class":96},[44,434,436],{"class":46,"line":435},9,[44,437,110],{"emptyLinePlaceholder":109},[44,439,441,443,446],{"class":46,"line":440},10,[44,442,397],{"class":92},[44,444,445],{"class":400}," region",[44,447,404],{"class":96},[44,449,451,453,455,458,461],{"class":46,"line":450},11,[44,452,410],{"class":92},[44,454,413],{"class":153},[44,456,457],{"class":96},".random_element(",[44,459,460],{"class":153},"self",[44,462,463],{"class":96},".regions)\n",[44,465,467],{"class":46,"line":466},12,[44,468,110],{"emptyLinePlaceholder":109},[44,470,472,474,477],{"class":46,"line":471},13,[44,473,397],{"class":92},[44,475,476],{"class":400}," category_and_price",[44,478,404],{"class":96},[44,480,482,485,487,489,491,494,496,498],{"class":46,"line":481},14,[44,483,484],{"class":96},"        cat ",[44,486,119],{"class":92},[44,488,413],{"class":153},[44,490,457],{"class":96},[44,492,493],{"class":153},"list",[44,495,180],{"class":96},[44,497,460],{"class":153},[44,499,500],{"class":96},".categories))\n",[44,502,504,507,509,511],{"class":46,"line":503},15,[44,505,506],{"class":96},"        lo, hi ",[44,508,119],{"class":92},[44,510,413],{"class":153},[44,512,513],{"class":96},".categories[cat]\n",[44,515,517,519,522,525,527,529,532,535,537,540,543,545,548,551,553,555,557,559],{"class":46,"line":516},16,[44,518,410],{"class":92},[44,520,521],{"class":96}," cat, ",[44,523,524],{"class":153},"round",[44,526,180],{"class":96},[44,528,460],{"class":153},[44,530,531],{"class":96},".generator.pyfloat(",[44,533,534],{"class":424},"min_value",[44,536,119],{"class":92},[44,538,539],{"class":96},"lo, ",[44,541,542],{"class":424},"max_value",[44,544,119],{"class":92},[44,546,547],{"class":96},"hi, ",[44,549,550],{"class":424},"right_digits",[44,552,119],{"class":92},[44,554,380],{"class":153},[44,556,358],{"class":96},[44,558,380],{"class":153},[44,560,157],{"class":96},[44,562,564],{"class":46,"line":563},17,[44,565,110],{"emptyLinePlaceholder":109},[44,567,569,571,574,576,579],{"class":46,"line":568},18,[44,570,168],{"class":92},[44,572,573],{"class":96}," factory ",[44,575,174],{"class":92},[44,577,578],{"class":96}," fake.factories:          ",[44,580,582],{"class":581},"s-wDw","# a multi-locale Faker has one generator per locale\n",[44,584,586],{"class":46,"line":585},19,[44,587,588],{"class":96},"    factory.add_provider(ShopProvider)\n",[44,590,592,595],{"class":46,"line":591},20,[44,593,594],{"class":153},"print",[44,596,597],{"class":96},"(fake.sku(), fake.region(), fake.category_and_price())\n",[10,599,600,601,604,605,608,609,612,613,616,617,620,621,624,625,628,629,632,633,636],{},"With several locales, ",[19,602,603],{},"fake"," is a proxy over one generator per locale, and calling ",[19,606,607],{},"fake.add_provider()"," on it raises ",[19,610,611],{},"NotImplementedError"," — register the provider on each of ",[19,614,615],{},"fake.factories"," instead; with a single locale, ",[19,618,619],{},"fake.add_provider(ShopProvider)"," works directly. ",[19,622,623],{},"bothify"," replaces ",[19,626,627],{},"?"," with a letter and ",[19,630,631],{},"#"," with a digit, here from an alphabet without the easily confused I, O and Q. ",[19,634,635],{},"random_element"," picks from a sequence using the seeded generator, so custom values are reproducible too. Price ranges per category keep amounts plausible — a test report showing a pair of gloves at 9,800 euros hides bugs instead of revealing them.",[638,639,647,648,647,652,647,656,647,663,647,673,647,684,647,691,647,694,647,699,647,703,647,709,647,714,647,718,647,725,647,731,647,735,647,739,647,743,647,746,647,751,647,758,647,763,647,766,647,769,647,773,647,776,647,781,647,788,647,793,647,796,647,799,647,803,647,806,647,809,647,815,647,819,647,822],"svg",{"viewBox":640,"role":641,"ariaLabelledBy":642,"xmlns":645,"style":646},"4 0 752 160","img",[643,644],"fk-pipeline-t","fk-pipeline-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[649,650,651],"title",{"id":643},"From seed to test workbook",[653,654,655],"desc",{"id":644},"A seeded Faker instance with several locales and a custom provider generates customers and then orders that reference them. A messiness step injects blanks, spaces and text values, and the result is written as a two-sheet workbook used by tests.",[657,658],"rect",{"x":183,"y":659,"width":660,"height":661,"fill":662},"0","752","160","#ffffff",[657,664],{"x":665,"y":666,"width":667,"height":668,"rx":669,"fill":670,"stroke":671,"style":672},"20.0","16.0","115.2","128.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[674,675,677],"g",{"transform":676},"translate(65.6,29.0) scale(1.000)",[678,679],"path",{"d":680,"fill":681,"stroke":682,"style":683},"M12 9a3 3 0 1 0 .01 0z M12 2.5v3 M12 18.5v3 M2.5 12h3 M18.5 12h3 M5.3 5.3l2.1 2.1 M16.6 16.6l2.1 2.1 M5.3 18.7l2.1-2.1 M16.6 7.4l2.1-2.1","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[685,686,690],"text",{"x":687,"y":688,"style":689},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Seeded",[685,692,21],{"x":687,"y":693,"style":689},"93.0",[685,695,698],{"x":687,"y":696,"style":697},"112.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","locales +",[685,700,702],{"x":687,"y":701,"style":697},"130.0","ShopProvider",[46,704],{"x1":705,"y1":706,"x2":707,"y2":706,"stroke":708,"style":672},"140.2","80.0","162.2","var(--brand,#5b5cf0)",[710,711],"polygon",{"points":712,"fill":713},"166.2,80.0 157.2,85.0 157.2,75.0","#5b5cf0",[657,715],{"x":716,"y":666,"width":667,"height":668,"rx":669,"fill":717,"stroke":708,"style":672},"171.2","#f0f4ff",[674,719,721],{"transform":720},"translate(216.8,37.8) scale(1.000)",[678,722],{"d":723,"fill":681,"stroke":724,"style":683},"M9 11a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z M2.5 20c0-3.6 2.9-6 6.5-6s6.5 2.4 6.5 6 M16 4.5a3.3 3.3 0 0 1 0 6.3 M18 14.3c2.2.7 3.5 2.8 3.5 5.7","var(--brand-strong,#4338ca)",[685,726,730],{"x":727,"y":728,"style":729},"228.8","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Customers",[685,732,734],{"x":727,"y":733,"style":697},"103.8","unique ids,",[685,736,738],{"x":727,"y":737,"style":697},"121.2","names, cities",[46,740],{"x1":741,"y1":706,"x2":742,"y2":706,"stroke":708,"style":672},"291.4","313.4",[710,744],{"points":745,"fill":713},"317.4,80.0 308.4,85.0 308.4,75.0",[657,747],{"x":748,"y":666,"width":667,"height":668,"rx":669,"fill":749,"stroke":750,"style":672},"322.4","#d9f4f1","var(--teal,#0f9488)",[674,752,754],{"transform":753},"translate(368.0,37.8) scale(1.000)",[678,755],{"d":756,"fill":681,"stroke":757,"style":683},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--teal-ink,#0b6157)",[685,759,762],{"x":760,"y":728,"style":761},"380.0","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Orders",[685,764,765],{"x":760,"y":733,"style":697},"valid customer",[685,767,768],{"x":760,"y":737,"style":697},"ids, dates",[46,770],{"x1":771,"y1":706,"x2":772,"y2":706,"stroke":708,"style":672},"442.6","464.6",[710,774],{"points":775,"fill":713},"468.6,80.0 459.6,85.0 459.6,75.0",[657,777],{"x":778,"y":666,"width":667,"height":668,"rx":669,"fill":779,"stroke":780,"style":672},"473.6","#fdefd8","var(--gold,#b4740a)",[674,782,784],{"transform":783},"translate(519.2,37.8) scale(1.000)",[678,785],{"d":786,"fill":681,"stroke":787,"style":683},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)",[685,789,792],{"x":790,"y":728,"style":791},"531.2","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Mess it up",[685,794,795],{"x":790,"y":733,"style":697},"blanks, spaces,",[685,797,798],{"x":790,"y":737,"style":697},"text numbers",[46,800],{"x1":801,"y1":706,"x2":802,"y2":706,"stroke":708,"style":672},"593.8","615.8",[710,804],{"points":805,"fill":713},"619.8,80.0 610.8,85.0 610.8,75.0",[657,807],{"x":808,"y":666,"width":667,"height":668,"rx":669,"fill":749,"stroke":750,"style":672},"624.8",[674,810,812],{"transform":811},"translate(670.4,37.8) scale(1.000)",[678,813],{"d":814,"fill":681,"stroke":757,"style":683},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6",[685,816,818],{"x":817,"y":728,"style":761},"682.4","test_data.xlsx",[685,820,821],{"x":817,"y":733,"style":697},"same file every",[685,823,824],{"x":817,"y":737,"style":697},"run",[30,826,828],{"id":827},"step-3-related-sheets-with-valid-keys","Step 3 — Related sheets with valid keys",[10,830,831],{},"Report code joins sheets, so the fake data must join too. Generate customers first with unique IDs, then orders that only reference those IDs:",[35,833,835],{"className":83,"code":834,"language":85,"meta":40,"style":40},"import pandas as pd\nfrom datetime import date\n\ndef customers(n):\n    ids = [f\"C{fake.unique.random_int(1000, 9999)}\" for _ in range(n)]\n    return pd.DataFrame({\n        \"Customer ID\": ids,\n        \"Company\": [fake.company() for _ in ids],\n        \"Contact\": [fake.name() for _ in ids],\n        \"Email\": [fake.company_email() for _ in ids],\n        \"City\": [fake.city() for _ in ids],\n        \"Region\": [fake.region() for _ in ids],\n    })\n\ndef orders(cust, n, start=date(2026, 7, 1), end=date(2026, 9, 30)):\n    rows = []\n    for i in range(n):\n        cat, price = fake.category_and_price()\n        rows.append({\n            \"Order ID\": f\"SO-{10001 + i}\",\n            \"Customer ID\": fake.random_element(list(cust[\"Customer ID\"])),\n            \"Order date\": fake.date_between(start_date=start, end_date=end),\n            \"SKU\": fake.sku(),\n            \"Category\": cat,\n            \"Qty\": fake.random_int(1, 20),\n            \"Unit price\": price,\n        })\n    return pd.DataFrame(rows)\n\ncust = customers(40)\nords = orders(cust, 400)\nprint(cust.shape, ords.shape, \"| orphan orders:\", (~ords[\"Customer ID\"].isin(cust[\"Customer ID\"])).sum())\nprint(ords[\"Order date\"].min(), \"→\", ords[\"Order date\"].max(), \"| unique customer ids:\", cust[\"Customer ID\"].is_unique)\n",[19,836,837,850,862,866,877,924,932,940,957,973,989,1005,1021,1026,1030,1080,1090,1104,1114,1119,1150,1170,1195,1204,1213,1232,1241,1247,1255,1260,1276,1292,1322],{"__ignoreMap":40},[44,838,839,841,844,847],{"class":46,"line":47},[44,840,100],{"class":92},[44,842,843],{"class":96}," pandas ",[44,845,846],{"class":92},"as",[44,848,849],{"class":96}," pd\n",[44,851,852,854,857,859],{"class":46,"line":106},[44,853,93],{"class":92},[44,855,856],{"class":96}," datetime ",[44,858,100],{"class":92},[44,860,861],{"class":96}," date\n",[44,863,864],{"class":46,"line":113},[44,865,110],{"emptyLinePlaceholder":109},[44,867,868,871,874],{"class":46,"line":147},[44,869,870],{"class":92},"def",[44,872,873],{"class":400}," customers",[44,875,876],{"class":96},"(n):\n",[44,878,879,882,884,887,889,892,894,897,900,902,905,908,910,912,915,917,919,921],{"class":46,"line":160},[44,880,881],{"class":96},"    ids ",[44,883,119],{"class":92},[44,885,886],{"class":96}," [",[44,888,197],{"class":92},[44,890,891],{"class":54},"\"C",[44,893,204],{"class":203},[44,895,896],{"class":96},"fake.unique.random_int(",[44,898,899],{"class":153},"1000",[44,901,128],{"class":96},[44,903,904],{"class":153},"9999",[44,906,907],{"class":96},")",[44,909,213],{"class":203},[44,911,200],{"class":54},[44,913,914],{"class":92}," for",[44,916,171],{"class":96},[44,918,174],{"class":92},[44,920,177],{"class":153},[44,922,923],{"class":96},"(n)]\n",[44,925,926,929],{"class":46,"line":165},[44,927,928],{"class":92},"    return",[44,930,931],{"class":96}," pd.DataFrame({\n",[44,933,934,937],{"class":46,"line":189},[44,935,936],{"class":54},"        \"Customer ID\"",[44,938,939],{"class":96},": ids,\n",[44,941,942,945,948,950,952,954],{"class":46,"line":407},[44,943,944],{"class":54},"        \"Company\"",[44,946,947],{"class":96},": [fake.company() ",[44,949,168],{"class":92},[44,951,171],{"class":96},[44,953,174],{"class":92},[44,955,956],{"class":96}," ids],\n",[44,958,959,962,965,967,969,971],{"class":46,"line":435},[44,960,961],{"class":54},"        \"Contact\"",[44,963,964],{"class":96},": [fake.name() ",[44,966,168],{"class":92},[44,968,171],{"class":96},[44,970,174],{"class":92},[44,972,956],{"class":96},[44,974,975,978,981,983,985,987],{"class":46,"line":440},[44,976,977],{"class":54},"        \"Email\"",[44,979,980],{"class":96},": [fake.company_email() ",[44,982,168],{"class":92},[44,984,171],{"class":96},[44,986,174],{"class":92},[44,988,956],{"class":96},[44,990,991,994,997,999,1001,1003],{"class":46,"line":450},[44,992,993],{"class":54},"        \"City\"",[44,995,996],{"class":96},": [fake.city() ",[44,998,168],{"class":92},[44,1000,171],{"class":96},[44,1002,174],{"class":92},[44,1004,956],{"class":96},[44,1006,1007,1010,1013,1015,1017,1019],{"class":46,"line":466},[44,1008,1009],{"class":54},"        \"Region\"",[44,1011,1012],{"class":96},": [fake.region() ",[44,1014,168],{"class":92},[44,1016,171],{"class":96},[44,1018,174],{"class":92},[44,1020,956],{"class":96},[44,1022,1023],{"class":46,"line":471},[44,1024,1025],{"class":96},"    })\n",[44,1027,1028],{"class":46,"line":481},[44,1029,110],{"emptyLinePlaceholder":109},[44,1031,1032,1034,1037,1040,1042,1045,1048,1050,1053,1055,1058,1061,1063,1065,1067,1069,1072,1074,1077],{"class":46,"line":503},[44,1033,870],{"class":92},[44,1035,1036],{"class":400}," orders",[44,1038,1039],{"class":96},"(cust, n, start",[44,1041,119],{"class":92},[44,1043,1044],{"class":96},"date(",[44,1046,1047],{"class":153},"2026",[44,1049,128],{"class":96},[44,1051,1052],{"class":153},"7",[44,1054,128],{"class":96},[44,1056,1057],{"class":153},"1",[44,1059,1060],{"class":96},"), end",[44,1062,119],{"class":92},[44,1064,1044],{"class":96},[44,1066,1047],{"class":153},[44,1068,128],{"class":96},[44,1070,1071],{"class":153},"9",[44,1073,128],{"class":96},[44,1075,1076],{"class":153},"30",[44,1078,1079],{"class":96},")):\n",[44,1081,1082,1085,1087],{"class":46,"line":516},[44,1083,1084],{"class":96},"    rows ",[44,1086,119],{"class":92},[44,1088,1089],{"class":96}," []\n",[44,1091,1092,1095,1098,1100,1102],{"class":46,"line":563},[44,1093,1094],{"class":92},"    for",[44,1096,1097],{"class":96}," i ",[44,1099,174],{"class":92},[44,1101,177],{"class":153},[44,1103,876],{"class":96},[44,1105,1106,1109,1111],{"class":46,"line":568},[44,1107,1108],{"class":96},"        cat, price ",[44,1110,119],{"class":92},[44,1112,1113],{"class":96}," fake.category_and_price()\n",[44,1115,1116],{"class":46,"line":585},[44,1117,1118],{"class":96},"        rows.append({\n",[44,1120,1121,1124,1127,1129,1132,1134,1137,1140,1143,1145,1147],{"class":46,"line":591},[44,1122,1123],{"class":54},"            \"Order ID\"",[44,1125,1126],{"class":96},": ",[44,1128,197],{"class":92},[44,1130,1131],{"class":54},"\"SO-",[44,1133,204],{"class":203},[44,1135,1136],{"class":153},"10001",[44,1138,1139],{"class":92}," +",[44,1141,1142],{"class":96}," i",[44,1144,213],{"class":203},[44,1146,200],{"class":54},[44,1148,1149],{"class":96},",\n",[44,1151,1153,1156,1159,1161,1164,1167],{"class":46,"line":1152},21,[44,1154,1155],{"class":54},"            \"Customer ID\"",[44,1157,1158],{"class":96},": fake.random_element(",[44,1160,493],{"class":153},[44,1162,1163],{"class":96},"(cust[",[44,1165,1166],{"class":54},"\"Customer ID\"",[44,1168,1169],{"class":96},"])),\n",[44,1171,1173,1176,1179,1182,1184,1187,1190,1192],{"class":46,"line":1172},22,[44,1174,1175],{"class":54},"            \"Order date\"",[44,1177,1178],{"class":96},": fake.date_between(",[44,1180,1181],{"class":424},"start_date",[44,1183,119],{"class":92},[44,1185,1186],{"class":96},"start, ",[44,1188,1189],{"class":424},"end_date",[44,1191,119],{"class":92},[44,1193,1194],{"class":96},"end),\n",[44,1196,1198,1201],{"class":46,"line":1197},23,[44,1199,1200],{"class":54},"            \"SKU\"",[44,1202,1203],{"class":96},": fake.sku(),\n",[44,1205,1207,1210],{"class":46,"line":1206},24,[44,1208,1209],{"class":54},"            \"Category\"",[44,1211,1212],{"class":96},": cat,\n",[44,1214,1216,1219,1222,1224,1226,1229],{"class":46,"line":1215},25,[44,1217,1218],{"class":54},"            \"Qty\"",[44,1220,1221],{"class":96},": fake.random_int(",[44,1223,1057],{"class":153},[44,1225,128],{"class":96},[44,1227,1228],{"class":153},"20",[44,1230,1231],{"class":96},"),\n",[44,1233,1235,1238],{"class":46,"line":1234},26,[44,1236,1237],{"class":54},"            \"Unit price\"",[44,1239,1240],{"class":96},": price,\n",[44,1242,1244],{"class":46,"line":1243},27,[44,1245,1246],{"class":96},"        })\n",[44,1248,1250,1252],{"class":46,"line":1249},28,[44,1251,928],{"class":92},[44,1253,1254],{"class":96}," pd.DataFrame(rows)\n",[44,1256,1258],{"class":46,"line":1257},29,[44,1259,110],{"emptyLinePlaceholder":109},[44,1261,1263,1266,1268,1271,1274],{"class":46,"line":1262},30,[44,1264,1265],{"class":96},"cust ",[44,1267,119],{"class":92},[44,1269,1270],{"class":96}," customers(",[44,1272,1273],{"class":153},"40",[44,1275,157],{"class":96},[44,1277,1279,1282,1284,1287,1290],{"class":46,"line":1278},31,[44,1280,1281],{"class":96},"ords ",[44,1283,119],{"class":92},[44,1285,1286],{"class":96}," orders(cust, ",[44,1288,1289],{"class":153},"400",[44,1291,157],{"class":96},[44,1293,1295,1297,1300,1303,1306,1309,1312,1314,1317,1319],{"class":46,"line":1294},32,[44,1296,594],{"class":153},[44,1298,1299],{"class":96},"(cust.shape, ords.shape, ",[44,1301,1302],{"class":54},"\"| orphan orders:\"",[44,1304,1305],{"class":96},", (",[44,1307,1308],{"class":92},"~",[44,1310,1311],{"class":96},"ords[",[44,1313,1166],{"class":54},[44,1315,1316],{"class":96},"].isin(cust[",[44,1318,1166],{"class":54},[44,1320,1321],{"class":96},"])).sum())\n",[44,1323,1325,1327,1330,1333,1336,1339,1342,1344,1347,1350,1353,1355],{"class":46,"line":1324},33,[44,1326,594],{"class":153},[44,1328,1329],{"class":96},"(ords[",[44,1331,1332],{"class":54},"\"Order date\"",[44,1334,1335],{"class":96},"].min(), ",[44,1337,1338],{"class":54},"\"→\"",[44,1340,1341],{"class":96},", ords[",[44,1343,1332],{"class":54},[44,1345,1346],{"class":96},"].max(), ",[44,1348,1349],{"class":54},"\"| unique customer ids:\"",[44,1351,1352],{"class":96},", cust[",[44,1354,1166],{"class":54},[44,1356,1357],{"class":96},"].is_unique)\n",[10,1359,1360,1363,1364,1367,1368,1370,1371,1374],{},[19,1361,1362],{},"fake.unique.random_int(...)"," never returns the same value twice for this instance, so customer IDs are unique without checking by hand; call ",[19,1365,1366],{},"fake.unique.clear()"," between independent datasets. Orders pick customer IDs from the generated list, so every order joins to a customer — no orphans — and dates fall inside the quarter. For data that should look like a real business, skew the choices: ",[19,1369,635],{}," also accepts an ",[19,1372,1373],{},"OrderedDict"," of weights, which makes a few customers account for most orders, as they usually do.",[30,1376,1378],{"id":1377},"step-4-add-the-messiness-real-exports-have","Step 4 — Add the messiness real exports have",[10,1380,1381],{},"Clean fake data only tests the happy path. Real exports have blanks, stray spaces, inconsistent capitalisation, numbers stored as text and dates typed as strings. Inject a controlled amount of each, at seeded positions:",[35,1383,1385],{"className":83,"code":1384,"language":85,"meta":40,"style":40},"def make_messy(df, rate=0.03):\n    df = df.astype(object).copy()\n    n = len(df)\n    pick = lambda: fake.random_elements(range(n), length=max(1, int(n * rate)), unique=True)\n    for i in pick():\n        df.loc[i, \"Unit price\"] = None                                  # blank cells\n    for i in pick():\n        df.loc[i, \"Category\"] = f\" {df.loc[i, 'Category'].lower()} \"    # spaces and case\n    for i in pick():\n        df.loc[i, \"Qty\"] = str(df.loc[i, \"Qty\"])                        # number stored as text\n    for i in pick():\n        df.loc[i, \"Order date\"] = df.loc[i, \"Order date\"].strftime(\"%d\u002F%m\u002F%Y\")   # date as text\n    df.loc[fake.random_int(0, n - 1), \"SKU\"] = \"=HYPERLINK(\\\"http:\u002F\u002Fexample.com\\\")\"  # formula-like text\n    return df\n\nmessy = make_messy(ords)\nprint(\"blank prices:\", messy[\"Unit price\"].isna().sum(),\n      \"| text qty:\", sum(isinstance(v, str) for v in messy[\"Qty\"]),\n      \"| text dates:\", sum(isinstance(v, str) for v in messy[\"Order date\"]),\n      \"| odd categories:\", sorted(set(messy[\"Category\"]) - {\"Tools\", \"Garden\", \"Safety\"}))\n",[19,1386,1387,1404,1420,1433,1488,1499,1518,1528,1564,1574,1599,1609,1641,1683,1690,1694,1704,1721,1760,1791],{"__ignoreMap":40},[44,1388,1389,1391,1394,1397,1399,1402],{"class":46,"line":47},[44,1390,870],{"class":92},[44,1392,1393],{"class":400}," make_messy",[44,1395,1396],{"class":96},"(df, rate",[44,1398,119],{"class":92},[44,1400,1401],{"class":153},"0.03",[44,1403,186],{"class":96},[44,1405,1406,1409,1411,1414,1417],{"class":46,"line":106},[44,1407,1408],{"class":96},"    df ",[44,1410,119],{"class":92},[44,1412,1413],{"class":96}," df.astype(",[44,1415,1416],{"class":153},"object",[44,1418,1419],{"class":96},").copy()\n",[44,1421,1422,1425,1427,1430],{"class":46,"line":113},[44,1423,1424],{"class":96},"    n ",[44,1426,119],{"class":92},[44,1428,1429],{"class":153}," len",[44,1431,1432],{"class":96},"(df)\n",[44,1434,1435,1438,1440,1443,1446,1449,1452,1455,1457,1460,1462,1464,1466,1469,1472,1475,1478,1481,1483,1486],{"class":46,"line":147},[44,1436,1437],{"class":96},"    pick ",[44,1439,119],{"class":92},[44,1441,1442],{"class":92}," lambda",[44,1444,1445],{"class":96},": fake.random_elements(",[44,1447,1448],{"class":153},"range",[44,1450,1451],{"class":96},"(n), ",[44,1453,1454],{"class":424},"length",[44,1456,119],{"class":92},[44,1458,1459],{"class":153},"max",[44,1461,180],{"class":96},[44,1463,1057],{"class":153},[44,1465,128],{"class":96},[44,1467,1468],{"class":153},"int",[44,1470,1471],{"class":96},"(n ",[44,1473,1474],{"class":92},"*",[44,1476,1477],{"class":96}," rate)), ",[44,1479,1480],{"class":424},"unique",[44,1482,119],{"class":92},[44,1484,1485],{"class":153},"True",[44,1487,157],{"class":96},[44,1489,1490,1492,1494,1496],{"class":46,"line":160},[44,1491,1094],{"class":92},[44,1493,1097],{"class":96},[44,1495,174],{"class":92},[44,1497,1498],{"class":96}," pick():\n",[44,1500,1501,1504,1507,1510,1512,1515],{"class":46,"line":165},[44,1502,1503],{"class":96},"        df.loc[i, ",[44,1505,1506],{"class":54},"\"Unit price\"",[44,1508,1509],{"class":96},"] ",[44,1511,119],{"class":92},[44,1513,1514],{"class":153}," None",[44,1516,1517],{"class":581},"                                  # blank cells\n",[44,1519,1520,1522,1524,1526],{"class":46,"line":189},[44,1521,1094],{"class":92},[44,1523,1097],{"class":96},[44,1525,174],{"class":92},[44,1527,1498],{"class":96},[44,1529,1530,1532,1535,1537,1539,1542,1545,1547,1550,1553,1556,1558,1561],{"class":46,"line":407},[44,1531,1503],{"class":96},[44,1533,1534],{"class":54},"\"Category\"",[44,1536,1509],{"class":96},[44,1538,119],{"class":92},[44,1540,1541],{"class":92}," f",[44,1543,1544],{"class":54},"\" ",[44,1546,204],{"class":203},[44,1548,1549],{"class":96},"df.loc[i, ",[44,1551,1552],{"class":54},"'Category'",[44,1554,1555],{"class":96},"].lower()",[44,1557,213],{"class":203},[44,1559,1560],{"class":54}," \"",[44,1562,1563],{"class":581},"    # spaces and case\n",[44,1565,1566,1568,1570,1572],{"class":46,"line":435},[44,1567,1094],{"class":92},[44,1569,1097],{"class":96},[44,1571,174],{"class":92},[44,1573,1498],{"class":96},[44,1575,1576,1578,1581,1583,1585,1588,1591,1593,1596],{"class":46,"line":440},[44,1577,1503],{"class":96},[44,1579,1580],{"class":54},"\"Qty\"",[44,1582,1509],{"class":96},[44,1584,119],{"class":92},[44,1586,1587],{"class":153}," str",[44,1589,1590],{"class":96},"(df.loc[i, ",[44,1592,1580],{"class":54},[44,1594,1595],{"class":96},"])                        ",[44,1597,1598],{"class":581},"# number stored as text\n",[44,1600,1601,1603,1605,1607],{"class":46,"line":450},[44,1602,1094],{"class":92},[44,1604,1097],{"class":96},[44,1606,174],{"class":92},[44,1608,1498],{"class":96},[44,1610,1611,1613,1615,1617,1619,1622,1624,1627,1629,1632,1635,1638],{"class":46,"line":466},[44,1612,1503],{"class":96},[44,1614,1332],{"class":54},[44,1616,1509],{"class":96},[44,1618,119],{"class":92},[44,1620,1621],{"class":96}," df.loc[i, ",[44,1623,1332],{"class":54},[44,1625,1626],{"class":96},"].strftime(",[44,1628,200],{"class":54},[44,1630,1631],{"class":203},"%d",[44,1633,1634],{"class":54},"\u002F%m\u002F%Y\"",[44,1636,1637],{"class":96},")   ",[44,1639,1640],{"class":581},"# date as text\n",[44,1642,1643,1646,1648,1651,1654,1657,1659,1662,1664,1666,1669,1672,1675,1677,1680],{"class":46,"line":471},[44,1644,1645],{"class":96},"    df.loc[fake.random_int(",[44,1647,659],{"class":153},[44,1649,1650],{"class":96},", n ",[44,1652,1653],{"class":92},"-",[44,1655,1656],{"class":153}," 1",[44,1658,358],{"class":96},[44,1660,1661],{"class":54},"\"SKU\"",[44,1663,1509],{"class":96},[44,1665,119],{"class":92},[44,1667,1668],{"class":54}," \"=HYPERLINK(",[44,1670,1671],{"class":203},"\\\"",[44,1673,1674],{"class":54},"http:\u002F\u002Fexample.com",[44,1676,1671],{"class":203},[44,1678,1679],{"class":54},")\"",[44,1681,1682],{"class":581},"  # formula-like text\n",[44,1684,1685,1687],{"class":46,"line":481},[44,1686,928],{"class":92},[44,1688,1689],{"class":96}," df\n",[44,1691,1692],{"class":46,"line":503},[44,1693,110],{"emptyLinePlaceholder":109},[44,1695,1696,1699,1701],{"class":46,"line":516},[44,1697,1698],{"class":96},"messy ",[44,1700,119],{"class":92},[44,1702,1703],{"class":96}," make_messy(ords)\n",[44,1705,1706,1708,1710,1713,1716,1718],{"class":46,"line":563},[44,1707,594],{"class":153},[44,1709,180],{"class":96},[44,1711,1712],{"class":54},"\"blank prices:\"",[44,1714,1715],{"class":96},", messy[",[44,1717,1506],{"class":54},[44,1719,1720],{"class":96},"].isna().sum(),\n",[44,1722,1723,1726,1728,1731,1733,1736,1739,1742,1745,1747,1750,1752,1755,1757],{"class":46,"line":568},[44,1724,1725],{"class":54},"      \"| text qty:\"",[44,1727,128],{"class":96},[44,1729,1730],{"class":153},"sum",[44,1732,180],{"class":96},[44,1734,1735],{"class":153},"isinstance",[44,1737,1738],{"class":96},"(v, ",[44,1740,1741],{"class":153},"str",[44,1743,1744],{"class":96},") ",[44,1746,168],{"class":92},[44,1748,1749],{"class":96}," v ",[44,1751,174],{"class":92},[44,1753,1754],{"class":96}," messy[",[44,1756,1580],{"class":54},[44,1758,1759],{"class":96},"]),\n",[44,1761,1762,1765,1767,1769,1771,1773,1775,1777,1779,1781,1783,1785,1787,1789],{"class":46,"line":585},[44,1763,1764],{"class":54},"      \"| text dates:\"",[44,1766,128],{"class":96},[44,1768,1730],{"class":153},[44,1770,180],{"class":96},[44,1772,1735],{"class":153},[44,1774,1738],{"class":96},[44,1776,1741],{"class":153},[44,1778,1744],{"class":96},[44,1780,168],{"class":92},[44,1782,1749],{"class":96},[44,1784,174],{"class":92},[44,1786,1754],{"class":96},[44,1788,1332],{"class":54},[44,1790,1759],{"class":96},[44,1792,1793,1796,1798,1801,1803,1806,1809,1811,1814,1816,1818,1820,1822,1824,1826,1828],{"class":46,"line":591},[44,1794,1795],{"class":54},"      \"| odd categories:\"",[44,1797,128],{"class":96},[44,1799,1800],{"class":153},"sorted",[44,1802,180],{"class":96},[44,1804,1805],{"class":153},"set",[44,1807,1808],{"class":96},"(messy[",[44,1810,1534],{"class":54},[44,1812,1813],{"class":96},"]) ",[44,1815,1653],{"class":92},[44,1817,216],{"class":96},[44,1819,344],{"class":54},[44,1821,128],{"class":96},[44,1823,361],{"class":54},[44,1825,128],{"class":96},[44,1827,375],{"class":54},[44,1829,1830],{"class":96},"}))\n",[10,1832,1833,1834,128,1837,1840,1841,1844,1845,1848,1849,1852,1853,28],{},"About 3% of rows get each problem, chosen by the seeded generator, so the same rows are messy every run and a failing test points at the same row each time. Each injected problem corresponds to a real bug report: a blank price that turns a total into ",[19,1835,1836],{},"NaN",[19,1838,1839],{},"\" tools \""," that creates a fourth category in a pivot, ",[19,1842,1843],{},"\"7\""," that sorts after ",[19,1846,1847],{},"\"19\"",", a ",[19,1850,1851],{},"dd\u002Fmm\u002Fyyyy"," string that pandas reads as text, and a formula-like value that becomes live if written back to Excel unescaped. The cleaning side of these problems is covered in ",[24,1854,1856],{"href":1855},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstrip-whitespace-and-normalise-text-columns-with-pandas\u002F","Strip Whitespace and Normalise Text Columns with pandas",[638,1858,647,1864,647,1867,647,1870,647,1875,647,1881,647,1885,647,1891,647,1895,647,1898,647,1902,647,1906,647,1910,647,1914,647,1918,647,1922,647,1926,647,1930,647,1934,647,1938,647,1941,647,1945,647,1947,647,1950,647,1952,647,1955,647,1957,647,1960,647,1962,647,1965,647,1967,647,1970,647,1973,647,1976,647,1978,647,1982,647,1984,647,1987,647,1989,647,1992,647,1994,647,1997,647,1999,647,2002,647,2004,647,2007,647,2010,647,2014,647,2016,647,2019,647,2021,647,2024,647,2026,647,2029,647,2031,647,2034,647,2036,647,2038,647,2042,647,2045,647,2048,647,2050,647,2053,647,2055,647,2057,647,2059,647,2062,647,2064,647,2068,647,2070,647,2073,647,2075,647,2078,647,2081,647,2085,647,2087,647,2090,647,2092,647,2095,647,2097,647,2100,647,2102,647,2105,647,2107,647,2110,647,2112,647,2115,647,2118,647,2121,647,2123,647,2126,647,2128,647,2131,647,2133,647,2136,647,2138,647,2140,647,2142,647,2144,647,2146],{"viewBox":1859,"role":641,"ariaLabelledBy":1860,"xmlns":645,"style":1863},"121 0 518 224",[1861,1862],"fk-messy-t","fk-messy-d","width:100%;max-width:518px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[649,1865,1866],{"id":1861},"A few rows of the messy Orders sheet",[653,1868,1869],{"id":1862},"Generated orders with injected problems. One row has a blank unit price, one has the category written as lower case with spaces, one has the quantity stored as text and one has the order date as a text string in day-month-year order.",[657,1871],{"x":1872,"y":659,"width":1873,"height":1874,"fill":662},"121","518","224",[657,1876],{"x":1877,"y":666,"width":1878,"height":1879,"rx":659,"fill":670,"stroke":671,"style":1880},"137.3","32.0","24.0","stroke-width:1px",[657,1882],{"x":1883,"y":666,"width":1884,"height":1879,"rx":659,"fill":670,"stroke":671,"style":1880},"169.3","76.7",[685,1886,1890],{"x":1887,"y":1888,"style":1889},"207.7","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[657,1892],{"x":1893,"y":666,"width":1894,"height":1879,"rx":659,"fill":670,"stroke":671,"style":1880},"246.0","90.8",[685,1896,1897],{"x":741,"y":1888,"style":1889},"B",[657,1899],{"x":1900,"y":666,"width":1901,"height":1879,"rx":659,"fill":670,"stroke":671,"style":1880},"336.8","91.8",[685,1903,1905],{"x":1904,"y":1888,"style":1889},"382.7","C",[657,1907],{"x":1908,"y":666,"width":1909,"height":1879,"rx":659,"fill":670,"stroke":671,"style":1880},"428.6","74.0",[685,1911,1913],{"x":1912,"y":1888,"style":1889},"465.6","D",[657,1915],{"x":1916,"y":666,"width":1917,"height":1879,"rx":659,"fill":670,"stroke":671,"style":1880},"502.6","44.0",[685,1919,1921],{"x":1920,"y":1888,"style":1889},"524.6","E",[657,1923],{"x":1924,"y":666,"width":1925,"height":1879,"rx":659,"fill":670,"stroke":671,"style":1880},"546.6","76.0",[685,1927,1929],{"x":1928,"y":1888,"style":1889},"584.6","F",[657,1931],{"x":1877,"y":1932,"width":1878,"height":1933,"rx":659,"fill":670,"stroke":671,"style":1880},"40.0","28.0",[685,1935,1057],{"x":1936,"y":1937,"style":1889},"153.3","59.0",[657,1939],{"x":1883,"y":1932,"width":1884,"height":1933,"rx":659,"fill":717,"stroke":708,"style":1940},"stroke-width:1.5px",[685,1942,1944],{"x":1887,"y":1937,"style":1943},"font-size:10.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Order ID",[657,1946],{"x":1893,"y":1932,"width":1894,"height":1933,"rx":659,"fill":717,"stroke":708,"style":1940},[685,1948,1949],{"x":741,"y":1937,"style":1943},"Customer ID",[657,1951],{"x":1900,"y":1932,"width":1901,"height":1933,"rx":659,"fill":717,"stroke":708,"style":1940},[685,1953,1954],{"x":1904,"y":1937,"style":1943},"Order date",[657,1956],{"x":1908,"y":1932,"width":1909,"height":1933,"rx":659,"fill":717,"stroke":708,"style":1940},[685,1958,1959],{"x":1912,"y":1937,"style":1943},"Category",[657,1961],{"x":1916,"y":1932,"width":1917,"height":1933,"rx":659,"fill":717,"stroke":708,"style":1940},[685,1963,1964],{"x":1920,"y":1937,"style":1943},"Qty",[657,1966],{"x":1924,"y":1932,"width":1925,"height":1933,"rx":659,"fill":717,"stroke":708,"style":1940},[685,1968,1969],{"x":1928,"y":1937,"style":1943},"Unit price",[657,1971],{"x":1877,"y":1972,"width":1878,"height":1933,"rx":659,"fill":670,"stroke":671,"style":1880},"68.0",[685,1974,380],{"x":1936,"y":1975,"style":1889},"87.0",[657,1977],{"x":1883,"y":1972,"width":1884,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,1979,1981],{"x":1887,"y":1975,"style":1980},"font-size:10.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","SO-10012",[657,1983],{"x":1893,"y":1972,"width":1894,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,1985,1986],{"x":741,"y":1975,"style":1980},"C4821",[657,1988],{"x":1900,"y":1972,"width":1901,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,1990,1991],{"x":1904,"y":1975,"style":1980},"2026-07-19",[657,1993],{"x":1908,"y":1972,"width":1909,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,1995,1996],{"x":1912,"y":1975,"style":1980},"Tools",[657,1998],{"x":1916,"y":1972,"width":1917,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2000,2001],{"x":1920,"y":1975,"style":1980},"6",[657,2003],{"x":1924,"y":1972,"width":1925,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2005,2006],{"x":1928,"y":1975,"style":1980},"142.80",[657,2008],{"x":1877,"y":2009,"width":1878,"height":1933,"rx":659,"fill":670,"stroke":671,"style":1880},"96.0",[685,2011,2013],{"x":1936,"y":2012,"style":1889},"115.0","3",[657,2015],{"x":1883,"y":2009,"width":1884,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2017,2018],{"x":1887,"y":2012,"style":1980},"SO-10013",[657,2020],{"x":1893,"y":2009,"width":1894,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2022,2023],{"x":741,"y":2012,"style":1980},"C1937",[657,2025],{"x":1900,"y":2009,"width":1901,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2027,2028],{"x":1904,"y":2012,"style":1980},"2026-08-02",[657,2030],{"x":1908,"y":2009,"width":1909,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2032,2033],{"x":1912,"y":2012,"style":1980},"Garden",[657,2035],{"x":1916,"y":2009,"width":1917,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2037,2013],{"x":1920,"y":2012,"style":1980},[657,2039],{"x":1924,"y":2009,"width":1925,"height":1933,"rx":659,"fill":2040,"stroke":2041,"style":1940},"#fce9e9","var(--danger,#dc2626)",[657,2043],{"x":1877,"y":2044,"width":1878,"height":1933,"rx":659,"fill":670,"stroke":671,"style":1880},"124.0",[685,2046,183],{"x":1936,"y":2047,"style":1889},"143.0",[657,2049],{"x":1883,"y":2044,"width":1884,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2051,2052],{"x":1887,"y":2047,"style":1980},"SO-10014",[657,2054],{"x":1893,"y":2044,"width":1894,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2056,1986],{"x":741,"y":2047,"style":1980},[657,2058],{"x":1900,"y":2044,"width":1901,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2060,2061],{"x":1904,"y":2047,"style":1980},"2026-08-05",[657,2063],{"x":1908,"y":2044,"width":1909,"height":1933,"rx":659,"fill":779,"stroke":780,"style":1940},[685,2065,2067],{"x":1912,"y":2047,"style":2066},"font-size:10.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle"," garden ",[657,2069],{"x":1916,"y":2044,"width":1917,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2071,2072],{"x":1920,"y":2047,"style":1980},"11",[657,2074],{"x":1924,"y":2044,"width":1925,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2076,2077],{"x":1928,"y":2047,"style":1980},"37.15",[657,2079],{"x":1877,"y":2080,"width":1878,"height":1933,"rx":659,"fill":670,"stroke":671,"style":1880},"152.0",[685,2082,2084],{"x":1936,"y":2083,"style":1889},"171.0","5",[657,2086],{"x":1883,"y":2080,"width":1884,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2088,2089],{"x":1887,"y":2083,"style":1980},"SO-10015",[657,2091],{"x":1893,"y":2080,"width":1894,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2093,2094],{"x":741,"y":2083,"style":1980},"C6604",[657,2096],{"x":1900,"y":2080,"width":1901,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2098,2099],{"x":1904,"y":2083,"style":1980},"2026-09-14",[657,2101],{"x":1908,"y":2080,"width":1909,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2103,2104],{"x":1912,"y":2083,"style":1980},"Safety",[657,2106],{"x":1916,"y":2080,"width":1917,"height":1933,"rx":659,"fill":779,"stroke":780,"style":1940},[685,2108,2109],{"x":1920,"y":2083,"style":2066},"'4",[657,2111],{"x":1924,"y":2080,"width":1925,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2113,2114],{"x":1928,"y":2083,"style":1980},"9.90",[657,2116],{"x":1877,"y":2117,"width":1878,"height":1933,"rx":659,"fill":670,"stroke":671,"style":1880},"180.0",[685,2119,2001],{"x":1936,"y":2120,"style":1889},"199.0",[657,2122],{"x":1883,"y":2117,"width":1884,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2124,2125],{"x":1887,"y":2120,"style":1980},"SO-10016",[657,2127],{"x":1893,"y":2117,"width":1894,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2129,2130],{"x":741,"y":2120,"style":1980},"C2210",[657,2132],{"x":1900,"y":2117,"width":1901,"height":1933,"rx":659,"fill":779,"stroke":780,"style":1940},[685,2134,2135],{"x":1904,"y":2120,"style":2066},"21\u002F09\u002F2026",[657,2137],{"x":1908,"y":2117,"width":1909,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2139,1996],{"x":1912,"y":2120,"style":1980},[657,2141],{"x":1916,"y":2117,"width":1917,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2143,380],{"x":1920,"y":2120,"style":1980},[657,2145],{"x":1924,"y":2117,"width":1925,"height":1933,"rx":659,"fill":662,"stroke":671,"style":1880},[685,2147,2148],{"x":1928,"y":2120,"style":1980},"88.00",[30,2150,2152],{"id":2151},"step-5-write-the-workbook-and-use-it-in-pytest","Step 5 — Write the workbook and use it in pytest",[10,2154,2155],{},"Write both sheets with a layout like the real export — including, if the real one has them, a title row above the header:",[35,2157,2159],{"className":83,"code":2158,"language":85,"meta":40,"style":40},"def write_test_workbook(path, seed=20261002, n_customers=40, n_orders=400):\n    Faker.seed(seed)\n    fake.unique.clear()\n    cust = customers(n_customers)\n    ords = make_messy(orders(cust, n_orders))\n    with pd.ExcelWriter(path, engine=\"openpyxl\") as xw:\n        cust.to_excel(xw, sheet_name=\"Customers\", index=False)\n        ords.to_excel(xw, sheet_name=\"Orders\", index=False, startrow=2)\n        xw.sheets[\"Orders\"][\"A1\"] = \"Order export — generated test data\"\n    return path\n\na = pd.read_excel(write_test_workbook(\"test_a.xlsx\"), sheet_name=\"Orders\", header=2)\nb = pd.read_excel(write_test_workbook(\"test_b.xlsx\"), sheet_name=\"Orders\", header=2)\nprint(\"same data from the same seed:\", a.equals(b), \"|\", a.shape)\n",[19,2160,2161,2191,2196,2201,2211,2221,2244,2269,2300,2320,2327,2331,2363,2393],{"__ignoreMap":40},[44,2162,2163,2165,2168,2171,2173,2175,2178,2180,2182,2185,2187,2189],{"class":46,"line":47},[44,2164,870],{"class":92},[44,2166,2167],{"class":400}," write_test_workbook",[44,2169,2170],{"class":96},"(path, seed",[44,2172,119],{"class":92},[44,2174,154],{"class":153},[44,2176,2177],{"class":96},", n_customers",[44,2179,119],{"class":92},[44,2181,1273],{"class":153},[44,2183,2184],{"class":96},", n_orders",[44,2186,119],{"class":92},[44,2188,1289],{"class":153},[44,2190,186],{"class":96},[44,2192,2193],{"class":46,"line":106},[44,2194,2195],{"class":96},"    Faker.seed(seed)\n",[44,2197,2198],{"class":46,"line":113},[44,2199,2200],{"class":96},"    fake.unique.clear()\n",[44,2202,2203,2206,2208],{"class":46,"line":147},[44,2204,2205],{"class":96},"    cust ",[44,2207,119],{"class":92},[44,2209,2210],{"class":96}," customers(n_customers)\n",[44,2212,2213,2216,2218],{"class":46,"line":160},[44,2214,2215],{"class":96},"    ords ",[44,2217,119],{"class":92},[44,2219,2220],{"class":96}," make_messy(orders(cust, n_orders))\n",[44,2222,2223,2226,2229,2232,2234,2237,2239,2241],{"class":46,"line":165},[44,2224,2225],{"class":92},"    with",[44,2227,2228],{"class":96}," pd.ExcelWriter(path, ",[44,2230,2231],{"class":424},"engine",[44,2233,119],{"class":92},[44,2235,2236],{"class":54},"\"openpyxl\"",[44,2238,1744],{"class":96},[44,2240,846],{"class":92},[44,2242,2243],{"class":96}," xw:\n",[44,2245,2246,2249,2252,2254,2257,2259,2262,2264,2267],{"class":46,"line":189},[44,2247,2248],{"class":96},"        cust.to_excel(xw, ",[44,2250,2251],{"class":424},"sheet_name",[44,2253,119],{"class":92},[44,2255,2256],{"class":54},"\"Customers\"",[44,2258,128],{"class":96},[44,2260,2261],{"class":424},"index",[44,2263,119],{"class":92},[44,2265,2266],{"class":153},"False",[44,2268,157],{"class":96},[44,2270,2271,2274,2276,2278,2281,2283,2285,2287,2289,2291,2294,2296,2298],{"class":46,"line":407},[44,2272,2273],{"class":96},"        ords.to_excel(xw, ",[44,2275,2251],{"class":424},[44,2277,119],{"class":92},[44,2279,2280],{"class":54},"\"Orders\"",[44,2282,128],{"class":96},[44,2284,2261],{"class":424},[44,2286,119],{"class":92},[44,2288,2266],{"class":153},[44,2290,128],{"class":96},[44,2292,2293],{"class":424},"startrow",[44,2295,119],{"class":92},[44,2297,380],{"class":153},[44,2299,157],{"class":96},[44,2301,2302,2305,2307,2310,2313,2315,2317],{"class":46,"line":435},[44,2303,2304],{"class":96},"        xw.sheets[",[44,2306,2280],{"class":54},[44,2308,2309],{"class":96},"][",[44,2311,2312],{"class":54},"\"A1\"",[44,2314,1509],{"class":96},[44,2316,119],{"class":92},[44,2318,2319],{"class":54}," \"Order export — generated test data\"\n",[44,2321,2322,2324],{"class":46,"line":440},[44,2323,928],{"class":92},[44,2325,2326],{"class":96}," path\n",[44,2328,2329],{"class":46,"line":450},[44,2330,110],{"emptyLinePlaceholder":109},[44,2332,2333,2336,2338,2341,2344,2346,2348,2350,2352,2354,2357,2359,2361],{"class":46,"line":466},[44,2334,2335],{"class":96},"a ",[44,2337,119],{"class":92},[44,2339,2340],{"class":96}," pd.read_excel(write_test_workbook(",[44,2342,2343],{"class":54},"\"test_a.xlsx\"",[44,2345,358],{"class":96},[44,2347,2251],{"class":424},[44,2349,119],{"class":92},[44,2351,2280],{"class":54},[44,2353,128],{"class":96},[44,2355,2356],{"class":424},"header",[44,2358,119],{"class":92},[44,2360,380],{"class":153},[44,2362,157],{"class":96},[44,2364,2365,2368,2370,2372,2375,2377,2379,2381,2383,2385,2387,2389,2391],{"class":46,"line":471},[44,2366,2367],{"class":96},"b ",[44,2369,119],{"class":92},[44,2371,2340],{"class":96},[44,2373,2374],{"class":54},"\"test_b.xlsx\"",[44,2376,358],{"class":96},[44,2378,2251],{"class":424},[44,2380,119],{"class":92},[44,2382,2280],{"class":54},[44,2384,128],{"class":96},[44,2386,2356],{"class":424},[44,2388,119],{"class":92},[44,2390,380],{"class":153},[44,2392,157],{"class":96},[44,2394,2395,2397,2399,2402,2405,2408],{"class":46,"line":481},[44,2396,594],{"class":153},[44,2398,180],{"class":96},[44,2400,2401],{"class":54},"\"same data from the same seed:\"",[44,2403,2404],{"class":96},", a.equals(b), ",[44,2406,2407],{"class":54},"\"|\"",[44,2409,2410],{"class":96},", a.shape)\n",[10,2412,2413,2414,2417],{},"Two files written with the same seed contain identical data, so a test that fails today fails the same way tomorrow. The title row and ",[19,2415,2416],{},"startrow=2"," mimic an export with a heading above the table, which is exactly the kind of layout detail that breaks a reader built against a tidy sample. A pytest fixture then gives each test a fresh copy in a temporary directory:",[35,2419,2421],{"className":83,"code":2420,"language":85,"meta":40,"style":40},"# conftest.py\nimport pytest\nfrom testdata import write_test_workbook\n\n@pytest.fixture\ndef orders_workbook(tmp_path):\n    return write_test_workbook(tmp_path \u002F \"orders.xlsx\")\n\n# test_report.py\ndef test_report_ignores_blank_prices(orders_workbook):\n    report = build_report(orders_workbook)\n    assert report[\"Revenue\"].notna().all()\n",[19,2422,2423,2428,2434,2446,2450,2455,2465,2480,2484,2489,2499,2509],{"__ignoreMap":40},[44,2424,2425],{"class":46,"line":47},[44,2426,2427],{"class":581},"# conftest.py\n",[44,2429,2430,2432],{"class":46,"line":106},[44,2431,100],{"class":92},[44,2433,67],{"class":96},[44,2435,2436,2438,2441,2443],{"class":46,"line":113},[44,2437,93],{"class":92},[44,2439,2440],{"class":96}," testdata ",[44,2442,100],{"class":92},[44,2444,2445],{"class":96}," write_test_workbook\n",[44,2447,2448],{"class":46,"line":147},[44,2449,110],{"emptyLinePlaceholder":109},[44,2451,2452],{"class":46,"line":160},[44,2453,2454],{"class":400},"@pytest.fixture\n",[44,2456,2457,2459,2462],{"class":46,"line":165},[44,2458,870],{"class":92},[44,2460,2461],{"class":400}," orders_workbook",[44,2463,2464],{"class":96},"(tmp_path):\n",[44,2466,2467,2469,2472,2475,2478],{"class":46,"line":189},[44,2468,928],{"class":92},[44,2470,2471],{"class":96}," write_test_workbook(tmp_path ",[44,2473,2474],{"class":92},"\u002F",[44,2476,2477],{"class":54}," \"orders.xlsx\"",[44,2479,157],{"class":96},[44,2481,2482],{"class":46,"line":407},[44,2483,110],{"emptyLinePlaceholder":109},[44,2485,2486],{"class":46,"line":435},[44,2487,2488],{"class":581},"# test_report.py\n",[44,2490,2491,2493,2496],{"class":46,"line":440},[44,2492,870],{"class":92},[44,2494,2495],{"class":400}," test_report_ignores_blank_prices",[44,2497,2498],{"class":96},"(orders_workbook):\n",[44,2500,2501,2504,2506],{"class":46,"line":450},[44,2502,2503],{"class":96},"    report ",[44,2505,119],{"class":92},[44,2507,2508],{"class":96}," build_report(orders_workbook)\n",[44,2510,2511,2514,2517,2520],{"class":46,"line":466},[44,2512,2513],{"class":92},"    assert",[44,2515,2516],{"class":96}," report[",[44,2518,2519],{"class":54},"\"Revenue\"",[44,2521,2522],{"class":96},"].notna().all()\n",[10,2524,2525,2528,2529,2532,2533,28],{},[19,2526,2527],{},"tmp_path"," is pytest's per-test temporary directory, so tests never share or leave behind files. Use a different seed, or several via ",[19,2530,2531],{},"pytest.mark.parametrize",", to explore more cases; keep one fixed seed for the regression tests whose expected values you assert. Building fixtures without touching disk at all is covered in ",[24,2534,2536],{"href":2535},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fcreate-excel-test-fixtures-in-memory\u002F","Create Excel Test Fixtures in Memory",[638,2538,647,2543,647,2546,647,2549,647,2552,647,2557,647,2565,647,2571,647,2576,647,2581,647,2585,647,2589,647,2592,647,2598,647,2603,647,2607,647,2610,647,2613],{"viewBox":2539,"role":641,"ariaLabelledBy":2540,"xmlns":645,"style":646},"4 0 752 207",[2541,2542],"fk-vs-t","fk-vs-d",[649,2544,2545],{"id":2541},"Real exports versus generated test data",[653,2547,2548],{"id":2542},"A copy of a real export contains personal data, cannot be shared or committed, and lacks rare problems. Generated data is safe to commit, reproducible from a seed, and contains every problem you choose to inject.",[657,2550],{"x":183,"y":659,"width":660,"height":2551,"fill":662},"207",[657,2553],{"x":665,"y":666,"width":2554,"height":2555,"rx":2556,"fill":2040,"stroke":2041,"style":672},"351.0","175.0","14",[674,2558,2560],{"transform":2559},"translate(182.5,29.0) scale(1.083)",[678,2561],{"d":2562,"fill":681,"stroke":2563,"style":2564},"M6 11h12v9H6z M8.5 11V8a3.5 3.5 0 0 1 7 0v3","var(--accent-ink,#be185d)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[685,2566,2570],{"x":2567,"y":2568,"style":2569},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","copy of a real export",[46,2572],{"x1":2573,"y1":2574,"x2":2575,"y2":2574,"stroke":2041,"style":1880},"36.0","91.0","355.0",[685,2577,2580],{"x":2567,"y":2578,"style":2579},"111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","personal data inside",[685,2582,2584],{"x":2567,"y":2583,"style":2579},"137.0","can't go in the repository",[685,2586,2588],{"x":2567,"y":2587,"style":2579},"163.0","only the problems it happens to have",[657,2590],{"x":2591,"y":666,"width":2554,"height":2555,"rx":2556,"fill":749,"stroke":750,"style":672},"389.0",[674,2593,2595],{"transform":2594},"translate(551.5,29.0) scale(1.083)",[678,2596],{"d":2597,"fill":681,"stroke":757,"style":2564},"M5 12.5l4.5 4.5L19 7",[685,2599,2602],{"x":2600,"y":2568,"style":2601},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","seeded Faker data",[46,2604],{"x1":2605,"y1":2574,"x2":2606,"y2":2574,"stroke":750,"style":1880},"405.0","724.0",[685,2608,2609],{"x":2600,"y":2578,"style":2579},"safe to commit and share",[685,2611,2612],{"x":2600,"y":2583,"style":2579},"same file from the same seed",[685,2614,2615],{"x":2600,"y":2587,"style":2579},"every problem you inject",[30,2617,2619],{"id":2618},"common-pitfalls","Common pitfalls",[2621,2622,2623,2631,2637,2643,2649],"ul",{},[2624,2625,2626,2630],"li",{},[2627,2628,2629],"strong",{},"Unseeded generators in tests."," Different data each run makes failures unreproducible.",[2624,2632,2633,2636],{},[2627,2634,2635],{},"Only clean data."," Inject blanks, spaces and text values, or the tests prove little.",[2624,2638,2639,2642],{},[2627,2640,2641],{},"Implausible values."," Keep amounts, dates and quantities in realistic ranges.",[2624,2644,2645,2648],{},[2627,2646,2647],{},"Orphan foreign keys."," Generate parents first and pick child keys from them.",[2624,2650,2651,2654],{},[2627,2652,2653],{},"Asserting exact generated values across Faker upgrades."," Pin Faker or assert on properties.",[30,2656,2658],{"id":2657},"conclusion","Conclusion",[10,2660,2661],{},"A seeded Faker generator turns test data from a liability into an asset: multi-locale names and addresses, a custom provider for your own codes, related sheets whose keys always join, and a controlled dose of the blanks, stray spaces, text numbers and text dates real exports contain. Write it in the same layout as the real export, generate it in a pytest fixture, and the report code is tested against realistic, shareable, reproducible workbooks.",[30,2663,2665],{"id":2664},"frequently-asked-questions","Frequently asked questions",[10,2667,2668,2671],{},[2627,2669,2670],{},"How do I generate fake data for Excel with Python?","\nUse Faker to generate values such as names, companies, cities and dates in a loop, collect them in a pandas DataFrame, and write it with to_excel.",[10,2673,2674,2677],{},[2627,2675,2676],{},"How do I make Faker produce the same data every time?","\nCall Faker.seed with a fixed number before generating, or seed_instance on a specific Faker instance. The same seed and Faker version produce the same values.",[10,2679,2680,2683],{},[2627,2681,2682],{},"How do I create my own fake values such as product codes?","\nWrite a class that inherits from BaseProvider with methods such as sku, using bothify or random_element, and register it with add_provider — on each generator in fake.factories when Faker was created with several locales.",[10,2685,2686,2689],{},[2627,2687,2688],{},"How do I make test data messy like real Excel exports?","\nAfter generating clean data, set a small seeded fraction of cells to blanks, add spaces or change case in text, store some numbers and dates as text, and include formula-like strings.",[30,2691,2693],{"id":2692},"related","Related",[2621,2695,2696,2702,2709,2716],{},[2624,2697,2698,2699,2701],{},"Up one level: ",[24,2700,27],{"href":26}," — making report code reliable.",[2624,2703,2704,2708],{},[24,2705,2707],{"href":2706},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Ftest-excel-cleaning-code-with-hypothesis\u002F","Test Excel Cleaning Code with Hypothesis"," — generated inputs that hunt for bugs.",[2624,2710,2711,2715],{},[24,2712,2714],{"href":2713},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fcompare-excel-output-to-a-golden-file-in-tests\u002F","Compare Excel Output to a Golden File in Tests"," — checking the report built from this data.",[2624,2717,2718,2722],{},[24,2719,2721],{"href":2720},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fprevent-formula-injection-in-excel-exports\u002F","Prevent Formula Injection in Excel Exports"," — handling the formula-like values.",[2724,2725,2726],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}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 .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sHg5J, html code.shiki .sHg5J{--shiki-default:#6F42C1;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":40,"searchDepth":106,"depth":106,"links":2728},[2729,2730,2731,2732,2733,2734,2735,2736,2737,2738],{"id":32,"depth":106,"text":33},{"id":73,"depth":106,"text":74},{"id":257,"depth":106,"text":258},{"id":827,"depth":106,"text":828},{"id":1377,"depth":106,"text":1378},{"id":2151,"depth":106,"text":2152},{"id":2618,"depth":106,"text":2619},{"id":2657,"depth":106,"text":2658},{"id":2664,"depth":106,"text":2665},{"id":2692,"depth":106,"text":2693},"2026-10-02","Build believable, reproducible test workbooks for Excel report code with Faker and pandas: seeded generators, locale-specific names and addresses, a custom provider for your own codes, related sheets with valid foreign keys, deliberate messiness such as blanks, stray spaces and text dates, unique values, and a pytest fixture that writes the file.","md",[2743,2745,2747,2749],{"q":2670,"a":2744},"Use Faker to generate values such as names, companies, cities and dates in a loop, collect them in a pandas DataFrame, and write it with to_excel.",{"q":2676,"a":2746},"Call Faker.seed with a fixed number before generating, or seed_instance on a specific Faker instance. The same seed and Faker version produce the same values.",{"q":2682,"a":2748},"Write a class that inherits from BaseProvider with methods such as sku, using bothify or random_element, and register it with add_provider — on each generator in fake.factories when Faker was created with several locales.",{"q":2688,"a":2750},"After generating clean data, set a small seeded fraction of cells to blanks, add spaces or change case in text, store some numbers and dates as text, and include formula-like strings.","generate realistic test data for excel reports with faker",null,{"breadcrumb":2754},[2755,2757,2760],{"name":2756,"item":2474},"Home",{"name":2758,"item":2759},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":27,"item":26},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fgenerate-realistic-test-data-for-excel-reports-with-faker",{"title":2763,"description":2764},"Generate Realistic Excel Test Data with Faker in Python","Create seeded, realistic test workbooks with Faker: locales, custom providers, related customer and order sheets, injected blanks and bad values, unique keys and a pytest fixture for Excel report tests.","generate-realistic-test-data-for-excel-reports-with-faker","automating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fgenerate-realistic-test-data-for-excel-reports-with-faker\u002Findex",[2768,2769],"reliable-jobs","pandas","how-to","bc2SOsfKiCwdWyzU1jbRWwMz74rg97j9UW-7WdO3j7s",[2773,2777],{"title":2774,"path":2775,"stem":2776,"children":-1},"Diff Excel Files in Git with a textconv Driver","\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fdiff-excel-files-in-git-with-a-textconv-driver","automating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fdiff-excel-files-in-git-with-a-textconv-driver\u002Findex",{"title":2778,"path":2779,"stem":2780,"children":-1},"Keep Excel Report Settings in a Config File","\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fkeep-excel-report-settings-in-a-config-file","automating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fkeep-excel-report-settings-in-a-config-file\u002Findex",[2782,2784],{"path":2783,"title":2758},"\u002Fautomating-reporting-workflows",{"path":2785,"title":27},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts",[],[2788,2792,2795,2798,2801,2804],{"path":2789,"title":2790,"kind":2791},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation","Error Handling and Logging in Excel Automation","guide",{"path":2793,"title":2794,"kind":2770},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fdetect-a-scheduled-report-that-did-not-run","Detect a Scheduled Report That Did Not Run",{"path":2796,"title":2797,"kind":2770},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fignore-excel-lock-files-when-processing-a-folder","Ignore Excel Lock Files When Processing a Folder",{"path":2799,"title":2800,"kind":2770},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Flog-python-excel-script-output-to-a-file","Log Python Excel Script Output to a File",{"path":2802,"title":2803,"kind":2770},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fmake-an-excel-report-job-safe-to-rerun","Make an Excel Report Job Safe to Rerun",{"path":2805,"title":2806,"kind":2770},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fprevent-formula-injection-in-excel-exports","Prevent Formula Injection in Excel Exports from Python",1790972142649]