Testování finančních výpočtů: úroky, AÚV a daň

Jan Zedníček · · Technici

Jeden finanční výpočet, třeba úrok nebo srážkovou daň, ověřujeme několika nezávislými způsoby. Kromě běžných testů kódu porovnáváme výsledky s ručně spočítanými případy a s výpočtem, který udělá AI agent jen podle emisních podmínek a pravidel výpočtu, která sepsal a schválil člověk z emisních podmínek a zákona, ne z kódu, bez znalosti aplikace. Když se výsledky liší, rozhodne člověk, která strana pravidlo pochopila špatně.

Testování finančních výpočtů je ověření, že aplikace spočítá úrok, AÚV a daň podle emisních podmínek a zákona, ne jen podle toho, jak jim rozuměl programátor. Testy ale obvykle píše stejný člověk jako kód. Když pravidlo přečte špatně, přečte ho stejně špatně i v testu, oba projdou a chyba se ukáže až u výplaty (investor dostane o pár korun jiný úrok nebo se srazí jiná daň). V článku se podíváme na to, jakými cestami ověřujeme výpočty při vývoji Bondie, co každá chytí a jak často se spouští.

Kalendář ceny, na kterém ukazuji zaokrouhlení denního úroku, vychází z jedné ukázkové emise ze snímku níže (jiné než v článcích o výplatě úroků):

Testování finančních výpočtů – proč testy autora nestačí

Každý výpočet v aplikaci má testy, které napsal jeho autor. Test autora je pořád potřeba. Hlídá, že zítřejší změna nerozbije, co dnes funguje (regresní testování). Nemůže ale zachytit chybu ve výkladu pravidla, protože ten výklad sdílí s kódem. Typický příklad je konvence počítání dnů: jestli se započítá první i poslední den, nebo jen jeden. Kód i test se v tomto případě shodnou, jen oba mohou být mimo emisní podmínky.

Je tedy potřeba i ověření, které vzniklo jinou cestou, buď z jiného zdroje, nebo od někoho, kdo kód neviděl. Ostatní zásady shrnuje článek Zásady vývoje software.

Testy oblasti po změně, celá sada před commitem

Testy máme rozdělené do oblastí (objednávky, platby, výplaty, daň, role a další). Po každé malé změně logiky běží jen testy dotčené oblasti a k nim stálé kontroly dokumentace a rozhraní, takže výsledek je do minuty. Před každým commitem běží celá sada, která trvá déle. Když spadne jediný test, změna neprojde.

Testy běží proti skutečné databázi. Každý test si proto založí vlastní testovací emisi a po sobě ji uklidí. Data si zakládá přes stejné služby jako aplikace. Kdyby test vkládal data napřímo, obešel by pravidla, která má ověřovat.

Invarianty: co musí v datech platit vždy

Jednotlivý test kontroluje jednu situaci. Některé chyby ale vzniknou až kombinací kroků (např. storno po částečné platbě). Proto máme invarianty, tedy pravidla nad celými daty, která musí platit bez ohledu na to, jak data vznikla:

  • zaplacená objednávka nemá nedoplatek,
  • počet vydaných dluhopisů odpovídá objednanému množství,
  • stornovaná objednávka nedrží žádný dluhopis,
  • výplata se rovná úroku plus jistině minus dani,
  • výplata, ze které se srazila daň, má odvod daně.

Invarianty jsou samostatné testy nad testovacími daty a běží s celou sadou. Porušení neřekne, kde je chyba, ale spolehlivě řekne, že někde je.

Referenční případy a nezávislý přepočet jinou cestou

Výpočty úroků, AÚV a daně ověřujeme proti hodnotám, které nevznikly v aplikaci. Referenční případy (ve vývoji se jim říká zlatá data) jsou dvojího druhu. První jsou veřejně publikované hodnoty, např. AÚV, které ministerstvo financí uvádí v oznámeních o aukcích státních dluhopisů. Druhé jsou ručně odvozené hraniční případy (přestupný rok, konec února v konvenci 30E/360, první a poslední kupón, výplata o víkendu). Každý případ je obyčejný test, který běží s celou sadou.

Nezávislý přepočet dělá AI agent v samostatné roli. Dostane jen emisní podmínky, sepsaná pravidla výpočtu a vstupy, výsledky aplikace ani kód nevidí. Spočítá hodnoty po svém a teprve potom je porovnáme s aplikací na referenčních případech a na vzorku z ukázkových emisí. Případy, ve kterých se přepočet s aplikací shodne, uložíme jako testy druhu „nezávislý výpočet“ a od té doby běží s celou sadou. Jak role fungují a co která vidí, popisuji v článku Oponentura v rolích.

Jedno číslo spočítané třemi nezávislými způsoby Zdroj pravidla: emisní podmínky a zákon sazba, konvence dnů, dny výplat, rozhodný den, sazba daně Výpočet aplikace kód, jak ho napsal autor a jak mu rozuměl zná: kód a výklad autora Nezávislý přepočet počítá znovu od nuly jen z pravidel a vstupů nevidí: kód ani výsledky Referenční případy veřejně publikované hodnoty a ručně spočítané hranice vznikly mimo aplikaci Porovnání na haléře shoda: případ zůstává v sadě jako test rozdíl: otázka pro člověka, ne oprava čísla
Tři cesty k jednomu číslu. Rozdíl neopravujeme podle aplikace, ale vracíme k pravidlu.

Při rozdílu neopravujeme tak, že přepíšeme očekávanou hodnotu podle aplikace. Vracíme se k pravidlu: chyba je buď v kódu, nebo v přepočtu, nebo pravidlo není jednoznačné. Který případ nastal, posoudí člověk (u daně s daňovým poradcem); agent rozdíl jen ohlásí a oprava jde dál až s testem.

