Komentář k draftu o dominanci unittestů

Měl jsem příležitost konfrontovat draft s čerstvou zkušeností: vícedenní refaktor extrakční pipeline, při kterém se pod ~28 stagemi vyměnil celý storage engine (klíčované stores → append-only záznamy s meta adresováním), změnil se wire kontrakt API a přepsala se vnitřní choreografie tří stagí. Testovací sada ~650 testů, z toho silná vrstva e2e testů s nahranými HTTP kazetami (VCR), které porovnávají odchozí LLM requesty byte po bytu.

Hlavní zpráva: žádná teze článku se v praxi nevyvrátila. Skoro každá má z toho refaktoru konkrétní inkarnaci. Zároveň praxe dala čtyři jemnější rozlišení, která v textu chybí — a aspoň dvě z nich by myslím článek znatelně posílila.

Co refaktor potvrdil

„Změnil jsem něco lokálně správně, ale okolí čekalo něco jiného." Učebnicový exemplář: wire vstupy se překlopily z dictu na list záznamů a kód has_image = "image" in cascade_inputs.strings zůstal. Na listu je to validní Python, lokálně korektní — a tiše vždy False. Pipeline jen vynechala jednu stage, nic nespadlo. Žádná jednotka se nechovala špatně, systém ano. Chytil to přesně ten test, který článek doporučuje: e2e přes celý flow s nahranou hranicí — kazeta odmítla request, ve kterém chyběly čtyři řádky promptu. Je to mimochodem přesně položka „změní se shape dat" z tvého výčtu — kdyby článek chtěl jeden konkrétní příběh místo abstraktního výčtu, tenhle typ bugu je ideální kandidát.

„Nemockujte vnitřek, mockujte okolí" + „fake > mock". Sada měla typované fakes jen na hranicích (LLM služba, vyhledávač) a reálné vnitřní wiring. Výsledek za celý refaktor: na boundary fakes jsem nesáhl ani jednou a testy s reálným vnitřkem přežily výměnu celého úložiště. Rozbilo se v podstatě jen to, co znalo vnitřní strukturu.

Záporná hodnota testů znalých vnitřku. ~113 míst ve 14 souborech sahalo přímo na tvar úložiště (ctx.vars.string_outputs[(stage, key)] = ...). Při refaktoru se migrovala skriptem třikrát a nechytila přitom jedinou reálnou regresi. Tvoje formulace „implementace už nevypadá tak, jak jsem si ji představoval" je doslova to, co ty failury říkaly.

Poměr sady. Odhadem 90 % jistoty nesla hrstka kazetových e2e testů při zlomku údržbových nákladů; bez nich by refaktor téhle velikosti byl hazard. Projekt s opačným poměrem, než článek kritizuje, je zároveň důkaz, že doporučení funguje.

Čtyři místa k doostření

1. Coupling se schovává i ve fixtures, nejen v assertech

Článek míří na mocky a assert_called_once_with(...). Naše největší migrační zátěž ale měla behaviorální asserty a seed přes vnitřnosti: testy ověřovaly výsledek poctivě, jenže výchozí stav zakládaly přímými zápisy do úložiště, obcházením veřejného povrchu. Při změně úložiště se rozbily úplně stejně jako choreografické testy. „Seed-heavy" fixtures jsou tatáž nemoc jiným kanálem — test může mít čisté asserty a přesto být přibetonovaný k implementaci. Doporučil bych tomu věnovat odstavec vedle mock-heavy sekce: stav zakládej přes veřejné API (vstupní payload, veřejné operace), ne přes strukturu skladu.

2. Rozliš „rozbití na vnitřní změně" a „rozbití na změně kontraktu"

Při refaktoru spadlo dohromady ~350 testů. Z článku by čtenář mohl vyvodit „špatná sada". Jenže velká část byly contract testy padající na záměrném breaking change — testovaly wire tvar, který jsme měnili. To není křehkost; to je test dělající přesně svou práci. Užitečný indikátor z praxe: tyhle failury šly migrovat mechanicky skriptem, protože konzistentně testovaly tvar, který se konzistentně změnil. Křehké testy se poznají podle toho, že padají, aniž se změnil kontrakt. Věta „rozbije testy tehdy, když rozbije systém" to implikuje, ale explicitní rozlišení by čtenáře ochránilo před závěrem „testy, co se při refaktoru rozbily = špatné testy".

3. Šedá zóna: choreografie, která JE produkt

Měli jsme testy assertující tvar access-event logu (která operace zapíše Read, která Write, která nic). Podle taxonomie článku čistá choreografie → záporná hodnota. Jenže ten log je pozorovatelný kontrakt — konzumuje ho debugovací UI. Takže to jsou legitimní contract testy… a přesto se při každém vnitřním pivotu přepisovaly. Závěr z praxe: i legitimní choreografický kontrakt je drahý závazek a má se vědomě držet malý. Binární dělení „chování dobré / choreografie špatná" tenhle případ nezachytí — stačila by věta, že choreografie povýšená na produkt se testuje jako kontrakt, ale platí se jako choreografie.

4. Nika unittestů jde říct pozitivněji: jednotky s vlastním kontraktem

Sekce „hodnota v den vzniku" platí pro unittesty implementace. Při refaktoru ale vznikl klasický unittestový soubor na nový storage engine (match sémantika, last-write-wins, diagnostika chyb — deterministická logika bez závislostí): byl nepostradatelný při stavbě a pak přežil šest navazujících fází beze změny jediného řádku. Rozdíl není v izolovanosti, ale v tom, že ta jednotka má vlastní dokumentovatelný kontrakt. To je prakticky použitelné kritérium, které by sekci „Ano, unittesty mají své místo" dalo ostřejší hranu: pokud bys sémantiku jednotky dokázal sepsat jako dokumentaci pro cizího čtenáře, unittest na ni stárne dobře; pokud testuješ „co zrovna dělá můj kód", stárne jako pomníček.

Drobnost na závěr

Mentální experiment „jaký projekt bych chtěl převzít" je nejsilnější část textu — a dal by se uzavřít zpětným odkazem z praxe: projekt s tím správným poměrem se pozná podle toho, že velký refaktor je nudný. Failury jsou buď očekávané (kontrakt se měnil) nebo informativní (systém se rozbil), a ta jedna záludná regrese, kterou by nikdo nenapsal do unit testu, se chytí na hraně. Přesně tak to proběhlo.