Testování pravidel
RuleTester a formát fixtur: soubory .code, .expected a
.violations, volby a cílová verze PHP v hlavičce fixtury, a co všechno tester ohlídá za vás.
Fixtury
Pravidlo se testuje nad adresářem fixtur, jedním na pravidlo. Fixtura (fixture) je až trojice souborů se stejným jménem:
basic.codeje kód před opravou;basic.expectedje kód po opravě; když soubor chybí, pravidlo nesmí kód změnit;basic.violationsjsou očekávaná hlášení, každé na svém řádku ve tvaru řádek a zpráva:
3: The message of an exception must end with a period
6: The message of an exception must end with a period
Fixtura může v komentářích na svém začátku nést volby pravidla jako JSON, verzi PHP, pro kterou je psaná, nejdelší
řádek, se kterým se měří, a slovo risky, které pravidlu dovolí i opravy měnící chování kódu:
<?php
// {"functions": ["dd", "dump"]}
// php 8.4
// lineLength 80
// risky
Bez uvedené délky se měří s řádky do 120 znaků, jaké mají standardy. Bez uvedené verze se pravidlo testuje na
verzi, o kterou si řeklo v requires, jinak na PHP 8.0; verze předaná testeru parametrem $phpVersion
má přednost před hlavičkou. Nikdy ne na verzi interpretu, který testy spouští, protože verdikt pravidla má být na
počítači nezávislý. Bez risky se rizikový výskyt jen ohlásí a soubor .expected ho musí nechat,
jak byl; fixtura s risky je zároveň důkaz pro referenci, že pravidlo takové opravy má.
Co deklarují jmenné prostory mimo fixturu, řeknou hlavičky ve tvaru položek příkazu use, stejně jako
klíče namespaces a nameResolution
v konfiguraci:
<?php
// namespacedFunctions App\helper, App\Utils\{format}
// namespacedConstants App\LIMIT
// nameResolution certain
Pravidlo s requiresTypes dostane typy kódu z PHPStanu projektu, ve kterém testy běží, spočítané nad
fixturou a nad deklaracemi z podadresáře stubs/ v adresáři fixtur. Pravidlo, kterému typy jen pomáhají, je
dostane tam, kde adresář stubs/ je.
Ukázky, které vypíše dresscode explain na terminál i do dokumentu s --output, mají stejný
tvar jako fixtury, ale leží v adresáři examples/ vedle composer.json balíčku, ze kterého
pravidlo pochází, v podadresáři se jménem pravidla bez vendora, třeba examples/exception-message-period/.
Balíček je tak má s sebou i po instalaci přes Composer, kde adresář tests/ chybí, a platí to pro
rozšíření stejně jako pro vestavěná pravidla. Ukázka má být krátká a čitelná. Pusťte ji v testech jako každou
jinou fixturu, RuleTester::run(ExceptionMessagePeriodRule::class, __DIR__ . '/../examples/exception-message-period'),
a ukázka nemůže zastarat.
Fixtura není ukázka do dokumentace: má být ošklivá a plná hraničních případů. Komentář uprostřed konstrukce, konstrukce na jednom řádku i přes tři, prázdné tělo, interpolovaný řetězec, alternativní syntaxe. Právě na těchhle místech se pravidla lámou.
RuleTester
use DressCode\Testing\RuleTester;
use Tester\Assert;
Assert::noError(fn() => RuleTester::run(ExceptionMessagePeriodRule::class, __DIR__ . '/fixtures/exception-message-period'));
Metoda run() projde všechny soubory *.code v adresáři a vrátí jejich počet; adresář bez nich
je selhání, ne prázdný úspěch. Selhání je výjimka DressCode\Testing\TestFailure se jménem fixtury a
s diffem, takže ji srozumitelně ukáže každý testovací framework. Nette Tester chce v každém testu aspoň jednu aserci,
proto je volání v ukázce zabalené do Assert::noError(); z PHPUnit je to jedno volání v testovací
metodě.
U každé fixtury tester ověří:
- výstup se rovná souboru
.expected(nebo vstupu, když.expectednení); - hlášení se rovnají souboru
.violations, řádek po řádku a ve stejném pořadí; - idempotenci: pravidlo nad vlastním výstupem už nic nezmění;
- komentáře: ve výstupu jsou všechny komentáře ze vstupu, pokud pravidlo nemá
modifiesComments; - potlačení: s komentářem
dresscode:ignore-filev hlavičce pravidlo nic neohlásí ani nezmění; - kontrakt oprav: žádná změna stromu bez hlášení, které prošlo;
- strom: každý uzel má správného rodiče, což se rozbije při chybném vkládání.
Pravidlo, které volby dostává jinak než z konfigurace (třeba se závislostí v konstruktoru), předáte místo třídy
jako továrnu fn(array $options): Rule. Továrna dostane volby z hlavičky fixtury tak, jak jsou zapsané v JSONu,
a bez hlavičky prázdné pole. U třídy tester volby prožene schématem pravidla, které je zvaliduje a doplní výchozí
hodnoty; u továrny to neudělá. Výchozí hodnoty proto doplní továrna sama, třeba
$options + ['functions' => ['var_dump', 'print_r']], a teprve pak zavolá configure().
Pro zvláštní případy jsou tu menší nástroje: RuleTester::runFixture() spustí jedinou fixturu,
RuleTester::check() ověří instanci pravidla nad řetězcem bez souborů (hodí se na rychlou reprodukci) a
RuleTester::collectViolations() vrátí hlášení nad fixturou přímo ve tvaru souboru .violations a
collectOutput() výstup ve tvaru souboru .expected, takže si je můžete nechat zapsat a jen
zkontrolovat diff, místo abyste je opisovali ručně. run(), runFixture() i obě
collect*() přijmou jako poslední parametr $phpVersion.
Zkouška v reálném provozu
Test pravidla ověří pravidlo samotné. Jak se chová ve společnosti ostatních, ukáže až běh nad skutečným kódem.
Přepínač dresscode check --strict-rules udělá z každého porušeného kontraktu chybu místo varování a
--jobs 1 nechá všechno běžet v jediném procesu, kde se pohodlně ladí.
Než pravidlo zveřejníte, pusťte fix nad větším cizím kódem, třeba nad adresářem vendor/,
a projděte si diff. Idempotenci a komentáře ohlídá tester, vkus ne.