Skutečný případ z vývoje, bez názvu emise: dluhopis s jmenovitou hodnotou 10 000 Kč, sazbou 5,5 % v konvenci ACT/360 a obdobím 92 dní, investor drží 50 kusů. Aplikace zaokrouhlí kupón na kus a vynásobí ho počtem kusů: 50 × 140,56 = 7 028,00 Kč. Přepočet spočítal úrok za celou pozici najednou a zaokrouhlil až výsledek, vyšlo mu 7 027,78 Kč, tedy o 7 028,00 − 7 027,78 = 0,22 Kč méně. Ani jedna strana nepočítala chybně, emisní podmínky jen neříkaly, kde se zaokrouhluje. Rozhodl člověk: kupón se zaokrouhluje na kus, protože podmínky určují výnos jednoho dluhopisu, a pravidlo se dopsalo do pravidel výpočtu, ze kterých přepočet vychází.

Snímek z ukázky aplikace Bondie: Kalendář ceny ukázkové emise s pevnou sazbou 6,5 % a konvencí 30/360 US: po dnech denní úrok 0,90 Kč, AÚV a cena jednoho kusu od jistiny 5 000 Kč
Kalendář ceny jedné emise: hodnoty po dnech, které porovnáváme s přepočtem jinou cestou. Zdroj: Bondie

Na snímku je kalendář ceny jedné ukázkové emise: denní úrok, AÚV a cena kusu po dnech. Právě takové řady porovnáváme s přepočtem. Podle parametrů výše je denní úrok 5 000 × 0,065 / 360 = 0,9028 Kč, na snímku tedy 0,90 Kč. Za dva dny ale nevychází 2 × 0,90 = 1,80 Kč, ale 2 × 5 000 × 0,065 / 360 = 1,8056 Kč, zaokrouhleno 1,81 Kč, protože se zaokrouhluje až výsledek. Postup výpočtu vysvětluje článek Jak spočítat AÚV.

Ani nezávislý přepočet nechytí všechno. Když se zákon dá přečíst dvojím způsobem nebo k situaci mlčí, mohou se splést obě strany stejně. Proto stavíme i na referenčních případech z veřejných zdrojů a poslední slovo má člověk.

Druhy testů a pokrytí kódu

Testy během vývoje přibývají s každou funkcí, proto jejich počty v článku neuvádíme; aktuální stav ukazuje stránka Vývoj v číslech. Ke dni psaní tvořily většinu funkční testy, které volají akci uživatele přes službu nad testovací databází. Testů nezávislého výpočtu je jen několik, každý ale projde celou řadu referenčních případů.

Pokrytí kódu testy říká, kolik příkazů kódu spustí aspoň jeden test. Neříká, že je kód bez chyby, jen ukazuje, kde test chybí úplně. Proto je hlídané minimum u kódu, který počítá peníze, vyšší: 95 % proti 93 % pro celý kód.

Kontroly obrazovek a rozhraní

Výpočet může být správně a obrazovka přesto ukáže něco jiného, např. když zobrazí starou hodnotu nebo tlačítko, které role nesmí použít. Proto běží i kontroly nad běžící aplikací: průchod průvodce pod jednotlivými rolemi, kontrola, kdo co vidí, otevření detailu prvního řádku každého přehledu a přístupnost.

Rozhraní a přehledy mají uložený snímek (kontrakt): sloupce přehledů, cesty rozhraní s oprávněním, nastavení a role. Změnu, která snímek posune, musí autor vědomě zapsat a vysvětlit. Změna sloupce, na který spoléhá obrazovka, tak bez vysvětlení neprojde.

Stabilizace před vydáním a jak často co běží

Na konci vlny práce (několik úkolů za sebou) běží stabilizace na kopii posledního commitu, aby se dál dalo vyvíjet. Obsahuje celou sadu testů, kontroly obrazovek pod rolemi, bezpečnostní kontroly kódu, obnovu ukázkových dat a celou sadu s každým volitelným modulem vypnutým zvlášť. Žádná změna aplikace nejde do hlavní větve, ze které se vydává, bez zelené stabilizace. Jednotlivé kontroly běží takhle často:

  • po každé změně logiky, i několikrát za hodinu: testy oblasti,
  • před každým commitem: celá sada včetně invariantů a referenčních případů,
  • při změně výpočtů a ve stabilizaci: nezávislý přepočet,
  • ve stabilizaci na konci vlny: kontroly obrazovek a rolí a sada s vypnutými moduly,
  • jednou týdně na serveru průběžné integrace: zranitelnosti knihoven,
  • po milníku: průzkum celé aplikace.

Když se chyba najde kdekoli v tomhle řetězci, nejdřív vznikne test, který ji zopakuje. Teprve pak přijde oprava u zdroje a kontrola podobných míst (stejný výpočet v jiném přehledu, stejný stav v jiné akci). Chyba, kterou našla kontrola obrazovek v prohlížeči, dostane i rychlý test v sadě, aby ji příště chytil dřív. Proč se výpočet nesmí počítat na dvou místech, ukazuje článek Jeden zdroj pravdy.

Závěr

Testy, které píše autor kódu, hlídají, aby se nerozbilo, co už fungovalo, ale jeho výklad pravidla neověří. Výpočet peněz proto potřebujeme spočítat ještě jinou cestou, z ručně ověřených případů a nezávislým přepočtem podle emisních podmínek, a doplnit ho kontrolou celých dat, která zachytí chyby vzniklé až kombinací kroků. Když se aplikace a přepočet rozejdou, neopravujeme očekávané číslo podle aplikace, ale vracíme se k pravidlu a o výsledku rozhodne člověk.

Související články