Přejít k navigační liště

Zdroják » PHP » Testování v PHP: anotace

Testování v PHP: anotace

Články PHP, Různé

Anotace poskytují širokou škálu možností od ovlivnění běhu jednotlivých test case, nastavení chování frameworku PHPUnit až po usnadnění práce v něm.

Anotace

V dnešním díle seriálu o testování v PHP se opět vrátíme k samotnému testovacímu frameworku PHPUnit a podíváme se na anotace. Otázka první – co jsou to vlastně anotace? Abych nebyl nařčen z opisování originální dokumentace, nepoužiji výraz „metadata“ a uvedu mnohem prostší vysvětlení – anotace nejsou nic jiného než komentáře se speciálním významem. Jsou vždy součástí blokového komentáře, který se nejčastěji označuje jako tzv. „javadoc“ (ano, podobnost s programovacím jazykem Java není náhodná). Poznáte jej tak, že začíná znaky /** a končí */.

Javadoc komentář může obsahovat nejrůznější textová data, která se později využívají k tvorbě automatické dokumentace (popisy, ukázky použití, seznamy a popisy parametrů, návratové hodnoty, …) a kromě toho i zmíněné anotace. Anotace vždy začínají znakem @ a pokud jste někdy nahlíželi do zdrojových kódů např. nějakého frameworku nebo open-source knihovny, určitě jste na řadu anotací už narazili. Jsou jimi třeba: @param pro typ a popis vstupního parametru, @return pro typ nebo popis návratové hodnoty, @see pro odkaz na jinou část kódu a mnohé další. Ve většině případů se anotace používají pouze pro automatické generování dokumentace, případně ještě pro code-completion v editorech.

Kromě dokumentace mohou anotace posloužit i k nejrůznějším konfiguračním účelům, což je i náš dnešní případ.

@author, @group

Tyto dvě anotace mají analogický význam, přesněji @author je pouze aliasem anotace @group. Díky nim můžeme test cases zařazovat do skupin a pomocí parametru –group spouštět jen testovací případy zvolené skupiny. Anotací @group (@author) můžeme mít neomezené množství.

/**
 * @group unit
 * @group projectOne
 * @author pepan
 */
public function testSomething() {}

Spouštění testů zvolených skupin:

phpunit --group unit

phpunit --group projectOne

phpunit --group pepan

@backupGlobals, @backupStaticAttributes

Tyto dvě anotace nejsou totožné jako v předchozím případě, souvisí ale s víceméně stejným nešvarem, proto jsem jejich popis spojil dohromady. Jde o problematiku globálního stavu a závislosti na něm. Pod pojmem „globální stav“ si můžete představit vše, k čemu je možné se dostat z jakéhokoli místa v kódu. Globální proměnné a statické metody či vlastnosti tříd jsou zářnými příklady.

Pozorní čtenáři si jistě vzpomínají, že jsem v prvním díle seriálu zmiňoval pravidla pro dobré testy (F.I.R.S.T.), která mimo jiné říkají, že testy by měly být navzájem nezávislé a v různém pořadí opakovatelné. A to je přesně to, co nám jakákoli závislost na globálním stavu bude úspěšně sabotovat. Jakmile nám testovaný kód „kdesi cosi“ mění pod rukama, tak se můžeme jít s podobnými testy rovnou bodnout, protože nám budou celkem vtipně náhodně selhávat. Takové testy nejsou ani nezávislé ani opakovatelné.

Ale protože ne vždy máme to štěstí stavět kód od základů a často jsme nuceni spravovat různě kvalitní legacy kód, tak nám PHPUnit nabízí dvě anotace, které nás částečně od nebezpečí závislosti na globálním stavu chrání. Anotace @backupGlobals říká, zda se před následující sadou testů nebo před následujícím test case mají zálohovat globální proměnné. Možné hodnoty jsou enabled nebo disabled. Nastavením hodnoty „enabled“ zajistíme, že před spuštěním každého testovacího případu jsou zazálohovány hodnoty globálních proměnných a po jeho ukončení opět vráceny. Po ukončení testovacího případu se tak nedostáváme do nějakým způsobem modifikovaného prostředí.

