
Proč potřebujeme jiné testy
Než začneme psát testovací skript, měli bychom si ujasnit, co od něj očekáváme.
Potřebujeme porovnat výsledky pro dva jazyky, takže nepůjde o unit testy v klasickém slova smyslu (a už vůbec ne o integrační). Tyto testy mají ještě jinou roli: ukazují, co lze použít - a co ne. Budou tedy sloužit nejen k testování, ale i jako jakýsi manuál. A protože porovnávají výsledky v PHP a JS, říkám jim křížové testy.
Jak bude stránka vypadat
Moje představa je stránka rozdělená na sekce (podle testovaných knihoven). V každé sekci bude tabulka se sloupci:
- Popis testu - co ověřujeme a proč
- Testovaný výraz - kód který se spouští
- Výsledek v PHP
- Výsledek v Javascriptu
- Zhodnocení - shodují se? Je rozdíl očekávaný?
Taková struktura umožňuje rychle identifikovat odchylky i pochopit, proč vznikly
Datová struktura testů
Pro takové zobrazení potřebujeme jednotnou datovou strukturu. Testy budou rozdělené podle knihoven - každá knihovna bude mít své pole, v němž budou jednotlivé funkce, a u každé funkce seznam testů.
Takže například v sekci nm_Util
bude asociativní pole, kde budou názvy jednotlivých testů. S tím ovšem přichází trochu problém, asociativní pole v PHP zakládáme pomocí []
v JS pomocí {}
. Vypadá to, že zde budeme muset použít combisriptový komentářový přepínač, kód rozvětvit a například pro testy knihovny nm_util
vznikne něco takového:
- <!-- --><?php
- "use strict";
- 0&&0<!--$_;/*
- $Tests['nm_Util'] = {};
- /*/
- $Tests['nm_Util'] = [];
- //*/
- $Tests['nm_Util']['nm_IsNumber'] = [Test1, Test2, Test3, ...];
- $Tests['nm_Util']['nm_Abs'] = [Test1, Test2, Test3, ...];
- ....
Kde v prvním řádku vidíme pro JS zakomentované přepnutí do PHP, abychom test mohli vkládat jak do JS tak do PHP scriptu. V druhém řádku vidíme use strict
- nastavení strict modu pro JS,které se v PHP nijak neprojeví. Pak následuje deklarace asociativního pole pro každý jazyk. A pak v každém klíči tohoto pole jednotlivé testy.
Asi by se taková hlavička testovacího souboru přežít dala, ale moc hezké to není. Samozřejmě lze využít toho, že díky tomu, že PHP má jen jeden druh polí tak jej deklarovat nemusíme, a můžeme napsat jen:
- <!-- --><?php
- "use strict";
- 0&&0<!--$_;/*
- $Tests['nm_Util'] = {};
- //*/
- $Tests['nm_Util']['nm_IsNumber'] = [Test1, Test2, Test3, ...];
- $Tests['nm_Util']['nm_Abs'] = [Test1, Test2, Test3, ...];
- ....
$Tests['nm_Util']['nm_IsNumber'] = ....
poradí i když klíč nm_Util
nebude předem definován. Ale nešlo by to zjednodušit ještě více? Kupodivu šlo. Klidně se můžeme combiscriptového přepínače zbavit a napsat i jen:
- <!-- --><?php
- "use strict";
- $Tests['nm_Util'] = [];
- $Tests['nm_Util']['nm_IsNumber'] = [Test1, Test2, Test3, ...];
- $Tests['nm_Util']['nm_Abs'] = [Test1, Test2, Test3, ...];
- ....
Teď se většina JS programátorů zděsí a začne protestovat. Tohle v JS nebude fungovat, pole []
přece v JS slouží k zápisu numerického pole a k zápisu asociativního pole slouží objekt {}
. Ale to není úplná pravda. Každé pole je v JS i objektem a zdědilo od něj vlastnosti objektu, což jsou i stringové klíče. Rozdíl mezi polem []
je v PHP a v JS mnohem menší než se traduje a do obou lze zapisovat stringové klíče. A my tohoto budeme v našem kódu masivně využívat.
Pole []
lze v PHP i JS využít k ukládání stringových klíčů
String nebo funkce
Další rozhodnutí je, zda test zapisovat jako řetězec, nebo přímo jako anonymní funkci zapsanou v CombiScriptu. Zápis jako string bude potřebovat spouštění přes eval
, u anonymní funkce je v PHP trochu problém s získáním zdrojového kódu funkce, který bychom museli vyparsovat ze zdrojového kódu. Nakonec se rozhoduji pro anonymní funkce. Součástí Combiscriptu budou např. i "reflection function" a ty by přes eval byly netestovatelné.
Výstupní funkce
Zbývá rozhodnout, jakou funkci použijeme pro výstup. Definuji, že budeme používat funkci Out. Tato funkce bude definována v PHP i v JS a vypíše výsledek testu do příslušného políčka tabulky.
Co všechno potřebujeme u testu za data
Každý test je tedy malý samostatný blok znalostí – a jeho struktura musí nést vše potřebné. Bude mít tři součásti
- Očekávání - zda se má výsledek v PHP a JS shodovat (nebo jde o varovný příklad),
- Anonymní funkci - která provede test a vypíše výsledek pomocí
Out()
. - Popis - vysvětlení proč test existuje.
Podle těchto pravidel můžeme napsat testy pro několik našich prvních funkcí:
- <!-- --><?php
- "use strict";
- $Tests['nm_Util'] = [];
- $Tests['nm_Util']['nm_IsNumber'] = [
- [1, function()
- {
- Out(nm_IsNumber(5));
- }, 'Ověřuje, že kladné celé číslo je číslo'],
- [1, function()
- {
- Out(nm_IsNumber(-5));
- }, 'Ověřuje, že záporné celé číslo je číslo'],
- [1, function()
- {
- Out(nm_IsNumber(0));
- }, 'Ověřuje, že nula je číslo'],
- [1, function()
- {
- Out(nm_IsNumber(2.5));
- }, 'Ověřuje, že kladné desetinné číslo je číslo'],
- [1, function()
- {
- Out(nm_IsNumber(-2.5));
- }, 'Ověřuje, že záporné desetinné číslo je číslo'],
- [1, function()
- {
- Out(nm_IsNumber(.5));
- }, 'Ověřuje, že desetinné číslo s vynechanou úvodní nulou je číslo'],
- [1, function()
- {
- Out(nm_IsNumber(-.5));
- }, 'Ověřuje, že záporné desetinné číslo s vynechanou úvodní nulou je číslo'],
- [1, function()
- {
- Out(nm_IsNumber(3.21E4));
- }, 'Ověřuje, že číslo v exponenciálním tvaru je číslo'],
- [1, function()
- {
- Out(nm_IsNumber(-3.21E4));
- }, 'Ověřuje, že záporné číslo v exponenciálním tvaru je číslo'],
- [1, function()
- {
- Out(nm_IsNumber(3.21E-4));
- }, 'Ověřuje, že číslo s negativním exponentem je číslo'],
- [1, function()
- {
- Out(nm_IsNumber(-3.21E-4));
- }, 'Ověřuje, že záporné číslo s negativním exponentem je číslo'],
- [1, function()
- {
- Out(nm_IsNumber(-3.21e-4));
- }, 'Ověřuje, že se akceptuje malé "e" v exponenciálním tvaru'],
- [1, function()
- {
- Out(nm_IsNumber("123"));
- }, 'Ověřuje, že string obsahující číslo není číslo'],
- [1, function()
- {
- Out(nm_IsNumber([1]));
- }, 'Ověřuje, že pole není číslo']
- ];
- $Tests['nm_Util']['nm_IsAsInt'] = [
- [1, function()
- {
- Out(nm_IsAsInt(5));
- }, 'Ověřuje, že kladné celé číslo je celé číslo'],
- [1, function()
- {
- Out(nm_IsAsInt(-5));
- }, 'Ověřuje, že záporné celé číslo je celé číslo'],
- [1, function()
- {
- Out(nm_IsAsInt(5.0));
- }, 'Ověřuje, že celočíselný float je celé číslo'],
- [1, function()
- {
- Out(nm_IsAsInt(-5.0));
- }, 'Ověřuje, že záporný celočíselný float je celé číslo'],
- [1, function()
- {
- Out(nm_IsAsInt(0));
- }, 'Ověřuje, že nula je celé číslo'],
- [1, function()
- {
- Out(nm_IsAsInt(-0));
- }, 'Ověřuje, že minus nula je celé číslo'],
- [1, function()
- {
- Out(nm_IsAsInt(2.5));
- }, 'Ověřuje, že kladné desetinné číslo není celé číslo'],
- [1, function()
- {
- Out(nm_IsAsInt(-2.5));
- }, 'Ověřuje, že záporné desetinné číslo není celé číslo'],
- [1, function()
- {
- Out(nm_IsAsInt("123"));
- }, 'Ověřuje, že string obsahující číslo není celé číslo'],
- [1, function()
- {
- Out(nm_IsAsInt(9007199254740991.5));
- }, 'Ověřuje, že velké číslo s desetinnou částí se stále jeví jako integer (rozložení na číselné ose je už tak hrubé)'],
- [1, function()
- {
- Out(nm_IsAsInt(4.9999999999999995));
- }, 'Toto ještě není 5'],
- [1, function()
- {
- Out(nm_IsAsInt(4.9999999999999996));
- }, 'Toto už je 5']
- ];
- $Tests['nm_Util']['nm_Abs'] = [
- [1, function()
- {
- Out(nm_Abs(2.5));
- }, 'Ověřuje, že absolutní hodnota kladného čísla je to samé číslo'],
- [1, function()
- {
- Out(nm_Abs(-2.5));
- }, 'Ověřuje, že absolutní hodnota záporného čísla změní znaménko na kladné'],
- [1, function()
- {
- Out(nm_Abs(0));
- }, 'Ověřuje, že absolutní hodnota nuly je nula'],
- [1, function()
- {
- Out(nm_Abs("123"));
- }, 'Ověřuje, že pro nečíselný parametr se vyhodí chyba'],
- ];
- $Tests['nm_Util']['nm_Sign'] = [
- [1, function()
- {
- Out(nm_Sign(2.5));
- }, 'Ověřuje, že signum kladného čísla je +1'],
- [1, function()
- {
- Out(nm_Sign(-2.5));
- }, 'Ověřuje, že signum záporného čísla je -1'],
- [1, function()
- {
- Out(nm_Sign(0));
- }, 'Ověřuje, že signum nuly je 0'],
- [1, function()
- {
- Out(nm_Sign(-0));
- }, 'Ověřuje, že signum záporné nuly je také 0'],
- [1, function()
- {
- Out(nm_Sign("0"));
- }, 'Ověřuje, že pro nečíselný parametr se vyhodí chyba'],
- ];
Tyto křížové testy nejsou jen technickým nástrojem - jsou mapou kompatibility mezi PHP a JavaScriptem. Každý test dokumentuje chování, odhaluje odchylky a posiluje důvěru v polyglotní kód.
V příštích dílech si ukážeme, jak tyto testy automaticky spustit, porovnat výsledky a vygenerovat přehlednou HTML tabulku - a to jejich dvojnásobným spuštěním na serveru a v prohlížeči.