Unit testen in de praktijk
Indeling Algemene theorie van unit testing PHPunit
Wat is het doel van testen? Controleren of software correct werkt. Voldoet software aan vooraf gegeven specificaties? Blijft software correct werken na veranderingen?
Beperkingen van testen Het testen van een programma is een effectieve manier om de aanwezigheid van fouten in een programma aan te tonen, maar het is volkomen inadequaat om hun afwezigheid te bewijzen. Edsger W. Dijkstra 'The Humble Programmer' 1972 (!)
Wat is unit testing? Manier om na te gaan of een individueel onderdeel van een stuk software naar behoren werkt. Methode Klasse Module Compleet systeem 'Verdeel en heers'
Wat is unittesting Bij een gegeven input, is de output wat verwacht wordt? Output is niet noodzakelijk de waarde die de functie zelf teruggeeft. Input is niet noodzakelijk de parameters die aan de functie worden meegegeven.
Output <?php class Transaction /** * Handles transactions from our webshop. * * @var paymentservice */ protected $paymentservice; public function pay($orderid) $this->paymentservice->startpayment(); $this->paymentservice->pay($orderid); return $this->paymentservice->checkstatus($orderid);
Input <?php class Transaction public function getunpayedorders() $orders = $this->paymentservice->getopenorders(); $unpayedorders = array(); foreach ($orders as $order) if ($order['status'] == 'unpayed') $unpayedorders[] = $order->id; return $unpayedorders;?>
Tests zijn software KISS DRY Test smells Test patterns
Test smells 'Er is iets mis met de tests' Symptoom, geen oorzaak. Code smells. Behavior smells. Project smells.
Code smells Obscure test. Hard-coded test data. Test code duplication.
Behavior Smells Interface sensitivity Behavior sensitivity Data sensitivity Context sensitivity Frequent debugging Slow tests
Project smells Production bugs Buggy tests High test maintenance cost
Test patterns Assertion Fixture Test double Setup Teardown Test suite Test runner
Testbaarheid Test voor of terwijl je code schrijft, niet erna. Houd rekening met testbaarheid tijdens het schrijven van de code. Verdeel en test.
PHP Unit testing Simpletest PHPUnit Lime (Symphony) PHPT
PHPUnit Lid van de xunit familie: JUnit, NUnit, PyUnit Uitgebreid gedocumenteerd Veel voorbeelden beschikbaar Beste keuze voor PHP
Basis unit test <?php //File class.transaction.php class Transaction public function pay($orderid) $this->paymentservice->startpayment(); $this->paymentservice->pay($orderid); return $this->paymentservice->checkstatus($orderid); <?php require_once 'source/class.transaction.php'; //File class.transactiontest.php class TransactionTest extends PHPUnit_Framework_TestCase //Removed code for clarity. public function testpay() $orderid = 1; $result = $this->transaction->pay($orderid); $this->assertequals('success', $result, '');
Basis unit test Voer een functie uit. Controleer output met een assertxxx(). Assert parameters: Expected, Actual, Message. Assert parameters: Actual, Message
Massa's assertwhatever().. $this->assertarrayhaskey(); $this->assertclasshasattribute(); $this->assertclasshasstaticattribute(); $this->assertcontains(); $this->assertcontainsonly(); $this->assertequalxmlstructure(); $this->assertequals(); $this->assertfalse(); $this->assertfileequals(); $this->assertfileexists(); $this->assertgreaterthan(); $this->assertgreaterthanorequal(); $this->assertlessthan(); $this->assertlessthanorequal(); $this->assertnotnull(); $this->assertobjecthasattribute(); $this->assertregexp();
Massa's assertwhatever().. $this->assertsame(); $this->assertselectcount(); $this->assertselectequals(); $this->assertselectregexp(); $this->assertstringequalsfile(); $this->asserttag(); $this->assertthat(); $this->asserttrue(); $this->asserttype(); $this->assertxmlfileequalsxmlfile(); $this->assertxmlstringequalsxmlfile();
pbouw test klasse <?php require_once 'source/class.transaction.php'; class TransactionTest extends PHPUnit_Framework_TestCase private $Transaction; //Prepares the environment before running a test. protected function setup() parent::setup (); $this->transaction = new Transaction(); // Cleans up the environment after running a test. protected function teardown() $this->transaction = null; parent::teardown (); // Test cases..
Test Doubles DOC (Depended Upon Component) Emuleren gedrag van een DOC. Stub emuleert DOC output. Mock controleert gebruik DOC.
Test Doubles <?php class Transaction protected $paymentservice; public function pay($orderid) //Do payment public function getunpayedorders() //Get a list of unpayed orders?>
Test Doubles (2) <?php class Transaction /** * Handles transactions from our webshop. * * @var paymentservice */ protected $paymentservice; //... public function pay($orderid) $this->paymentservice->startpayment(); $this->paymentservice->pay($orderid); return $this->paymentservice->checkstatus($orderid); //...?>
Test Doubles (3) <?php class paymentservice /** * Payment service. */ public function construct() /*.. */ public function startpayment() /*.. */ public function pay() /*.. */?> public function checkstatus() /*.. */ //...
Test Doubles (4) <?php require_once 'source/class.transaction.php'; require_once 'PHPUnit/Framework/TestCase.php'; class TransactionTest extends PHPUnit_Framework_TestCase private $Transaction; private $mockpaymentservice; //Prepares the environment before running a test. protected function setup() parent::setup (); $this->mockpaymentservice = $this->getmock('paymentservice', array('startpayment', 'pay', 'checkstatus', 'getunpayedorders')); $this->transaction = new Transaction($this->mockPaymentService); //...
Stub <?php require_once 'source/class.transaction.php'; require_once 'PHPUnit/Framework/TestCase.php'; class TransactionTest extends PHPUnit_Framework_TestCase //... public function testpay() $this->mockpaymentservice->expects($this->any()) - >method('checkstatus') ->will($this->returnvalue('success')); $result = $this->transaction->pay(1); $this->assertequals('success', $result, 'Payment for order 1 should always succeed'); //...
Mock <?php class TransactionTest extends PHPUnit_Framework_TestCase //... //Test Volgorde van aanroepen service public function testpaysequence() $orderid = '123'; $this->mockpaymentservice->expects($this->once()) - >method('startpayment'); $this->mockpaymentservice->expects($this->once()) - >method('pay') ->with($this->equalto($orderid)); $this->mockpaymentservice->expects($this->once()) - >method('checkstatus') ->will($this->returnvalue('success')); $result = $this->transaction->pay($orderid); $this->assertequals('success', $result, 'Payment for order 123 should always succeed'); //...
Database Double Gevoelig voor veranderingen Langzaam Indien mogelijk vermijden
Database test opzetten <?php //..Includes class TransactionLogTest extends PHPUnit_Extensions_Database_TestCase /** * REQUIRED * Get the connection for the database setup/teardown. * * @return Database connection. */ protected function getconnection() return $this->createdefaultdbconnection ( $this->pdo, 'shoptest' ); /** * REQUIRED Get the default dataset for * * @return XML dataset */ protected function getdataset() return $this->createflatxmldataset( dirname( FILE ). '/_files/orderlog-seed.xml' ); //...
DB test opzetten (2) Eenvoudige mini test-database Orderlog tabel met orderid, status en een timestamp <?xml version="1.0" encoding="utf-8"?> <dataset> <orderlog orderid="1" status="success" ordertime="2008-10-05 12:35"/> </dataset>
DB test opzetten (3) <?php class transactionlogtest extends PHPUnit_Extensions_Database_TestCase /*... */ /** * OPTIONAL: Truncate everything before inserting. * This is actually the default. */ protected function getsetupoperation() return $this->getoperations()->clean_insert(); /** * OPTIONAL:Truncate all tables after running a test. */ protected function getteardownoperation() return $this->getoperations()->truncate(); /*... */
Databases testen <?php class Transaction //... // PDO database connection protected $pdo; //... public function logorder($orderid, $status) = $this->pdo->prepare( "INSERT INTO orderlog (orderid, status) VALUES (:orderid, :status)" ); if ($stmt->execute( array('orderid'=>$orderid, 'status' => $status ) ) === false) return false; return true; stmt$
DB test opzetten (4a) Na het toevoegen verwacht ik: <?xml version="1.0" encoding="utf-8"?> <dataset> <orderlog orderid="1" status="success" ordertime="2008-10-05 12:35"/> <orderlog orderid="4" status="success" ordertime="2008-10-23 12:36"/> </dataset>
Databases testen (2) <?php //... class TransactionLogTest extends PHPUnit_Extensions_Database_TestCase public function testlogorder() $expecteddataset = $this->createflatxmldataset( dirname( FILE ). '/_files/orderlog-after-add.xml' ); $this->transaction->logorder(4,'success'); $actualdataset = $this->getconnection ()->createdataset (); $this->assertdatasetsequal ($expecteddataset, $actualdataset, 'Add did not show up' );
DB test opzetten (4b) Timestamps zijn niet te voorspellen, die zou ik liever niet terug willen zien in de 'verwachte' data: <?xml version="1.0" encoding="utf-8"?> <dataset> <orderlog orderid="1" status="success"/> <orderlog orderid="4" status="success"/> </dataset>
Databases testen (3) <?php //... /** * Transaction test case. */ class TransactionLogTest extends PHPUnit_Extensions_Database_TestCase //... public function testlogorderfiltered() $expecteddataset = $this->createflatxmldataset( dirname( FILE ). '/_files/orderlog-after-add-filtered.xml' ); $this->transaction->logorder(4,'success'); $actualdataset = new PHPUnit_Extensions_Database_DataSet_DataSetFilter( $this->getconnection()->createdataset(array('orderlog')), array('orderlog'=>'ordertime') -this$ ;( >assertdatasetsequal ( $expecteddataset, $actualdataset, 'Add did not show up' );
PHPUnit en Selenium (RC) Selenium is een Firefox plugin voor het testen van web front-ends. Selenium IDE is een recorder waarmee je acties kunt vastleggen op een website, gebaseerd op het DOM van de pagina's. Selenium RC is een java server die met browsers (FF, IE, Safari, Chrome?) can communiceren. Wanneer je Java hebt, gewoon unzippen in een handige directory. http://selenium.openqa.org/ http://selenium-rc.openqa.org/
PHPUnit en Selenium RC <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"> <title>test Title</title> </head> <body> <p>selenium Test Testpage.</p> <div id='requireddiv'>important div</div> </body> </html>
PHPUnit en Selenium RC <?php require_once 'PHPUnit/Extensions/SeleniumTestCase.php'; class seleniumtestcase extends PHPUnit_Extensions_SeleniumTestCase function setup() $this->sethost("localhost"); $this->setbrowser("*iexplore"); $this->setbrowserurl("http://uidp/"); function testmytestcase() $this->open("/html"); $this->asserttitleequals('test Title'); $this->asserttextpresent('selenium Test Testpage.'); $this->assertelementpresent("//div[@id='requireddiv']");?>