Obdobnou funkci má anotace @backupStaticAttributes, jen s tím rozdílem, že zálohuje statické atributy tříd. Aby nedošlo v průběhu testu k jejich nechtěné modifikaci. Obě anotace si můžete vyzkoušet na následujícím příkladu, kde v testovaném kódu máme jak závislost na globální proměnné, tak na statickém atributu. Zkuste různě měnit enabled/disabled u obou anotací a sledujte jak budou test cases procházet nebo selhávat.

$GLOBALS['globalValue'] = 10;

class SomeClass
{
    public static $staticProperty = 1;

    public function getStaticValue()
    {
        $oldValue = self::$staticProperty;
        ++self::$staticProperty;

        return $oldValue;
    }

    public function getGlobalValue()
    {
        $oldValue = $GLOBALS['globalValue'];
        ++$GLOBALS['globalValue'];

        return $oldValue;
    }
}
require_once "SomeClass.php";

/**
 * @backupStaticAttributes enabled
 * @backupGlobals enabled
 */
class SomeClassTest extends PHPUnit_Framework_TestCase
{
    public function testGlobalValue()
    {
        $object = new SomeClass();
        $this->assertEquals(10, $object->getGlobalValue());
    }

    public function testGlobalValueSecondTest()
    {
        $object = new SomeClass();
        $this->assertEquals(10, $object->getGlobalValue());
    }

    public function testStaticValue()
    {
        $object = new SomeClass();
        $this->assertEquals(1, $object->getStaticValue());
    }

    public function testStaticValueSecondTest()
    {
        $object = new SomeClass();
        $this->assertEquals(1, $object->getStaticValue());
    }
}

Defaultní chování frameworku PHPUnit ve verzi 3.7.0 je: @backupGlobals enabled, @backupStaticAttributes disabled a je možné toto změnit pomocí direktiv v konfiguračním XML souboru, který si podrobně představíme v některém z dalších dílů. K výše uvedeným anotacím ještě jedno doporučení – stavíte-li kód od základů, nechte obě anotace vypnuté a nedopusťte zanesení jakékoli špíny v podobě závislostí na globálním stavu. Prvními příznaky tohoto neduhu vám budou náhodně padající testy. U legacy kódu je lepší nechat obě anotace zapnuté a vypínat je až po důkladném refaktoringu a pokrytí testy.

Problematice globálního stavu se ještě budeme podrobně věnovat v druhé části seriálu, která bude věnována tvorbě kvalitnějšího, testovatelného, kódu.

@covers

Anotace se používá pro označení metod (příp. tříd), které testovací případ pokrývá. Všechny anotace obsahující „cover“ nebo „coverage“ se používají pro omezování, příp. přesnější generování code-coverage reportu (pokrytí kódu testy). Touto anotací říkáme, že informace (např. „navštívené“ řádky kódu) získané z průběhu následujícího test case chceme použít pro generování code-coverage pouze uvedených metod nebo tříd. Pokud test case pokrývá i jiné metody, pro code-coverage nebudou nabyté informace použity. Anotace může nabývat hodnot:

ClassName::methodName test case pokrývá uvedenou metodu
ClassName test case pokrývá celou uvedenou třídu
ClassName<extended> test case pokrývá celou uvedenou třídu a všechny její předky (třídy, rozhraní)
ClassName::<public> test case pokrývá všechny veřejné (public) metody uvedené třídy
ClassName::<protected> test case pokrývá všechny chráněné (protected) metody uvedené třídy
ClassName::<private> test case pokrývá všechny soukromé (private) metody uvedené třídy
ClassName::<!public> test case pokrývá všechny metody uvedené třídy, kromě veřejných
ClassName::<!protected> test case pokrývá všechny metody uvedené třídy, kromě chráněných
ClassName::<!private> test case pokrývá všechny metody uvedené třídy, kromě soukromých

@coversNothing

Opak předešlé – touto anotací můžeme říci, že následující test case „nepokrývá“ žádnou metodu ani třídu. Nepokrývá samozřejmě není úplně přesný výraz – jde o to, že informace získané na základě takto označeného test case nebudou použity pro generování code-coverage.

@codeCoverageIgnore

Vyřazení celé metody nebo třídy z generování code-coverage. Tato anotace se používá v testovaném kódu, ne v testu!

@codeCoverageIgnoreStart, @codeCoverageIgnoreEnd

