Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Wprowadzenie do PHPUnit
By Michał Kowalik
Czym są testy jednostkowe?
● W programowaniu metoda testowania tworzonego
oprogramowania poprzez wykonywanie testów
weryfi...
Korzyści
● Są automatyczne
Odbywają się za nas, nie musimy pamiętać by ręcznie sprawdzić jakiś
tam jeszcze edge-case.
● Ba...
Korzyści
● Pozwalają wykrywać problemy na etapie tworzenia
aplikacji.
● Skracają czas programowania.
Nie musimy przeskakiw...
Korzyści
● PHP jest dynamicznym językiem
Wykrywanie błędów składni, nieistniejących metod, niewłaściwego
wykorzystania typ...
Korzyści
● Testy nabierają znaczenia gdy nasz projekt rośnie
Początkowo testy mogą wydawać się zbędę i niepotrzebnie nas
o...
Korzyści tylko wtedy gdy:
● Testy działają szybko
● Nie generują dodatkowych błędów
Są napisane w możliwie prosty sposób
●...
Instalacja (phar) - zalecana
#unix
wget https://phar.phpunit.de/phpunit.phar
chmod +x phpunit.phar
mv phpunit.phar phpunit...
Instalacja 3.7 (pear) - przestarzała
pear update-channels
pear config-set auto_discover 1
pear channel-discover pear.phpun...
Konfiguracja
● phpunit.xml
Dzięki niemu możemy skonfigurować środowisko w jednym
miejscu i uruchomić testy poprzez ./phpun...
phpunit.xml
<?xml version="1.0"
encoding="UTF-8"?>
<phpunit
backupGlobals="true"
backupStaticAttributes="true"
bootstrap="...
phpunit.xml
● backupStaticAttributes="true"
Przełącznik te pozwala za zachowanie zmiennych statycznych
pomiędzy testami. Z...
Jak działa PHPUnit?
● bootstrap.php → ładuje klase TestTest → setUpBeforeClass()
→ setUp() → testTest1() → tearDown() → se...
Testy weryfikujemy porównaniami
self::assertEquals() // ==
self::assertSame() // === lepsze przy testowaniu tablic
self::a...
Prosty przykład
<?php
namespace tests;
class ValidTest extends Kohana_UnitTest_TestCase
{
/**
* @covers Valid::min_value
*...
@dataProvider
public function
dataTestMinValue()
{
return array(
array(0, 0, true),
array(0, 1, false),
array('23', 0, tru...
Przechwytywanie wyjątków
/**
* @covers Valid::min_value
* @expectedException ExceptionClass
* @expectedExceptionCode 123
*...
Testy jednostkowe
● Powinny działać ultra szybko.
● Weryfikują działanie cząstki testowanego kodu
● Skupiamy się jedynie n...
Testy jednostkowe
function abc($a, $b)
{
$c = $a;
if ($a > 0)
{
$c += $b;
if ($b < 0)
{
$c *= $b;
}
}
return $c;
}
public ...
Co oznacz że kod jest łatwy do
przetestowania?
● Zależności można łatwo zastąpić makietami
obiektów (Dependancy Injection)...
Dependency Injection
● Największym wrogiem testów jest tzw.
Hardcodeded dependency, czyli zależność której nie
możemy w ła...
Dependency Injection
● By kod przetestować należy go odpowiednio
zmodyfikować, szczególnie w przypadku gdy kod
został stwo...
Makiety obiektów
● Stubs
Symulują / udają działanie
obiektów.
$stub = $this->getMock('Mail');
$stub->expects($this->any())...
Makiety obiektów
● W większość frameworków stuby i mocki są w
rzeczywistości tym samym obiektem.
● W praktyce częściej sto...
Makiety obiektów
● Parametry dla expects()
self::any()
self::never()
self::atLeastOnce()
self::once()
self::exactly($count...
Makiety obiektów
● with() - Akceptuje dowolną listę argumentów:
self::anything()
self::contains($value)
self::arrayHasKey(...
Makiety obiektów
● will() → wartości zwracana przez metodę
self::returnValue($value)
self::returnArgument($argumentIndex)
...
Makiety obiektów
namespace tests;
class ClassToMock
{
public function method($arg)
{
throw new Exception('Original method ...
Makiety obiektów
● XpMock → warrper dla PHPUnit mocks
$this->mock('MyClass')
->getBool(true)
->getNumber(1)
->getString('s...
Makiety globalnych obiektów i funkcji
//application
namespace app
{
class TestObject
{
public function method()
{
$model =...
Reflection API
● Gdy piszemy dla testy dla cudzego kodu, po fakcie,
bardzo przydatnym narzędziem jest Reflection API.
Pozw...
TDD
● Test Driven Development
● Polega na stworzeniu testów przed przystąpieniem
do kodowania (praca red to green)
● Podej...
Code Coverage
● Gotowe narzędzie do analizowania pokrycia testów
jednostkowych.
● Dobrze jest stosować tag @covers. Raport...
Code Coverage
● Dodatkowo raport zawiera metrykę kodu
CRAP. Jeżeli osiągnęliśmy 100% a
metryka >= 100 powinniśmy
refaktory...
Testy integracyjne
● Weryfikacja poprawności komunikacji między aplikacją
a zewnętrznymi zasobami (gł. baza danych).
● Pow...
Testy integracyjne
● Przykładem dobrego ORM (pod kątem testów
jednostkowych) jest ten z Zend Framework.
● ORM oparte o Act...
Testy funkcjonalne (e2e)
● Pozwalają testować wymagania użytkownika
class TestFunctional extends PHPUnit_Extensions_Seleni...
Testowanie MVC
● Nie ma potrzeby tworzenia testów e2e by
zweryfikować działanie akcji.
● Przykładem może być Zend Framewor...
Do przeczytania
● http://phpunit.de
● http://artofunittesting.com/
● xUnit Design Patterns
● PHP Reflection API
● Art of U...
Upcoming SlideShare
Loading in …5
×

Wprowadzenie do PHPUnit

Wprowadzenie do tworzenia testów jednostkowych w PHP (PHPUnit, testy jednostkowe, testy funkcjonalne, testy integracyjne).
Wersja: alpha

  • Login to see the comments

Wprowadzenie do PHPUnit

  1. 1. Wprowadzenie do PHPUnit By Michał Kowalik
  2. 2. Czym są testy jednostkowe? ● W programowaniu metoda testowania tworzonego oprogramowania poprzez wykonywanie testów weryfikujących poprawność działania pojedynczych elementów (jednostek) programu. ● Wyróżniamy następujące rodzaje: - testy jednostkowe weryfikacja kodu - testy integracyjne weryfikacja komunikacji z zasobami np. bazą danych - testy funkcjonalne /end-to-end/ weryfikacja wymagań użytkownika
  3. 3. Korzyści ● Są automatyczne Odbywają się za nas, nie musimy pamiętać by ręcznie sprawdzić jakiś tam jeszcze edge-case. ● Bardzo dobrze wpływają na jakość kodu Dzielimy program na mniejsze klocki. Zaczynamy korzystać z wzorców projektowych. Za każdym razem musimy odpowiedzieć sobie na pytanie Jak ja to potem przetestuje?
  4. 4. Korzyści ● Pozwalają wykrywać problemy na etapie tworzenia aplikacji. ● Skracają czas programowania. Nie musimy przeskakiwać do przeglądarki i tracić czasu na ręczne testy. Możemy pracować bez odrywania się od IDE.
  5. 5. Korzyści ● PHP jest dynamicznym językiem Wykrywanie błędów składni, nieistniejących metod, niewłaściwego wykorzystania typów, błędnego wykorzystania funkcji wbudowanych ● Weryfikacja działania na różnych platformach Programiści mogą pracować na Windowsach, ale serwery są zazwyczaj na Linuxach Co pewien czas wychodzi nowa wersja PHP, testy pozwalają nam sprawdzić czy program zadziała w nowym środowisku (np. w HHVM) Obecność testów jest konieczna do wykorzystania narzędzi typu Continous Integration (Github / Travis).
  6. 6. Korzyści ● Testy nabierają znaczenia gdy nasz projekt rośnie Początkowo testy mogą wydawać się zbędę i niepotrzebnie nas obciążać W marę jak rozbudowujemy projekt testy pozwalają zweryfikować czy zmiany nie uszkodziły przedniej funkcjonaliści Pozwalają nowemu programiści w zespole sprawdzić – samodzielnie – czy czegoś nie zepsuł.
  7. 7. Korzyści tylko wtedy gdy: ● Testy działają szybko ● Nie generują dodatkowych błędów Są napisane w możliwie prosty sposób ● Są w stanie przetrwać ew. modyfikacje kodu Testy nie mogą być zbyt szczegółowe ● Generalnie pisanie testów jest sztuką samą w sobie Oddzielna specjalizacja
  8. 8. Instalacja (phar) - zalecana #unix wget https://phar.phpunit.de/phpunit.phar chmod +x phpunit.phar mv phpunit.phar phpunit #phpunit.bat c:phpphp.exe c:phpphpunit.phar %* #sprawdzenie poprawności > phpunit –-version PHPunit 4.1.0 by Sebastian Bergmann.
  9. 9. Instalacja 3.7 (pear) - przestarzała pear update-channels pear config-set auto_discover 1 pear channel-discover pear.phpunit.de pear install --alldeps --force phpunit/PHPUnit pear install --alldeps --force phpunit/DbUnit pear install --alldeps --force phpunit/PHPUnit_Selenium pear install --alldeps --force phpunit/PHPUnit_SkeletonGenerator pear install --alldeps --force phpunit/PHPUnit_Story pear install --alldeps --force phpunit/PHP_CodeCoverage pear install --alldeps --force phpunit/PHP_Invoker
  10. 10. Konfiguracja ● phpunit.xml Dzięki niemu możemy skonfigurować środowisko w jednym miejscu i uruchomić testy poprzez ./phpunit ● bootsrap.php W tym piku inicjujemy projekt (ustawiamy globalne zmienne, class loadery, startujemy framework). Bardzo często zawiera prawie to samo co public/index.php Położenie można ustalić w phpunit.xml
  11. 11. phpunit.xml <?xml version="1.0" encoding="UTF-8"?> <phpunit backupGlobals="true" backupStaticAttributes="true" bootstrap="bootstrap.php" cacheTokens="false" colors="false" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnError="false" stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false" timeoutForSmallTests="1" timeoutForMediumTests="10" timeoutForLargeTests="60" strict="false" verbose="false" > <testsuites> <testsuite name="Kohana Tests"> <directory>./</directory> </testsuite> </testsuites> <!-- Selenium browser set --> <selenium> <browser name="Internet Explorer" browser="*iexplore" /> <browser name="Firefox" browser="*firefox" /> </selenium> <!-- Code coverage filter --> <filter> <whitelist processUncoveredFiles... ...FromWhitelist="false"> <directory suffix=".php"> ../../../hako/classes </directory> </whitelist> </filter> <!-- Code Coverage report --> <logging> <log type="coverage-html" target="./report" charset="UTF-8" highlight="false" lowUpperBound="35" highLowerBound="70"/> </logging> </phpunit>
  12. 12. phpunit.xml ● backupStaticAttributes="true" Przełącznik te pozwala za zachowanie zmiennych statycznych pomiędzy testami. Zmienne globalne są zachowywane domyślnie. ● bootstrap="bootstrap.php" Wskazuje położenie pliku inicjującego testy ● <filter> Pozwala na odfiltrowanie plików źródłowych dla generowanych raportów pokrycia kodu.
  13. 13. Jak działa PHPUnit? ● bootstrap.php → ładuje klase TestTest → setUpBeforeClass() → setUp() → testTest1() → tearDown() → setUp() → testTest2() → tearDown() → … → tearDownAfterClass() ● Automatycznie konwertuje błędy do wyjątków które są potem wyświetlane w opisie pod testem. ● Automatycznie traktuje klasy z suffiksem Test jak test jednostkowy ● Wykonuje metody z prefiksem test lub oznaczone komentarzem @test. ● Metody setUp() oraz tearDown() służa do „sprzątania” przed i po testach.
  14. 14. Testy weryfikujemy porównaniami self::assertEquals() // == self::assertSame() // === lepsze przy testowaniu tablic self::assertEmpty() self::assertContains() self::assertCount() self::assertTrue() self::assertRegExp() self::assertFileExists() self::assertJsonStringEqualsJsonFile() self::fail($message) ● W wersji 3.7 self:: → $this-> ● I wiele innych...
  15. 15. Prosty przykład <?php namespace tests; class ValidTest extends Kohana_UnitTest_TestCase { /** * @covers Valid::min_value */ public function test_min_value() { $actual = Valid::min_value(33, 40); $this->assertEquals(40, $actual); } }
  16. 16. @dataProvider public function dataTestMinValue() { return array( array(0, 0, true), array(0, 1, false), array('23', 0, true), array('sdfdsf', 0, false), ); } /** * @dataProvider dataTestMinValue * @covers Valid::min_value */ public function test_min_value ($value, $limit, $excepted) { $actual = Valid::min_value($value, $limit); $this->assertEquals($excepted $actual); } ● Testują funkcję zdarza się ze powielamy testy różnice się tylko o parametry, definiujące provider danych możemy temu zapobiec.
  17. 17. Przechwytywanie wyjątków /** * @covers Valid::min_value * @expectedException ExceptionClass * @expectedExceptionCode 123 * @expectedExceptionMessage Tekst wyj tkuą */ public function test_min_value ($value, $limit, $excepted) { functionUnderTest('should throw exception'); } ● @expectedExceptionCode oraz @expectedExceptionMessage są opcjonalne. ● Dla @expectedException domyślna klasa to Exception.
  18. 18. Testy jednostkowe ● Powinny działać ultra szybko. ● Weryfikują działanie cząstki testowanego kodu ● Skupiamy się jedynie na testowanej metodzie / funkcji. Testowany kod powinien działać nawet gdy zależny on od komponentu którego jeszcze nie ma → wszelkie powiązania powinniśmy zastępować mockami. ● Traktujemy testy jak użytkownika pisanego przez nas api. Powinniśmy formować kod tak by był łatwy do przetestowania.
  19. 19. Testy jednostkowe function abc($a, $b) { $c = $a; if ($a > 0) { $c += $b; if ($b < 0) { $c *= $b; } } return $c; } public function testAbc1() { $this->assertEquals(-1, -1); } public function testAbc2() { $this->assertEquals(1, -1); } public function testAbc3() { $this->assertEquals(1, 1); } ● Testujemy pojedynczą metodę / funkcję Dokładniej mówią testujemy pojedyncza ścieżkę wykonywania się tak by pokrycie kody wyniosło 100%. ● Nie testujemy frameworka, nie powinniśmy testować tej samej funkcjonalności w kilku miejscach.
  20. 20. Co oznacz że kod jest łatwy do przetestowania? ● Zależności można łatwo zastąpić makietami obiektów (Dependancy Injection) ● Złożoność cyklomatyczna jest niska. Tzn. jest stosunkowo niedużo ifów, forów itp., a ich zagnieżdżenia nie przekraczają głębokość ok. 5. ● Metoda nie powinna być nadmiernie długa (ok. 200 linijek). ● Cyklomatyczność oraz duża ilość metod prywatnych może sugerować utworzenie nowej klasy.
  21. 21. Dependency Injection ● Największym wrogiem testów jest tzw. Hardcodeded dependency, czyli zależność której nie możemy w łatwy sposób zastąpić makietą. public function login() { $login = $this->param('login'); $pass = $this->param('pass'); if ($this->authenticate($login, $pass)) { $this->redirect('/'); } else { $mail = new Mail(); $mail->subject = …; $mail->to = …; $mail->body = …; $mail->send(); } } ● Jak zastąpić $mail = new Mail();?
  22. 22. Dependency Injection ● By kod przetestować należy go odpowiednio zmodyfikować, szczególnie w przypadku gdy kod został stworzony przed napisaniem testów. ● Popularnymi sposobami na wstrzykiwanie zależności są: - Constructor injection (przez konstruktor) - Property injection (dodatkowa właściwość obiektu) - Factory method (metoda generująca obiekty) - Isolation of Control Container
  23. 23. Makiety obiektów ● Stubs Symulują / udają działanie obiektów. $stub = $this->getMock('Mail'); $stub->expects($this->any()) ->method('send') ->will( $this->returnValue(true) ); ● Mocks - Weryfikują czy prawidłowo korzystamy z obiektów. $mock = $this->getMock('Mail'); $mock->expects($this->once()) ->method('addTo') ->with( $this->equalTo('m@t.c') );
  24. 24. Makiety obiektów ● W większość frameworków stuby i mocki są w rzeczywistości tym samym obiektem. ● W praktyce częściej stosujemy stuby, ale zdarzają się mocki hybrydowe, czasami mockujemy obiekt które testujemy. ● Makiety obiektów pozwalają nam zasymulowac dowolną sytuację w badanym kodzie.
  25. 25. Makiety obiektów ● Parametry dla expects() self::any() self::never() self::atLeastOnce() self::once() self::exactly($count) self::at($index) ● Jeżeli któryś z warunków nie zostanie spełniony test zostanie oznaczony jako nieudany.
  26. 26. Makiety obiektów ● with() - Akceptuje dowolną listę argumentów: self::anything() self::contains($value) self::arrayHasKey($key) self::equalTo($value, $delta, $maxDepth) self::classHasAttribute($attribute) self::greaterThan($value) self::isInstanceOf($className) self::isType($type) self::matchesRegularExpression($regex) self::stringContains($string, $case) ● withAnyParameters() → cokolwiek ● Niespełnienie warunków zfailuje test.
  27. 27. Makiety obiektów ● will() → wartości zwracana przez metodę self::returnValue($value) self::returnArgument($argumentIndex) self::returnCallback($stub) self::returnSelf() self::returnValueMap($valueMap) self::throwException($exception) self::onConsecutiveCalls(...)
  28. 28. Makiety obiektów namespace tests; class ClassToMock { public function method($arg) { throw new Exception('Original method invoked'); } } class MockTest extends PHPUnit_Framework_TestCase { public function testMock() { $stubMock = $this->getMock('testsClassToMock'); $stubMock->expects($this->at(0)) ->method('method') ->with(self::equalTo('getMe33')) ->will(self::returnValue(33)); $stubMock->expects($this->at(1)) ->method('method') ->with(self::equalTo('getMe44')) ->will(self::returnValue(44)); self::assertSame(33, $stubMock->send('getMe33')); self::assertSame(44, $stubMock->send('getMe44')); } }
  29. 29. Makiety obiektów ● XpMock → warrper dla PHPUnit mocks $this->mock('MyClass') ->getBool(true) ->getNumber(1) ->getString('string') ->new(); ● Wymaga 5.4 (działa jako trait) ● https://github.com/ptrofimov/xpmock
  30. 30. Makiety globalnych obiektów i funkcji //application namespace app { class TestObject { public function method() { $model = ORM::factory('Test'); file_exists('some file'); } } } //tests namespace app { class ORM { static public function factory($className, $id=null) { echo "my ORM::factoryn"; return new stdClass; } } } //tests namespace app { function file_exists($filename) { echo "my file_existsn"; return file_exists($filename); } } namespace test { class TestTest extends PHPUnit_Framework_TestCase { public function testTest() { $obj = new appTestObject; $obj->method(); } } }
  31. 31. Reflection API ● Gdy piszemy dla testy dla cudzego kodu, po fakcie, bardzo przydatnym narzędziem jest Reflection API. Pozwala ono dostac się do niedostepnych zakamarków kodu. namespace tests; class SomeClass { private function prvMethod($arg1) { return $arg1; } } class PrvTest extends PHPUnit_Framework_TestCase { public function testPrivateMethod() { $obj = new testsSomeClass(); $class = new ReflectionClass($obj); $method = $class->getMethod('prvMethod'); $method->setAccessible(true); $method->invoke($obj, 'arg1'); } }
  32. 32. TDD ● Test Driven Development ● Polega na stworzeniu testów przed przystąpieniem do kodowania (praca red to green) ● Podejście to pozwala na tworzenie lepszych testów ● Mitem jest przeświadczenie że należy posiadać dokładne założenia projektowe by go zastosować ● TDD można stosować do pojedynczych tasków.
  33. 33. Code Coverage ● Gotowe narzędzie do analizowania pokrycia testów jednostkowych. ● Dobrze jest stosować tag @covers. Raport będzie brał pod uwagę tylko kod do którego stworzyliśmy testy intencjonalnie. ● Wymaga zainstalowanego Xdebug 2.1.3 (nie instaluj 2.2.4 bo nie działa, najlepiej 2.2.3). ● If ($a == 0) $c= 3 else $d = 5 traktowane jest jako jedno wyrażenie dlatego wymaganie jest stosowanie klamer.
  34. 34. Code Coverage ● Dodatkowo raport zawiera metrykę kodu CRAP. Jeżeli osiągnęliśmy 100% a metryka >= 100 powinniśmy refaktoryzować program. ● phpunit --coverage-html ./report ● phpunit.xml <filter> <whitelist processUncovered... ...FilesFromWhitelist="false"> <directory suffix=".php"> ../../../hako/classes </directory> </whitelist> </filter> <logging> <log type="coverage-html" target="./report" charset="UTF-8" highlight="false" lowUpperBound="35" highLowerBound="70"/> </logging>
  35. 35. Testy integracyjne ● Weryfikacja poprawności komunikacji między aplikacją a zewnętrznymi zasobami (gł. baza danych). ● Powinniśmy jedynie sprawdzać czy obiekty się poprawnie wstawiają / usuwają / pobierają. Nie powinniśmy mieszać z logiką biznesową (od tego są unit testy). ● Wymaga utworzenia zbioru testowego który należy odbudowywać przed wykonaniem każdego z testów. Gdy framework korzysta z PDO można wykorzystać hack z zagnieżdżonymi transakcjami https://github.com/wakeless/transaction_pdo/blob/master/TransactionPDO.php
  36. 36. Testy integracyjne ● Przykładem dobrego ORM (pod kątem testów jednostkowych) jest ten z Zend Framework. ● ORM oparte o ActiveRecord są trudne do testowania. Powodem jest powiązanie obiektu biznesowego z reprezentacją w bazie danych (Kohana, Yii). ● W aplikacji nie powinniśmy stosować statycznych zapytań SQL, a korzystać z tego co oferuje framework.
  37. 37. Testy funkcjonalne (e2e) ● Pozwalają testować wymagania użytkownika class TestFunctional extends PHPUnit_Extensions_SeleniumTestCase { const SITE_URL = 'http://beta.modeview.dev/'; protected function setUp() { $this->setBrowser('*firefox'); $this->setBrowserUrl(self::SITE_URL); } public function testTitle() { $this->open(self::SITE_URL); $this->assertElementPresent('div.languages'); } } ● Wymaga uruchomienia servera selenium java -jar selenium-server-standalone.jar
  38. 38. Testowanie MVC ● Nie ma potrzeby tworzenia testów e2e by zweryfikować działanie akcji. ● Przykładem może być Zend Framework → można mockować obiekt request / response i badać efekt działania akcji poszczególnych kontrolerów (ta sama zasada dotyczy metod typu before(), after()). ● Źródłem wielu błędów są widoki. Mają one ustalone parametry przekazywane z kontrolera, możemy więc generować ich rożne wartości i przekazywać do widoków.
  39. 39. Do przeczytania ● http://phpunit.de ● http://artofunittesting.com/ ● xUnit Design Patterns ● PHP Reflection API ● Art of Unit Testing [Part 2] ● https://github.com/ptrofimov/xpmock

×