Pár otázek ohledně testování – PHP – Fórum – Programujte.com
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

Pár otázek ohledně testování – PHP – Fórum – Programujte.comPár otázek ohledně testování – PHP – Fórum – Programujte.com

 

Facedown0
Newbie
9. 8. 2015   #1
-
0
-

Ahoj,

nikdy jsem netestoval a tak jsem se to rozhodl změnit. Testy jsem se rozhodl psát na již rozpracované aplikaci, takže psát kód pomocí TDD se zatím nechystám, protože musím pokrýt testy kód, který teď mám.

Přečetl jsem několik článků ohledně testování a na většinu otázek jsem dostal odpověď, ale stále mi nějaké zbývají.

1. Psát vždy integrační testy?

Když už jsem hotov se všemi unit testy, měl bych se poté vrhnout na testy integrační? Tedy, místo mocků předat skutečný objekt a otestovat, jestli i tak vše funguje? Je to proces, kterým musí projít každá (nebo alespoň většina) kvalitních aplikací?

2. Test s databází je test integrační nebo jednotkový?

Je mi známo, že jedním z vlastností unit testů je izolace a rychlost, což je test s databází přesně naopak, ale jedná se v příkladu níže již o test integrační?

class Ban {
    private $database;
    private $email;

    public function __construct(Database $database, $email) {
        $this->database = $database;
        $this->email = $email;
    }

    public function is() {
        return $this->hasBan();
    }

    private function hasBan() {
        $query = "SELECT 1 FROM banned_users
        WHERE user_id = (SELECT ID FROM users WHERE email = ?)
        AND to_time - NOW() > 0";
        return (bool)$this->database->query($query, $this->email)->fetch();
    }
}

V tomto případě potřebuji databázi, abych něco ověřil, ale přijde mi, že se i přesto jedná o test jednotkový, protože stále testuji jednotku a to i když potřebuji databázi. Platí tedy vždy, že test s databází == integrační test nebo ne?

3. Testování výsledků, který podává databáze

Mám třídy, ve kterých jsou pouze dotazy, žádné podmínky nebo cykly. Měl bych i v tomto případě pro ně psát Unit/Integrační testy, abych zjistil jestli jsem napsal dotazy správně? Testování bych prováděl v testovácí databázi s testovacími daty a úklidem. Řekl bych, že bych měl, ale nejsem si na 100% jistý.

4. Rozdělit Unit testy od Integračních testů?

Měl bych zajistit, aby Unit testy byly v jiném adresáři než jsou integrační testy? Integrační testy přece jen trvají déle, takže bude mi k něčemu, pokud se budu snažit o rozdělení? Neboli - budu někdy potřebovat mít možnost ověřit pouze integrační testy bez unit testů a naopak?

5. Jak strukturovat testy?

Měl bych v adresáři testsvytvořit pod-adresáře, dle jmenných prostorů, které mé skutečné třídy používají nebo je rozdělit dle jiného uvážení?

6. Duplicita kódu když používám mocky

V některých případech testuji třídy, které potřebují stejné rozhraní. Tento opakující se mock se mi nechce stále dokola vytvářet, tak jsem si na něj vytvořil samotnou třídu, která vrací namockovaný objekt. Tuto třídu spokojeně používám na několika místech. Měl bych něco takového dělat nebo tam ty duplicity nechat a nestarat se o ně? Nicméně existují ještě další takové mocky(které mají stejné rohraní), které vyhazují výjimku nebo nesmí být nikdy zavolány, ale na ty již žádnou třídu nemám a také se mi párkrát opakují. Nelíbí se mi to, jak z toho ven?

7. Prověření všech případů

<?php

class InactiveState implements State {
    private $user;
    private $offlineState;

    public function __construct(OfflineState $offlineState, User $user) {
        $this->offlineState = $offlineState;
        $this->user = $user;
    }

    public function is() {
        if($this->offlineState->is())
            return $this->isInactive();
        return false;
    }

    private function isInactive() {
        return $this->user->getLogoutReason() === IUserStorage::INACTIVITY;
    }
}

Kód využívá třídu User, která je z Nette, OfflineState má rozhraní State a jedinou metodu is. Měl bych v tomto případě projít tímto procesem: Namockovat nejdřívě OfflineState tak, aby napřed vracel false a otestovat InactiveState jestli vrací false, dále třídu User a metodu getLogoutReason namockovat tak, aby vracela něco jiného než IUserStorage::INACTIVITY a OfflineState s metodou is nastavit na true a ověřit, jestli je výsledek také false? A projít takto všechny kombinace, které mohou nastat? Zdá se mi to složitý, ale nevím, zda je to potřeba nebo ne.