Vyřazení bloku kódu z code-coverage. I tyto dvě anotace se používají v testovaném kódu, ne v samotných testech.

@dataProvider

Velice užitečná anotace, která vám ušetří spoustu času! Využijete ji především v případě, kdy potřebujete ověřit nějakou funkčnost sadou různých vstupních hodnot a nechcete dokola psát x podobných assertů nebo celých test cases. Pojďme si anotaci ukázat na příkladu:

class CalcTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider getTestAddData
     */
    public function testAdd($expectedResult, $x, $y)
    {
        $calc = new Calc();
        $this->assertEquals($expectedResult, $calc->add($x, $y));
    }

    public function getTestAddData()
    {
        return array(
            array(3, 1, 2),
            array(10, 5, 5),
            array(0, 5, -5)
        );
    }
}

Ano, opět naše oblíbená třída Calc! Mimochodem, Britové mají pro podobné situace krásný idiom: Slowly Slowly Catchy Monkey. Ale zpět k tématu – předpokládejme, že chceme otestovat metodu add(), která přijímá dva parametry a vrací jejich součet. Namísto toho abychom psali velké množství asertů na všechny možné případy, použijeme pouze jeden asert a anotaci @dataProvider. Hodnotou anotace je název metody vracející iterovatelnou kolekci polí, jejichž prvky budou dosazeny jako parametry testovací metody.

Testovací metoda bude tedy zavolána tolikrát, kolik polí parametrů vrácená kolekce obsahuje. V našem případě třikrát, poprvé s parametry: 3, 1, 2 atd. Úmyslně se držím pojmu „iterovatelná kolekce“, protože nemusí vždy jít jen o „pole polí“, ale i o instanci třídy implementující rozhraní Iterator. U anotace @dataProvider pozor na jedno omezení – odkazovaná metoda musí být veřejná (public)!

@depends

Anotace @depends je opět příkladem něčeho, co by vaše testy neměly vůbec obsahovat. Její pomocí je možné určitým způsobem řídit pořadí, v jakém jsou jednotlivé test case spouštěny, což stejně jako závislost na globálním stavu porušuje pravidla dobrých testů. Jako v případě @backupGlobals a @backupStaticAttributes, i tato anotace se používá jen jako pomůcka pro „debordelizaci“ legacy kódu.

@expectedException, @expectedExceptionCode, @expectedExceptionMessage

S první anotací ze seznamu jsme se již setkali v minulém díle, kde jsme ji používali k ověření, zda test case vyvolá výjimku zadané třídy. A to je přesně její účel. Pokud v testovací metodě, která je touto anotací označena, nedojde k vyvolání výjimky uvedené třídy, pak je test case označen jako neúspěšný (failed). Druhé dvě anotace (s postfixem Code a Message) mají obdobnou úlohu, pomocí nich je možné ještě více upřesnit jakou výjimku očekáváme a to pomocí jejího kódu nebo chybové zprávy.

Při používání anotace @expectedException je třeba pamatovat na fakt, že veškeré odchytitelné chyby PHP (warning, notice, …) jsou v PHPUnit interně převáděny na výjimky! Důrazně se proto nedoporučuje očekávat výjimku bázové třídy Exception, protože může dojít jak k falešně pozitivním, tak falešně negativním výsledkům. Ukažme si to na příkladu:

class SomeClass
{
    public function throwsInvalidArgumentException()
    {
        throw new InvalidArgumentException();
    }

    public function shouldThrowExceptionButDoesNot()
    {
        // throw new InvalidArgumentException();

        // bad logic error!
        explode("foo");

        return false;
    }
}
class SomeClassTest extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException InvalidArgumentException
     */
    public function testExceptionIsThrown()
    {
        $s = new SomeClass();
        $s->throwsInvalidArgumentException();
    }

    /**
     * @expectedException Exception
     */
    public function testExceptionIsNotThrownButTestIsOk()
    {
        $s = new SomeClass();
        $s->shouldThrowExceptionButDoesNot();
    }
}

Test první metody throwsInvalidArgumentException je naprosto v pořádku – očekáváme výjimku typu InvalidArgumentException a ta je skutečně vyvolána. Druhý test bude však falešně pozitivní, protože očekáváme výjimku bázové třídy Exception, ale naše metoda shouldThrowExceptionButDoesNot žádnou nevyvolá! Jak je tedy možné, že je test označený za úspěšný?

