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. 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. 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. 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. 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. 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
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
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. 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. 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. 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. @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. 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. 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. 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. 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. 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. 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. 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. 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. 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.
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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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