Omlouvám se za delší text, ale jsem v tomto začátečník a vše bych si rád ujasnil. Děkuji za jakékoliv vaše názory a odpovědi.

Nahlásit jako SPAM
IP: 213.220.250.–
Kit+15
Guru
9. 8. 2015   #2
-
0
-

#1 Facedown
1. Ano. Integrační testy se často píší v jiném jazyce, např. v shellu.

2. V jednotkových testech se databáze zpravidla nahrazuje mockem. Test s databází je tedy integrační.

3. Ano. Jednotkový test může kontrolovat syntaktickou správnost SQL dotazů. Mock zase odpovídá nějakými pseudovýsledky. Integrační se dělá s testovací databází.

4. Čas v tomto případě nehraje roli - vždyť jednotkové testy proběhnou během max. několika málo sekund. Spíš je důležité tyto testy oddělit kvůli snadné detekci zdroje chyby. Jednotkové testy testují jednotku, integrační testují komunikaci mezi jednotkami. Každý tedy dělá něco jiného.

5. Bez stejné struktury podadresářů podle namespace by to snad ani nešlo.

6. Čím méně opakujícího kódu, tím lépe. To platí i pro mocky. V každém testu určuješ, která sestava mocků se použije pro testování jednotky. Pro každou jednotku můžeš mít více testů, každý s jinými mocky, ale ty mocky mohou být sdíleny mezi různými testy.

7. Jakákoli čtyřtečka v kódu je problém, který se řeší individuálně. Saháš totiž přes hranici jednotky a přitom se nedá nahradit mockem. Konkrétně část "$this->user->getLogoutReason() === IUserStorage::INACTIVITY;" už do této metody v této třídě nepatří, ale podle Démétéřina zákona patří do třídy User. V této třídě by mělo zbýt jen její volání např. "$this->user->isInactive();" Tím se zbavíš skryté závislosti na IUserStorage a půjde to testovat.

Ten poslední bod vlastně naznačuje hlavní přínos TDD: Pokud se něco špatně mockuje, je to nejspíš špatně napsáno a musí se to předělat. Proto programy napsané touto technikou vypadají jinak. Bývají kratší, přehlednější a rychlejší.

Když už jsme u třídy InactiveState, tak mám takový pocit, že skoro nic nedělá. Co kdybys jí převedl pár úkolů z vyší vrstvy? Navíc bych raději viděl názvy metod s pozitivní logikou, tedy místo isInactive() změnit ten název na isActive(). Název metody is() považuji za zmatečný a nicneříkající.

I ten název třídy InactiveState je takový divný. Klidně bych použil název třídy State a volal bych metodu $state->isActive(). Tím také zmizí metoda is(), protože se stane nepotřebnou.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
Facedown0
Newbie
9. 8. 2015   #3
-
0
-

#2 Kit
Děkuji za tvůj čas a odpověď, jsem s ní spokojený.

Ohledně té 7. odpovědi - je problém při testování i tehdy, pokud rozhraní obsahuje konstantu a daná třída, která implementuje toto rozhraní také používá tuto konstantu?

Ohledně té negativní logiky máš pravdu, jdu to předělat :)

Potíž je v tom, že pro všechny stavy jsem si vytvořil třídu, tedy mám třídy jako Online, Offline, dále stavy, jestli má uživatel aktivní účet či jestli má ban. Všechny tyto třídy implementují rozhraní State, proto tak obecná metoda is(). V daném kontextu třídy mi to přijde přehledné. Nicméně, třídy jako Online, Offline a jim podobný budu muset smazat, protože toho sami o sobě moc nedělají.

Nahlásit jako SPAM
IP: 213.220.250.–
Kit+15
Guru
9. 8. 2015   #4
-
0
-

#3 Facedown
Pokud by ses ve třídě InactiveState odvolával na State::INACTIVITY, bylo by to v naprostém pořádku. Proto se konstanty do takových rozhraní dávají. Jenže se odvoláváš na konstantu v rozhraní, které nemáš v hlavičce třídy - tedy na cizince.

V podstatě by ti na tohle měla stačit jediná třída State s metodami isOnline(), isBanned(), isActive() apod. Za metodu s názvem is() bys také mohl být ukamenován, tak na to pozor :) Určitě ti taková třída nepřekročí můj obvyklý softlimit 65 řádek. a bude kompaktně informovat o stavu uživatele. On i takový SRP je nutné aplikovat s rozumem, abychom se neutopili v příliš rozdrobeném kódu.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
Facedown0
Newbie
9. 8. 2015   #5
-
0
-