V metodě shouldThrowExceptionButDoesNot máme logickou chybu (chybějící parametr), která vyvolá Warning a ten je interně převeden na výjimku třídy PHPUnit_Framework_Error_Warning, která je samozřejmě potomkem třídy Exception a je tedy naprosto korektně odchycena naší anotací!

Některé verze PHPUnit < 3.7.0 dokonce zakazují používat v této anotaci bázovou třídu Exception, ale v aktuální verzi bylo toto omezení zrušeno. Pokud si nejste skutečně jisti, co děláte, pak zápis @expectedException Exception raději vůbec nepoužívejte!

@requires

Touto anotací říkáme, že pro korektní běh testu je nutné aby prostředí, ve kterém test spouštíme splňovalo definované podmínky. Těmi můžou být např. verze PHP, verze PHPUnit, existence nějaké core funkce nebo rozšíření. Není-li některé z omezení splněno, pak je test označen jako skipped (viz. druhý díl seriálu). Příklady omezení:

Klíčové slovo Význam Příklady
PHP Minimální verze PHP @requires PHP 5.3.3
PHPUnit Minimální verze PHPUnit @requires PHPUnit 3.6.3
function Existence core funkce nebo metody @requires function imap_open, @requires function ReflectionMethod::setAccessible
extension Přítomnost rozšíření @requires extension mysqli

@test

Pokud z nějakého důvodu nechceme nebo nemůžeme testovací metodu označit prefixem „test“ a tím ji nechat frameworkem automaticky spouštět, pak můžeme využít tuto anotaci, která má stejný efekt.

/**
 * @test
 */
public function runThisTest() {/*...*/}

@testdox

Význam této anotace si ukážeme později, až se budeme zabývat XML konfigurací frameworku a popisem výstupních formátů. Pro tuto chvíli alespoň stručně – díky ní je možné ve formátu Testdox nahradit automaticky vygenerovaný název testovací metody námi definovaným.

/**
  * @testdox Calc can sum
  */
public function testCalcIsAbleToSumTwoNumbers() {/*...*/}

Namísto automaticky vygenerovaného názvu „Calc is able to sum two numbers“ bude ve výstupu uvedeno „Calc can sum“.

@runInSeparateProcess, @runTestsInSeparateProcesses

Jak už název napovídá, tyto anotace slouží ke spouštění jednotlivých test cases nebo celých sad v oddělených procesech. K čemu je toto dobré? V mnoha případech opět k testování nejrůznějšího legacy kódu. Jakkoli se PHPUnit snaží aby všechny testovací případy probíhaly ve vzájemně nezávislém prostředí, ne vždy se mu to úplně daří. Jednou ze situací, se kterou si PHPUnit neumí poradit je vícenásobná deklarace třídy nebo definice konstanty. Řešení je nasnadě – spouštět všechny testy v samostatných procesech, ale to má často za následek zpomalení testů, proto toto chování není defaultní.

@outputBuffering

Anotace @outputBuffering je víceméně obdoba funkce ob_start v PHP a slouží k bufferování výstupu uvnitř test case. Narazíme na ni ještě v některém z příštích dílů, používá se např. k testování HTTP hlaviček.

@ticket

Pomocí této anotace jsme schopni jednak označit, které testy patří ke kterým issue, ale hlavně propojit testy s naším Bug-tracking systémem, kterým může být např. Github. Výborný článek na toto téma před časem napsal Radim Daniel Pánek na webu phpunit.cz, rozhodně stojí za přečtení!

Příště…

To je z dnešního průletu anotacemi PHPUnit vše. Jak jsem slíbil, příště to bude opět více o praktických příkladech a vrhneme se na jednu z nejdůležitějších kapitol – odstiňování závislostí pomocí mockování. Stay tuned!

Komentáře

Subscribe
Upozornit na
guest
28 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Kokeš

Tak to vypadá, že to PHPčkaři při tom opisování z Javy zase nějak popletli. Anotace, které řídí chod programu (v tomto případě testu) patří přímo do kódu — do komentářů se píší maximálně dokumentační anotace, které ale na běh programu nemají vliv a jsou to jen metadata do JavaDocu.

Enemy unknown

Vrat se zpatky na idnes

arron

My to víme, ale protože tohle PHPčko nativně nepodporuje, tak jsme si to lehce přiohnuli no :-)

Kokeš

No budiž, jako dočasné řešení. Ale jsou aspoň nějaké snahy, aby se to standardizovalo a anotace se staly normální součástí jazyka resp. jeho specifikace? Je to dost užitečná věc.

Radek Miček

Je to dost užitečná věc.

Ani bych neřekl – vzhledem k tomu, že atributy v C#/anotace v Javě nejdou přidat k cizímu kódu bez toho, abych ho změnil (tj. bez změny zdrojáku nebo bajtkódu).

Kokeš

Překvapivě :-) To je asi jako si stěžovat, že v takovém jazyce nemůžu měnit metody tříd, aniž bych přepsal zdroják nebo bajtkód.

something

Ztezuj si tady https://bugs.php.net/. Na zdrojaku ti s tim asi moc nepomuzou.

Radek Miček

Ne, v případě C# to je jako stěžovat si na použití Service Locatoru, když jde použít DI. C# má lepší nástroj než jsou atributy.

Kokeš

Ale DI je přece jen jedna z mnoha možností využití anotací. Opravdu nevidím problém v tom, že anotace nejdou měnit bez úpravy zdrojáku/bajtkódu (totéž mi nevadí u metod/proměnných) a považuji je za užitečné i s tímto „omezením“.

Radek Miček

Myslel jsem fakt, že atributy odpovídají Service Locatoru a vhodně použité Expression Trees odpovídají DI. Service Locator je často považován za antivzor a ze stejných důvodů jsou antivzor i atributy.

Kokeš

To je ale přece věc konkrétní aplikace anotací, jejich konkrétní využití. Anotace jako takové jsou jen způsob, jak třídě/metodě/vlas­tnosti dát nějaké atributy a pak si je někde jinde (typicky v nějakém frameworku) číst a podle toho s těmi objekty zacházet. Nic víc, nic míň.

Jestli je někdo používá jako Service Locator nebo nějak špatně, je to jeho chyba, ne chyba anotací.

Radek Miček

Anotace jako takové jsou jen způsob, jak třídě/metodě/vlas­tnosti dát nějaké atributy a pak si je někde jinde (typicky v nějakém frameworku) číst a podle toho s těmi objekty zacházet.

Přesně tak. Problém je ve čtení anotací – jde v podstatě o čtení z globálních konstant indexovaných typem/metodou apod. Když čtete z anotací, tak váš kód závisí na globálních konstantách a rázem se zhoršuje jeho znovupoužitelnost a testovatelnost.

Kokeš

To je ošklivé a falešné překrucování :-) Je to asi jako říct, že umístění metody do třídy je taky fuj fuj globální konstanta indexovaná balíčkem (jmenným prostorem) a názvem třídy. A že to zhoršuje znovupoužitelnost a testovatelnost.

Ano, mohly bychom všechny metody a deklarace proměnných nasypat na jednu hromadu, udělat z nich funkce a pak z nich ty třídy resp. objekty sestavovat až za chodu nebo při spuštění programu na základě konfigurace. Ale to už jsme úplně jinde, to je jiné paradigma.

Kokeš

Pokusím se to vysvětlit ještě jinak: stejně jako můžu metodu označit jako privátní, veřejnou nebo jinou, můžu ji pomocí anotace označit jako makovou — přičemž co je „maková“ není definováno ve specifikaci jazyka, ale je to moje vlastní označení, které jsem si vymyslel a používám ho. U označení „private“ (nebo jiných klíčových slov) ti snad nevadí, že jsou to „globální konstanty“ vázané na třídu/metodu, ne? Díky tomu je možné jazyk (poměrně libovolně) rozšiřovat, aniž by se musela měnit jeho specifikace.

Radek Miček

Když mám nějakou metodu jako veřejnou, tak ji stále ještě mohu skrýt (např. přetypováním na vhodný nadtyp). Nebo v OCamlu můžete modul omezit více různými rozhraními (signaturami). Zatímco, když nastavím např. mapování objektu v C# do databáze pomocí atributů, tak už to nemohu změnit nebo nemohu nastavit mnoho různých mapování. Nebo, když používám objekt z nějaké knihovny, tak ho nemohu namapovat do své DB pomocí atributů, protože je nemohu přidat. Proto podporuje Entity Framework ještě jiný způsob mapování – a to tzv. Fluent API, kde se využívají právě Expression Trees.

Kokeš

Ale já snad nikde netvrdil, že anotace jsou všelék a použitelné kdykoli. Jasně, že jsou případy, kdy se nehodí, nebo je nejde použít. Ale v jiných případech dobře použít jdou — a proto jsem psal, že je považuji za užitečné. Silniční auto snad taky považujeme za užitečné, přestože s ním nejde jezdit po poli nebo po poušti, ne?

Radek Miček

Proč jazyk komplikovat anotacemi, když existují obecnější mechanismy, které nejsou o mnoho složitější?

Clary

No ona Java sama o sobě taky není žádnej zázrak :)

ic

A co @assert annotation ( http://sebastian-bergmann.de/archives/628-Improved-Skeleton-Generator-in-PHPUnit-3.html ) ?

Je to samozřejmě jen taková hračka, ale moc pěkná. Rád tohle používám, pokud je to alespoň trochu možné.

puty

Tvrdenie, že anotácie sú komentáre so špeciálnym významom je úplne zcestné. Každý jeden komentár má špeciálny význam – ale jeho význam je určený pre prográmatora. Anotácie sú metadáta určené pre „program“. To, že sú v PHP zapísane ako komentár, je len nutné zlo. PHPDoc anotácie sú určené predovšetkým na generovanie dokumentácie, alebo ich využívaju aj vývojové prostredia – túto kategóriu anotácie je OK považovať za špeciálne komentáre. Špeciálne sú tým, že sú štruktúrované a s jasnou sémantikou a teda tiež sa jedná o metadáta. Akonáhle však využijem anotáciu tak, že k nej pristupujem cez reflexiu počas vykonávania skriptu jedná sa už o metadáta, ktoré sú určené pre interpreter a to v žiadnom prípade nie je možné označiť ako komentár.

Michal

Hlavne je to uplne „o hovne“. Proc maji lidi v ceskych diskuzich potrebu rozebirat neco co je spatne, ale stejne s tim nic nenadelaj? Proc se tady nediskutuje konstruktivne jako kdekoli jinde na webu (prevazne v EN)???

Kokeš

Když si to přečteš, tak zjistíš, že o tom tu padly asi dva komentáře a zvídavý dotaz, jestli to někdo řeší… Zbytek je o užitečnosti anotací obecně (bez ohledu na to, jestli se píší do komentářů nebo jsou součástí jazyka).

puty

Diskusiu som samozrejme čítal. Tá rieši, prečo anotácie nepatria do komentárov. Mne však vadí, že autor článku nazval anotáciu komentárom, lebo nechcel použiť výraz metadáta. To že v PHP sú komentáre znásilnené tak, aby reprezentovali metadáta pre runtime, na ktoré v iných jazykoch existujú špecializované konštrukty, vóbec neriešim.

Martin Hassman

Tím, že hovno nazveme smrkovou šiškou se smradu nijak nezbavíme. A tím, že se u anotací budeme vyhýbat označení komentář, nijak onen komentářový původ nezastřeme – každý ho tam vidí, každému to bude smrdět, není proč to skrývat.

Kokeš

+1

No tedy, klobouk dolů, tohle je snad tvůj první komenrář tady na Rootu, který dává smysl, není to jen ubohý výkřik a obsahuje docela rozumný názor.

RDPanek

Ahoj, pěkný článek :-)

Od verze PHPUnit 3.6.0 jsou k dispozici anotace @small, @medium a @large https://github.com/sebastianbergmann/phpunit/issues/58

a `@ticket` is now an alias for `@group`.


/**
*
* @covers Trida::testo­vanaMetoda
* @author RDpanek
* @ticket 1
*/
public function testFirst()


phpunit --list-groups

Available test group(s)
RDpanek
1

http://grip.espace-win.org/doc/apps/pear/PHPUnit/ChangeLog.markdown

Podbi

Díky za zajímavý článek o části PHPUnit, která není běžně až tak známá. Něco takového zde pro domácí komunitu chybělo. Těším se na další pokračování.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.