Programování s AI agenty: zadání, testy a kontrola
Jak agentovi napsat zadání s kritérii přijetí, proč pracovat po malých krocích, pokus s mutačním testováním (kolik chyb odhalí testy šťastné cesty, okrajové případy a vlastnostní testy), kdo má psát testy a na co si dát pozor.
Série Základy AI Díl 82 z 83
- Mapa umělé inteligence: co je co a jak to do sebe zapadá
- Stručná historie umělé inteligence: od Turinga po jazykové modely
- Data, model, trénink, inference: základní pojmy na jednom příkladu
- Jak číst matematický zápis: symboly, indexy, sumy a kvantifikátory
- Množiny a relace: stavebnice, ze které se skládají definice v AI
- Funkce a zobrazení: proč je každý model v AI funkce
- Logika a důkazy: pravidla, podle kterých se v AI uvažuje
- Grafy a stromy: struktura, na které stojí hledání i neuronové sítě
- Lineární algebra pro strojové učení: vektory, matice a proč AI hlavně násobí
- Derivace, gradient a řetízkové pravidlo: jak model zjistí, kterým směrem se učit
- Gradientní sestup: jak se model učí
- Co je pravděpodobnost: od hodu kostkou ke Kolmogorovovým axiomům
- Podmíněná pravděpodobnost a Bayesova věta: jak změnit odhad, když se dozvíte něco nového
- Náhodná veličina a distribuční funkce: jedna funkce, která o rozdělení řekne všechno
- Funkce hustoty a pravděpodobnostní funkce: proč má spojitá veličina v bodě pravděpodobnost nula
- Střední hodnota, rozptyl a kovariance: kde rozdělení leží, jak je široké a co s čím souvisí
- Přehled pravděpodobnostních rozdělení: Bernoulli, binomické, Poisson, rovnoměrné, normální a exponenciální
- Zákon velkých čísel a centrální limitní věta: proč se průměry chovají tak předvídatelně
- Statistika a odhady parametrů: maximální věrohodnost, MAP a intervaly spolehlivosti
- Teorie informace: entropie, křížová entropie a KL divergence — odkud se berou ztrátové funkce
- Prohledávání stavového prostoru: BFS, DFS, UCS a A* s animacemi
- Hry a protivník: minimax, alfa-beta ořezávání a Monte Carlo tree search
- Splňování omezujících podmínek: backtracking, propagace omezení a AC-3 na sudoku a mapě krajů
- Logika a znalostní systémy: dopředné a zpětné řetězení, rezoluce a expertní systémy
- Plánování: STRIPS, svět kostek, heuristiky z relaxace a plánování v AI agentech
- Lokální hledání a simulované žíhání: obchodní cestující přes krajská města
- Genetické algoritmy: populace, křížení, mutace a výběr na batohu na výlet
- Evoluční strategie a genetické programování: od pravidla jedné pětiny po Keplerův zákon
- Optimalizace mravenčí kolonií: jak feromonová stopa najde krátkou cestu
- Optimalizace hejnem částic: setrvačnost, vlastní zkušenost a zkušenost hejna
- Neuroevoluce: evoluce vah i tvaru neuronových sítí a algoritmus NEAT
- Fuzzy množiny a funkce příslušnosti: jak zapsat „trochu teplo“
- Fuzzy logika a pravděpodobnost: proč „trochu teplo“ neznamená „pravděpodobně teplo“
- Fuzzy logika: t-normy, implikace a pravidla KDYŽ–PAK
- Fuzzy řízení: Mamdani a Sugeno od ventilátoru po kyvadlo na vozíku
- Fuzzy shlukování: algoritmus fuzzy c-means a body mezi shluky
- Neuro-fuzzy systémy (ANFIS): fuzzy pravidla, která se učí z dat
- Markovovy řetězce: přechodová matice, stacionární rozdělení a PageRank
- Skryté Markovovy modely: Viterbiho algoritmus a nepoctivé kasino
- Bayesovské sítě: proč je tráva mokrá a co z toho plyne pro postřikovač
- Kalmanův filtr: jak z nepřesných měření odhadnout, kde auto je a jak rychle jede
- Druhy strojového učení: s učitelem, bez učitele, s částečným učitelem a další
- Přeučení a regularizace: proč model, který zná data nazpaměť, selže
- Vyhodnocení modelu: křížová validace, F1 a křivka ROC
- Shlukování a snížení dimenze: k-means, PCA a t-SNE
- Klasifikace a regrese: dvě úlohy učení s učitelem
- Lineární regrese: nejmenší čtverce geometricky i maticově
- Polynomiální a nelineární regrese: jak přímku ohnout
- Logistická regrese: sigmoida, šance a křížová entropie
- Vícetřídní klasifikace a softmax: od dvou tříd k mnoha
- Naivní Bayesův klasifikátor: spamový filtr, který funguje lépe, než by měl
- Ztrátové funkce: co přesně model minimalizuje
- Rozhodovací stromy a k nejbližších sousedů: modely bez vzorců
- Ansámbly: náhodný les a gradient boosting
- SVM a jádrový trik: nejširší hranice a jak ji ohnout
- Signál a vzorkování: proč platí Nyquistův teorém
- Fourierova transformace: rozklad signálu na frekvence
- Filtry a konvoluce: vyhlazení, hrany a odstranění šumu
- Zvuk a řeč: spektrogram, melová škála a MFCC
- Obraz jako signál: pixely, barvy, konvoluce a hrany
- Perceptron a umělý neuron: nejjednodušší neuronová síť a co neumí
- Neuronová síť od nuly: zpětné šíření chyby krok za krokem
- Aktivace, inicializace a normalizace: drobnosti, na kterých hluboká síť stojí
- Konvoluční sítě: jak síť poznává obrázky
- Rekurentní sítě: paměť pro posloupnosti a mizející gradient
- LSTM a GRU: brány, které rozhodují, co si síť zapamatuje
- Autoenkodéry: síť, která se učí data stlačit a zase rozbalit
- Embeddingy a word2vec: jak se slovo stane vektorem
- Mechanismus pozornosti: jak se model naučí, kam se dívat
- Transformery a velké jazykové modely: architektura krok za krokem
- Tokenizace: jak se text rozseká na kousky pro model
- Předtrénování a škálování: proč větší model a víc dat funguje
- Doladění a RLHF: jak se z doplňovače textu stane asistent
- Prompty, kontext a RAG: jak modelu dát správné informace
- Variační autoenkodéry (VAE): nové obrázky ze spojitého latentního prostoru
- Generativní adversariální sítě (GAN): padělatel proti detektivovi
- Difúzní modely: jak se z čistého šumu stane obrázek
- Markovovy rozhodovací procesy: stavy, akce, odměny a Bellmanovy rovnice
- Q-learning a DQN: učení hodnoty akcí od tabulky po neuronovou síť
- Policy gradient a PPO: učení přímo strategie
- Jak funguje AI agent: jazykový model ve smyčce s nástroji
- Programování s AI agenty: zadání, testy a kontrola
- Vyhodnocení a bezpečnost AI systémů: jak měřit a jak hlídat
Celá osnova série 83 z 83 témat hotovo
- Co patří do zadání pro agenta, aby věděl, kdy je hotovo.
- Kolik chyb odhalí různě důkladné testy a proč na okrajových případech záleží.
- Jaké chyby agenti dělají a jak je při kontrole najít.
Co se hodí znát předem: Jak funguje AI agent: jazykový model ve smyčce s nástroji, Jak přistupovat k vývoji s AI agenty
V minulém dílu jsme rozebrali, jak agent funguje uvnitř. Tenhle díl je praktický: jak mu zadávat práci, jak ji kontrolovat a jak neztratit přehled o tom, co se v projektu děje. Většina rad se dá shrnout jednou větou — agentovi musíte dát způsob, jak sám pozná, že je hotovo, a vy musíte mít způsob, jak to ověřit. Na pokusu ukážeme, jak moc na tom záleží.
Navazuje na díly Jak funguje AI agent a Jak přistupovat k vývoji s AI agenty, kde je víc o roli člověka a o tom, proč nechat agenta nejdřív navrhnout řešení.
1. Zadání: co, kde a jak poznat hotovo
Definice — kritéria přijetí
Nechť je dán úkol a R je jeho výsledek (stav projektu po odevzdání práce). Kritérium přijetí je podmínka K o výsledku, která splňuje:
- týká se jen toho, co jde na výsledku pozorovat nebo spustit — konkrétní chování („pro prázdný košík vrátí 0 Kč“), testy, které musí projít, příkaz, který musí doběhnout bez chyby;
- je ověřitelná: existuje předem známý postup (test, příkaz, kontrola podle seznamu), který pro každý výsledek R jednoznačně rozhodne, zda K(R) platí, nebo neplatí.
Má-li úkol kritéria přijetí K1, …, Km (m ≥ 1), je
úkol hotový ⇔ K1(R) ∧ K2(R) ∧ … ∧ Km(R).
Úkol je tedy hotový, právě když výsledek splní všechna kritéria. Na rozdíl od popisu cíle („ať je to přehledné“) se u kritéria dá jednoznačně rozhodnout, jestli platí.
„Ukliď pokoj“ je cíl. „Na zemi nic neleží, postel je ustlaná a koš je vynesený“ jsou kritéria přijetí. Teprve s nimi se dá říct, jestli je uklizeno — a dítě (nebo agent) ví, kdy může přestat.
Definice — specifikace jako předpoklad a závazek
Přesně se kritéria přijetí dají zapsat Hoareovou trojicí (C. A. R. Hoare, 1969; dnešní zápis se složenými závorkami). Nechť
- Σ je množina stavů programu — stav σ ∈ Σ přiřazuje hodnotu každé proměnné;
- C je program; zápis ⟨C, σ⟩ ⇓ σ′ znamená, že C spuštěný ve stavu σ doběhne a skončí ve stavu σ′;
- P a Q jsou podmínky na stavech, tj. pro každé σ ∈ Σ je P(σ) i Q(σ) pravda, nebo nepravda.
P se nazývá předpoklad (co smí program o vstupu čekat), Q závazek (co musí o výsledku platit). Hoareova trojice {P} C {Q} platí, právě když
∀ σ, σ′ ∈ Σ : ( P(σ) ∧ ⟨C, σ⟩ ⇓ σ′ ) ⇒ Q(σ′).
Slovy: kdykoli před spuštěním C platí P a C doběhne, platí potom Q. Program C je (částečně) správný vůči specifikaci (P, Q), právě když trojice platí; úplně správný, právě když navíc z každého stavu σ s P(σ) doběhne. O stavech, kde P neplatí, trojice nic neříká.
{ castka ≥ 0 } s := sazba(castka) { s = 0 pro castka < 1 000; s = 0,05 pro 1 000 ≤ castka < 5 000; s = 0,1 jinak }
- castka ≥ 0
- předpoklad: za zápornou částku funkce neručí — je tu, aby bylo jasné, co testovat nemusíme a co musí ohlídat volající
- s := sazba(castka)
- program, o kterém mluvíme; specifikace neříká nic o tom, jak je napsaný, takže ji agent může splnit libovolně
- s ∈ {0; 0,05; 0,1}
- závazek: přesně tři pásma a jejich hranice (≤ nebo <) — právě tady se nejčastěji chybuje, proto patří do kritérií přijetí hodnoty 999, 1 000, 4 999 a 5 000
Dobré zadání pro agenta má čtyři části. Ne proto, že by to byl obřad, ale protože každá odpovídá jedné věci, kterou agent jinak musí hádat:
Cíl: Na stránce košíku zobraz slevu podle částky (od 1 000 Kč 5 %, od 5 000 Kč 10 %).
Kontext: Výpočet ceny je v src/kosik.js, šablona v pohledy/kosik.ejs.
Podobně se počítá doprava ve funkci doprava() — drž se stejného stylu.
Omezení: Neměň formát dat v databázi. Žádné nové knihovny.
Hotovo, když:
- funkce sazba(castka) vrací 0 / 0,05 / 0,1 a testy v test/kosik.test.js projdou,
- přidáš testy pro hranice 999, 1 000, 4 999 a 5 000 Kč,
- npm test doběhne bez chyby a ukážeš mi rozdíl (git diff).
Kontext šetří agentovi hledání (a vám tokeny — viz cena kontextu). Omezení brání tomu, aby problém „vyřešil“ jinak, než chcete. A kritéria přijetí mu dávají zpětnou vazbu, bez které se dlouhé úlohy rozpadají.
2. Malé kroky, časté kontroly
V dílu o agentech jsme spočítali, že úloha z dvaceti kroků, z nichž každý se povede v 90 %, bez kontroly vyjde jen ve 12 % případů. Stejné pravidlo platí pro vás: čím větší kus práce agent odevzdá najednou, tím hůř se kontroluje a tím dražší je oprava. Praktické důsledky:
- Jedna změna = jeden úkol. Velkou věc rozdělte na kroky, které jdou zkontrolovat zvlášť (nejdřív výpočet, pak zobrazení, pak úprava dat).
- Verzovací systém jako záchranná síť. Před každým úkolem čistý stav v gitu, po něm commit. Když se něco pokazí, vrátíte se jedním příkazem.
- Čtěte rozdíl, ne shrnutí. Agentovo „hotovo, všechno funguje“ je tvrzení. Rozdíl v kódu (git diff) a výstup testů jsou důkazy.
Púspěch = pn
- p
- pravděpodobnost, že se jeden krok povede; je tu, protože každý krok je další příležitost k chybě
- n
- počet kroků mezi dvěma kontrolami; v exponentu je proto, že se pravděpodobnosti nezávislých kroků násobí — a s každým dalším krokem bez kontroly šance klesá geometricky, ne lineárně
- Púspěch
- šance, že celý úsek mezi kontrolami dopadne dobře a nic se nemusí předělávat
Příklad — kolik práce ušetří kontrola po pěti krocích
Barvy podle vzorce výše. Bez kontroly: Púspěch = 0,920 = 0,1216, tedy 12 %. S kontrolou po každých pěti krocích: 0,95 = 0,5905 pro každý ze čtyř úseků.
Předpokládejme, že kontrola chybu vždy pozná a pokažený úsek se udělá znovu, přičemž pokusy jsou nezávislé. Počet pokusů do prvního úspěchu má pak geometrické rozdělení: k-tý pokus je první úspěšný s pravděpodobností (1 − Púspěch)k−1 Púspěch a střední počet pokusů je ∑k=1∞ k (1 − Púspěch)k−1 Púspěch = 1/Púspěch. Celá úloha bez kontroly tedy stojí v průměru 20/0,1216 ≈ 165 kroků, s kontrolou po pěti 4 · 5/0,5905 ≈ 34 kroků a s kontrolou po každém kroku 20/0,9 ≈ 22 kroků. Malé kroky nezvyšují šanci, že se jeden krok povede — zmenšují, kolik práce se při chybě zahodí.
3. Testy jako pojistka: pokus
Jak moc testy pomohou? Vezmeme pět malých funkcí, jaké agent běžně píše — průměr, přestupný rok, zkrácení textu, binární hledání a sleva podle částky — a uděláme z nich záměrně pokažené verze.
Definice — mutační testování
Nechť f je program (originál), X množina jeho přípustných vstupů a T = {t1, …, tk} sada testů; každý test spustí program na zvolených vstupech a buď projde, nebo selže (za selhání se počítá i pád programu a zacyklení).
- Mutační operátor je pravidlo pro jednu drobnou syntaktickou změnu kódu:
<místo<=,- 1místo+ 1,||místo&&, jiná konstanta. - Mutant f′ je program, který vznikne z f jediným použitím jednoho mutačního operátoru na jedno místo kódu. M označme množinu všech takto vyrobených mutantů.
- Mutant f′ je ekvivalentní, právě když ∀ x ∈ X : f′(x) = f(x), tj. na žádném vstupu se od originálu neliší. E ⊆ M je množina ekvivalentních mutantů.
- Sada T mutanta f′ odhalí, právě když ∃ t ∈ T : t na f′ selže. O ⊆ M ∖ E je množina odhalených neekvivalentních mutantů.
Mutační skóre sady T je podíl odhalených mutantů mezi neekvivalentními (pro |M| > |E|):
MS(T) = |O|/|M| − |E| ∈ [0, 1].
Mutační skóre tedy měří, jak velkou část umělých chyb testy chytí. Ekvivalentní mutanti se nepočítají, protože je žádný test odhalit nemůže. Ekvivalence obecně nejde algoritmicky rozhodnout; v našem pokusu ji odhadujeme tak, že mutanta porovnáme s originálem na 2 000 náhodných vstupech.
Zkouška požárního hlásiče: schválně zapálíte malou svíčku a díváte se, jestli zapípá. Tady schválně vložíte do kódu malou chybu — přesně takovou, jakou dělají lidé i agenti — a díváte se, jestli to testy poznají.
Ze zdrojového kódu pěti funkcí vzniklo automaticky 32 mutantů. 3 z nich se na
2 000 náhodných vstupech nikdy nelišili od originálu (třeba a[s] <= x místo a[s] < x
v binárním hledání, kde rovnost už ošetřil předchozí řádek), takže zbývá 29 skutečných chyb. Proti nim
čtyři úrovně testů, každá obsahuje i ty předchozí:
- Šťastná cesta — jeden nebo dva typické příklady na funkci. Takové testy vznikají, když se řekne jen „přidej testy“.
- Okrajové případy — prázdné pole, hranice pásem (999, 1 000, 4 999, 5 000 Kč), roky 1900, 2000 a 2100, text přesně na hranici délky.
- Vlastnosti — 200 náhodných vstupů a pravidla, která musí platit vždy: výsledek zkrácení je nejvýš max znaků a je začátkem textu, nalezený index opravdu ukazuje na hledané číslo, průměr sedí s jinak zapsaným výpočtem.
Testy šťastné cesty chytí jen 15 z 29 chyb (52 %). Projdou i s funkcí, která pro prázdné pole vrací 1, se slevou, která začíná až od 1 001 Kč, nebo s binárním hledáním, které v poli o jednom prvku nic nenajde. Přidání okrajových případů zvedne podíl na 90 %, vlastnostní testy na 97 %.
| funkce | mutantů | šťastná cesta | + okraje | + vlastnosti |
|---|---|---|---|---|
prumer | 5 | 4 | 5 | 5 |
jePrestupny | 8 | 5 | 5 | 7 |
zkrat | 3 | 2 | 3 | 3 |
najdi | 6 | 3 | 6 | 6 |
sazba | 7 | 1 | 7 | 7 |
MS = |O|/|M| − |E|
- MS
- mutační skóre — podíl skutečných chyb, které sada testů chytí — výška sloupce v grafu
- |O|
- počet odhalených mutantů (aspoň jeden test na nich selhal, nebo spadly či se zacyklily); to, co testy umí
- |M|
- všichni vyrobení mutanti — zásobník chyb, proti kterému testy měříme
- |E|
- ekvivalentní mutanti; odečítají se, protože se od originálu neliší, a žádný test je proto odhalit nemůže — jinak by ani dokonalé testy nedošly ke 100 %
U nás |M| = 32 a |E| = 3, takže jmenovatel je 32 − 3 = 29 (barvy podle vzorce výše). Šťastná cesta: MS = 15/32 − 3 = 0,517; s okraji 26/32 − 3 = 0,897; s vlastnostmi 28/32 − 3 = 0,966. Postup, kterým čísla vznikla:
pro každou funkci f:
originál = přelož(kód f)
pro každý operátor (z → na) a každý výskyt z v kódu:
mutant = přelož(kód, kde je tenhle výskyt z nahrazený na)
když se mutant na 2 000 náhodných vstupech nikdy neliší od originálu:
ekvivalentní — nepočítá se, další
pro každou úroveň testů u:
odhalen[u] = některý test úrovně u na mutantovi selže,
spadne, nebo se zacyklí (pojistka po 10 000 průchodech)
MS(u) = počet odhalených na úrovni u / počet neekvivalentních
Nejzajímavější je přestupný rok. Okrajovými případy projdou mutanti, kteří za přestupný považují každý sudý rok,
nebo kteří vyřadí roky dělitelné deseti — tedy i rok 2020. Testy s roky 1900, 2000 a 2100 to nepoznají, protože
na ty náhodou odpovídají správně. Vlastnostní test s náhodnými roky obě chyby najde. Jediný mutant, který přežil
všechno, je (rok % 4 === 0 && rok % 100 !== 0) || rok % 200 === 0:
liší se jen u roků dělitelných 200, ale ne 400 (200, 600, 1000…), a mezi 200 náhodnými roky žádný takový nebyl.
Ani náhodné testy nejsou všemocné.
Ppřežije = ( 1 − q )n
- q
- podíl vstupů, na kterých se mutant liší od originálu; 1 − q je šance, že ho jeden náhodný vstup nepozná
- n
- počet náhodných vstupů; vstupy se losují nezávisle, takže se šance „nepoznal“ násobí
- Ppřežije
- pravděpodobnost, že mutanta mine všech n vstupů — chyba projde, i když vlastnost je napsaná správně
Příklad — proč přežil mutant s rokem dělitelným 200
Vlastnostní test losuje roky 0 až 2 399. Mutant rok % 200 se od originálu liší jen v 6 z nich (200, 600, 1000, 1400, 1800, 2200), takže q = 6/2400 = 0,0025 a při n = 200 přežije s pravděpodobností Ppřežije = (1 − 0,0025)200 = 0,606 (barvy podle vzorce výše) — zhruba v šesti pokusech z deseti. Že přežil, tedy nebyla smůla, ale očekávaný výsledek.
Mutant rok % 2 se liší u 600 roků (q = 0,25): přežije s pravděpodobností Ppřežije = 0,75200 ≈ 10-25, tedy prakticky nikdy. Mutant rok % 10 se liší u 96 roků (q = 0,04) a přežije s pravděpodobností Ppřežije = 0,96200 = 0,00028.
Kolik vstupů by bylo potřeba, aby se mutant s rokem 200 chytil s pravděpodobností 95 %? Požadujeme (1 − q)n ≤ 0,05. Po zlogaritmování n ln(1 − q) ≤ ln 0,05, a protože ln(1 − q) < 0, obrátí se při dělení nerovnost: n ≥ ln 0,05/ln(1 − q) = −2,996/−0,00250 ≈ 1 196,8, tedy 1 197 vstupů. Levnější je doplnit okrajový případ rok 200 nebo 600 — kdo zná pravidlo kalendáře, ví, kde hledat.
4. Kdo má testy psát
Testy v pokusu jsme psali podle zadání (co má funkce dělat), ne podle kódu. To je podstatné. Když agent napíše kód a hned k němu testy, hrozí, že testy jen zopakují, co kód dělá — včetně jeho chyb. Projdou, a nic nedokazují. Pomáhá:
- Testy (nebo aspoň jejich seznam) napřed. Nechte agenta nejdřív napsat testy podle zadání, přečtěte si je, a teprve pak kód. Kritéria přijetí ze zadání jsou přesně ten seznam.
- Okrajové případy vyjmenovat. Prázdný vstup, jedna položka, hranice pásem, záporná čísla, diakritika. Agent je často doplní sám, ale jen když je chcete.
- Vlastnosti tam, kde to jde. „Seřazené pole je pořád seřazené“, „zakódovat a dekódovat = původní text“, „nový výpočet dává totéž co starý“. Knihovny jako fast-check (JavaScript) nebo Hypothesis (Python) generují náhodné vstupy samy.
Definice — vlastnostní testování (property-based testing)
Nechť f : X → Y je testovaná funkce. Vlastnost je podmínka φ : X × Y → {pravda, nepravda} na dvojici vstup–výstup; funkce ji splňuje, právě když
∀ x ∈ X : φ(x, f(x)).
Vlastnostní test je tento algoritmus.
- Vstup: funkce f, vlastnost φ, počet pokusů N ∈ ℕ, N ≥ 1, generátor náhodných prvků X a zjednodušovací funkce, která ke každému x ∈ X vrátí konečnou množinu kandidátů zjednoduš(x) ⊆ X, z nichž každý má ostře menší velikost než x (velikost je přirozené číslo — délka textu, absolutní hodnota celého čísla, délka pole).
- Výstup: protipříklad x ∈ X s ¬φ(x, f(x)), nebo zpráva „prošlo N vstupů“.
- Pro i = 1, …, N: vygeneruj náhodné x ∈ X; platí-li ¬φ(x, f(x)), pokračuj krokem 2.
- Zmenšování: dokud existuje y ∈ zjednoduš(x) s ¬φ(y, f(y)), polož x := y. Velikost je přirozené číslo a v každém průchodu klesne, takže zmenšování skončí. Vrať protipříklad x a skonči.
- Pokud v kroku 1 žádný vstup vlastnost neporušil, vrať „prošlo N vstupů“.
Test tedy hledá protipříklad k výroku „pro všechna x“; když ho nenajde, vlastnost tím dokázaná není. Myšlenku zpopularizovala knihovna QuickCheck (Koen Claessen a John Hughes, 2000).
Neptáte se „kolik je 7 + 5“, ale „platí, že a + b je vždycky stejné jako b + a?“ — a necháte počítač vyzkoušet tisíc náhodných dvojic. Najde případy, na které byste nepomysleli.
Vlastnost je výrok s kvantifikátorem „pro všechna“. Pro funkci zkrat z pokusu ji test zapisuje takhle:
∀ t, m ≥ 1: |v| ≤ m ∧ ( |t| ≤ m ⇒ v = t ) ∧ ( |t| > m ⇒ v = t[0, |v| − 1) + „…“ ), kde v = zkrat(t, m)
- ∀ t, m ≥ 1
- pro každý text a každou délku — proto se dá ověřovat na náhodných vstupech a nepotřebujeme znát správný výsledek předem
- |v| ≤ m
- hlavní slib funkce: výsledek se vejde; chytí mutanta, který zapomene započítat „…“ (
slice(0, max)) - |t| ≤ m ⇒ v = t
- krátký text se nemění; bez toho by prošla funkce, která zkracuje vždy
- v = t[0, |v| − 1) + „…“
- zkrácený výsledek je začátek původního textu a končí výpustkou; bez toho by prošla funkce, která vrací třeba prázdný text
Nástroj pro vlastnostní testování pak dělá zhruba tohle:
pro i = 1 … N: # N bývá 100 až 1 000
x = vygeneruj náhodný vstup
když vlastnost(x, f(x)) neplatí:
# zmenšování: hledej jednodušší vstup, na kterém to pořád selže
dokud existuje y = zjednoduš(x), na kterém vlastnost neplatí:
x = y # kratší text, menší číslo, kratší pole
ohlas protipříklad x
ohlas „prošlo N vstupů“ # důkaz to není, jen dobrá zpráva
Poslední řádek je podstatný. Testy, ani náhodné, nedokazují správnost — jen hledají protipříklady. Edsger Dijkstra to v roce 1969 řekl tak, že testování umí ukázat přítomnost chyb, nikdy jejich nepřítomnost. Pravděpodobnost, že vzácná chyba proklouzne, jsme si spočítali výše.
5. Na co si dát pozor
Agenti dělají jiné chyby než lidé. Nejčastější, které stojí za to při kontrole hledat:
- „Hotovo“ bez ověření. Agent oznámí úspěch, aniž testy spustil, nebo je spustil jen zčásti. Chtějte výstup testů.
- Úprava testů místo kódu. Když test selže, nejsnazší „oprava“ je změnit očekávanou hodnotu nebo test smazat. V rozdílu hledejte změny v testech.
- Umlčené chyby. Prázdný
catch, výchozí hodnota místo chyby, natvrdo zapsaný výsledek pro testovací vstup. - Práce navíc. Přepsaný soubor, který s úkolem nesouvisí, nová knihovna, „vylepšený“ formát dat. Omezení v zadání a malé kroky to drží v mezích.
- Tajemství a nevratné kroky. Hesla v kódu, mazání souborů, zápis do produkční databáze, odeslání do vzdáleného repozitáře. Tyhle akce nechte schvalovat.
6. Neztratit přehled
Agent si mezi sezeními nic nepamatuje. Co má vědět pokaždé — jak se projekt spouští a testuje, jaké jsou konvence, čemu se vyhnout — patří do souboru s pokyny v kořeni projektu, který si agent přečte na začátku. Pište tam to, na co jste museli agenta upozornit víc než jednou. A to, co víte jen vy (proč je něco udělané takhle, co se zkoušelo a nefungovalo), patří do commitů a dokumentace — pro agenta i pro sebe za půl roku.
Poslední rada je nejjednodušší: zůstaňte u toho. Ne nutně u počítače — agent může pracovat sám a vy se podíváte, až bude potřebovat rozhodnutí. Proto vznikl coden: zadání, schvalování kroků a kontrolu výsledku máte v telefonu, zatímco agent pracuje na vašem počítači.
7. Shrnutí
- Dobré zadání: cíl, kontext (soubory, vzory), omezení a ověřitelná kritéria přijetí.
- Malé kroky, commit po každém, a kontrola rozdílu v kódu a výstupu testů, ne shrnutí.
- V pokusu odhalily testy šťastné cesty 52 % chyb, s okrajovými případy 90 %, s vlastnostmi 97 %.
- Testy pište (nebo schvalujte) podle zadání a ideálně před kódem, jinak jen zopakují chyby implementace.
- Hlídejte úpravy testů, umlčené chyby, práci navíc a nevratné kroky.
Chcete si to osahat? Požádejte AI agenta, ať na váš projekt pustí nástroj pro mutační testování (Stryker pro JavaScript, mutmut pro Python) a ukáže vám mutanty, které přežili. Každý z nich je test, který chybí.
Máte na to jiný názor? Napište na support@coden-app.com.