#4 Kit

Dávám ti za pravdu, dobrý nápad, v mém případě dodržení SRP a OCP je asi extrém.

Ještě jednou děkuju za užitečné rady.

Nahlásit jako SPAM
IP: 213.220.250.–
Facedown0
Newbie
11. 8. 2015   #6
-
0
-

#2 Kit
Ještě bych měl otázku k té tvé odpovědi #6.

Na mockování používám Mockery a moje duplicita kódu, která se opakuje na mnoho místech v tomto tvaru vypadá takto:

<?php

private function mockedClass1() {
	return Mockery::mock('Class1')
	->shouldReceive('method1')->once()->andReturn(true)->mock();
}


Znamená to, že bych měl duplicitě zabránit třeba nějak takto?

<?php

class MockedClass1 {
	public function onceCalledMethod1() {
		return Mockery::mock('Class1')
		->shouldReceive('someMethod')->once()->andReturn(true)->mock();
	}

	public function neverCalledMethod1() {
		return Mockery::mock('Class1')
		->shouldReceive('method1')->never()->mock();
	}
}
Nahlásit jako SPAM
IP: 213.220.250.–
Kit+15
Guru
11. 8. 2015   #7
-
0
-

#6 Facedown
Promiň, Mockery nepoužívám, své mocky si píši sám dle vlastních potřeb.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
Facedown0
Newbie
11. 8. 2015   #8
-
0
-

#7 Kit
Na tento způsob jsem již také koukal. Zajímalo by mě, jak s nimi řešíš některé věci:

1. Mockery má metodu kterou si můžeš určit, kolikrát může být daná metoda zavolána. Pro představu:

Mockery::('Trida')->shouldReceive('mojeMetoda')->times(2);

Řešíš to i v těch tvých mockách, či to není zase tak důležité?

2. Pokud bych chtěl po nějaké metodě, aby mi vracela jiné výsledky - třeba jednou true, a potom false, jak bys to pomocí těch svých mocků řešil? Udělal by sis dvě třídy? Jak by se jmenovali?

3. Jak obecně pojmenováváš takové třídy, které máš uvedené jako mock? Zvaž, že máš třídu jménem Car, mock od této třídy by se jmenoval třeba MockedCar?

Nahlásit jako SPAM
IP: 213.220.250.–
Kit+15
Guru
11. 8. 2015   #9
-
0
-

#8 Facedown
1. Nepoužívám to, ale není to složité. Stačí mít v mocku statickou proměnnou s čítačem.

2. Ne, takové záležitosti řeším v jedné třídě. Není problém udělat její dvě instance.

3. Jako mocky nedávám třídy, ale jejich instance. Slovo "Mocked" neobsahuje skoro žádnou informaci, proto místo něj volím jiné slovo, které lépe vystihuje účel toho mocku. Ovšem nedávám ho do názvu, takže mock třídy Car se bude jmenovat opět Car, ale bude v jiném namespace. To mi umožňuje takový mock použít v kterémkoli testu, nevznikají tak zbytečné duplicity.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
Zjistit počet nových příspěvků

Přidej příspěvek

Toto téma je starší jak čtvrt roku – přidej svůj příspěvek jen tehdy, máš-li k tématu opravdu co říct!

Ano, opravdu chci reagovat → zobrazí formulář pro přidání příspěvku

×Vložení zdrojáku

×Vložení obrázku

Vložit URL obrázku Vybrat obrázek na disku
Vlož URL adresu obrázku:
Klikni a vyber obrázek z počítače:

×Vložení videa

Aktuálně jsou podporována videa ze serverů YouTube, Vimeo a Dailymotion.
×
 
Podporujeme Gravatara.
Zadej URL adresu Avatara (40 x 40 px) nebo emailovou adresu pro použití Gravatara.
Email nikam neukládáme, po získání Gravatara je zahozen.
-
Pravidla pro psaní příspěvků, používej diakritiku. ENTER pro nový odstavec, SHIFT + ENTER pro nový řádek.
Sledovat nové příspěvky (pouze pro přihlášené)
Sleduj vlákno a v případě přidání nového příspěvku o tom budeš vědět mezi prvními.
Reaguješ na příspěvek:

Uživatelé prohlížející si toto vlákno

Uživatelé on-line: 0 registrovaných, 16 hostů

 

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032024 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý