PHPUNIT
#burningkeyboards
@denis_ristic
PHPUNIT
INTRO
‣ PHPUnit is a programmer-oriented testing framework for
PHP.
‣ A unit is simply a chunk of functionality that performs a
specific action where you can test the outcome.
‣ A unit test, then, is a sanity check to make sure that the
chunk of functionality does what it’s supposed to.
‣ “The secret in testing is writing testable code” - Miško
Hevery
2
PHPUNIT
INSTALLING PHPUNIT (WINDOWS)
‣ Create a directory for PHP binaries; e.g., C:bin
‣ Append ;C:bin to your PATH environment variable (related help)
‣ Download https://phar.phpunit.de/phpunit-6.1.phar and save the file as C:
binphpunit.phar
‣ Open a command line (e.g., press Windows+R » type cmd » ENTER)
‣ Create a wrapping batch script (results in C:binphpunit.cmd):
‣ C:Usersusername> cd C:bin
‣ C:bin> echo @php "%~dp0phpunit.phar" %* > phpunit.cmd
‣ C:bin> exit
‣ Open a new command line and confirm that you can execute PHPUnit from any path:
‣ C:Usersusername> phpunit --version
‣ PHPUnit x.y.z by Sebastian Bergmann and contributors.
3
PHPUNIT
EXAMPLE TEST CASE
<?php
class User
{
protected $name;
public function getName() {
return $this->name;
}
public function setName($name) {
$this->name = $name;
}
public function talk() {
return "Hello world!";
}
}
4
class UserTest extends PHPUnit_Framework_TestCase
{
// test the talk method
public function testCanTalk() {
// make an instance of the user
$user = new User();
// use assertEquals to ensure the greeting is what you
$expected = "Hello world!";
$actual = $user->talk();
$this->assertEquals($expected, $actual);
}
}
PHPUNIT
SETUP AND TEARDOWN
<?php
// The setUp() and tearDown() methods are called by PHPUnit before and after each test, so you can
skip instantiating the user in the test method
class UserTest extends PHPUnit_Framework_TestCase
{
protected $user;
public function testCanTalk() {
$expected = "Hello world!";
$actual = $this->user->talk();
$this->assertEquals($expected, $actual);
}
protected function setUp() {
$this->user = new User();
$this->user->setName("Tom");
}
protected function tearDown() {
unset($this->user);
}
}
5
PHPUNIT
RUNNING TESTS
phpunit --bootstrap src/User.php tests/UserTest
PHPUnit 6.2.0 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 70 ms, Memory: 10.00MB
OK (3 tests, 3 assertions)
TestDox example:
phpunit --bootstrap src/Email.php --testdox tests
PHPUnit 6.2.0 by Sebastian Bergmann and contributors.
User
[x] Can talk
[x] Can set name
[x] Can get name
6
PHPUNIT
RUNNING TESTS - FAILURE
phpunit UnitTest UserTest.php
PHPUnit 3.6.3 by Sebastian Bergmann.
F
Time: 0 seconds, Memory: 5.75Mb
There was 1 failure:
1) UserTest::testTalk
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-Hello World!
+blubb
/PHPUnit introduction/tests/UserTest.php:23
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
7
PHPUNIT
MOST USED ASSERTIONS
‣ AssertTrue/AssertFalse - Check the input to verify it equals true/false
‣ AssertEquals - Check the result against another input for a match
‣ AssertGreaterThan - Check the result to see if it’s larger than a value
(there’s also LessThan, GreaterThanOrEqual, and LessThanOrEqual)
‣ AssertContains - Check that the input contains a certain value
‣ AssertType - Check that a variable is of a certain type
‣ AssertNull - Check that a variable is null
‣ AssertFileExists - Verify that a file exists
‣ AssertRegExp - Check the input against a regular expression
8
PHPUNIT
EXAMPLE
<?php
namespace phpUnitTutorialTest;
use phpUnitTutorialPayment;
class PaymentTest extends PHPUnit_Framework_TestCase
{
public function testProcessPaymentReturnsTrueOnSuccessfulPayment()
{
$paymentDetails = array(
'amount' => 123.99,
'card_num' => '4111-1111-1111-1111',
'exp_date' => '03/2013',
);
$payment = new Payment();
$result = $payment->processPayment($paymentDetails);
$this->assertTrue($result);
}
}
9
PHPUNIT
MOCKING
‣ PHPUnit comes with a very powerful feature to help us handle outside
dependencies.
‣ It basically involves replacing the actual object with a fake, or 'mock',
object that we fully control, removing all dependencies on outside
systems or code that we really have no need to test.
‣ In the AuthorizeNetAIM class we know that the
method ::authorizeAndCapture() brings some serious problems to our
testing code - in that it pings an outside server that we neither have
control over nor desire to control.
10
PHPUNIT
MOCKING EXAMPLE
<?php
public function processPayment(array $paymentDetails)
{
$transaction = new AuthorizeNetAIM(self::API_ID,
self::TRANS_KEY);
$transaction->amount = $paymentDetails['amount'];
$transaction->card_num = $paymentDetails['card_num'];
$transaction->exp_date = $paymentDetails['exp_date'];
$response = $transaction->authorizeAndCapture();
if ($response->approved) {
return $this->savePayment($response-
>transaction_id);
}
throw new Exception($response->error_message);
}
11
public function processPayment(AuthorizeNetAIM $transaction, array
$paymentDetails)
{
$transaction->amount = $paymentDetails['amount'];
$transaction->card_num = $paymentDetails['card_num'];
$transaction->exp_date = $paymentDetails['exp_date'];
$response = $transaction->authorizeAndCapture();
if ($response->approved) {
return $this->savePayment($response->transaction_id);
}
throw new Exception($response->error_message);
}
PHPUNIT
MOCKING EXAMPLE
<?php
namespace phpUnitTutorialTest;
use phpUnitTutorialPayment;
class PaymentTest extends PHPUnit_Framework_TestCase
{
public function
testProcessPaymentReturnsTrueOnSuccessfulPayment()
{
$paymentDetails = array(
'amount' => 123.99,
'card_num' => '4111-1111-1111-1111',
'exp_date' => '03/2013',
);
$payment = new Payment();
$authorizeNet = new
AuthorizeNetAIM($payment::API_ID, $payment::TRANS_KEY);
$result = $payment->processPayment($authorizeNet,
$paymentDetails);
$this->assertTrue($result);
}
}
12
<?php
namespace phpUnitTutorialTest;
use phpUnitTutorialPayment;
class PaymentTest extends PHPUnit_Framework_TestCase
{
public function testProcessPaymentReturnsTrueOnSuccessfulPayment()
{
$paymentDetails = array(
'amount' => 123.99,
'card_num' => '4111-1111-1111-1111',
'exp_date' => '03/2013',
);
$payment = new Payment();
$authorizeNet = $this->getMockBuilder('AuthorizeNetAIM')
->setConstructorArgs(array($payment::API_ID,
$payment::TRANS_KEY))
->getMock();
$result = $payment->processPayment($authorizeNet,
$paymentDetails);
$this->assertTrue($result);
}
}
PHPUNIT
MOCKING
‣ One of the most powerful tools available to you is the getMock() method
- it allows you to create a new class that passes our two major requirements
above, all on the fly. You do not need to create separate files for each class,
you do not have to worry about maintaining a steadily-growing file
structure.
‣ public function getMock($originalClassName, $methods =
array(), array $arguments = array(), $mockClassName =
'', $callOriginalConstructor = TRUE, $callOriginalClone
= TRUE, $callAutoload = TRUE, $cloneArguments = TRUE)
‣ A few versions ago PHPUnit introduced a handy helper:
getMockBuilder(). It is little more than a wrapper around the
getMock() method above, but it provides a much more human-readable
format of chained methods, making creating mocked objects a breeze.
13
PHPUNIT
MOCKING EXAMPLE
<?php
public function authorizeAndCapture($amount = false,
$card_num = false, $exp_date = false)
{
($amount ? $this->amount = $amount : null);
($card_num ? $this->card_num = $card_num : null);
($exp_date ? $this->exp_date = $exp_date : null);
$this->type = "AUTH_CAPTURE";
return $this->_sendRequest();
}
14
<?php
// A stub method is a method that mimics the origin method in two ways
- same name and same parameters accepted. What makes a stub method
special, however, is that all the code within it has been erased.
public function authorizeAndCapture($amount = false, $card_num =
false, $exp_date = false)
{
return null;
}
PHPUNIT
MOCKING EXAMPLE
<?php
namespace phpUnitTutorialTest;
use phpUnitTutorialPayment;
class PaymentTest extends PHPUnit_Framework_TestCase
{
public function testProcessPaymentReturnsTrueOnSuccessfulPayment()
{
$paymentDetails = array(
'amount' => 123.99,
'card_num' => '4111-1111-1111-1111',
'exp_date' => '03/2013',
);
$payment = new Payment();
$response = new stdClass();
$response->approved = true;
$response->transaction_id = 123;
$authorizeNet = $this->getMockBuilder('AuthorizeNetAIM')
->setConstructorArgs(array($payment::API_ID, $payment::TRANS_KEY))
->getMock();
$authorizeNet->expects($this->once())
->method('authorizeAndCapture')
->will($this->returnValue($response));
$result = $payment->processPayment($authorizeNet, $paymentDetails);
$this->assertTrue($result);
}
}
15
PHPUNIT
BEST PRACTICES
‣ One assertion per test
‣ Write real unit tests - A real unit test verifies the bahavior of a single
code unit isolated from its dependencies
‣ Decouple test code & data
‣ Use a configuration file
‣ Be descriptive about what you are testing
‣ Be explicit about what you are testing
‣ Use the most specific assertion possible
16
PHPUNIT
PHPUNIT AND SELENIUM
‣ Selenium Server is a test tool that allows you to write automated user-
interface tests for web applications in any programming language
against any HTTP website using any mainstream browser.
‣ It performs automated browser tasks by driving the browser's process
through the operating system.
‣ Selenium tests run directly in a browser, just as real users do. These tests
can be used for both acceptance testing (by performing higher-level
tests on the integrated system instead of just testing each unit of the
system independently) and browser compatibility testing (by testing
the web application on different operating systems and browsers).
17
PHPUNIT
EXAMPLE
<?php
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';
class WebTest extends PHPUnit_Extensions_Selenium2TestCase
{
protected $captureScreenshotOnFailure = TRUE;
protected $screenshotPath = '/var/www/localhost/htdocs/screenshots';
protected $screenshotUrl = 'http://localhost/screenshots';
protected function setUp()
{
$this->setBrowser('*firefox');
$this->setBrowserUrl('http://www.example.com/');
}
public function testTitle()
{
$this->open('http://www.example.com/');
$this->assertTitle('Example WWW Page');
}
public function testElement()
{
$this->open('http://www.example.com/');
$elements = $this->elements($this->using('.class')->value('div'));
$this->assertEquals(4, count($elements));
}
}
18
PHPUNIT
PHPUNIT RESOURCES
‣ PHPUnit documentation
‣ https://phpunit.de/getting-started.html
‣ PHPUnit book
‣ https://phpunit.de/manual/current/en/phpunit-book.pdf
‣ PHPUnit & Selenium
‣ https://github.com/giorgiosironi/phpunit-selenium
‣ PHPUnit tutorials
‣ https://jtreminio.com/2013/03/unit-testing-tutorial-introduction-to-phpunit/
‣ https://www.sitepoint.com/getting-started-with-phpunit/
‣ https://www.sitepoint.com/tutorial-introduction-to-unit-testing-in-php-with-phpunit/
19

13 PHPUnit #burningkeyboards

  • 1.
  • 2.
    PHPUNIT INTRO ‣ PHPUnit isa programmer-oriented testing framework for PHP. ‣ A unit is simply a chunk of functionality that performs a specific action where you can test the outcome. ‣ A unit test, then, is a sanity check to make sure that the chunk of functionality does what it’s supposed to. ‣ “The secret in testing is writing testable code” - Miško Hevery 2
  • 3.
    PHPUNIT INSTALLING PHPUNIT (WINDOWS) ‣Create a directory for PHP binaries; e.g., C:bin ‣ Append ;C:bin to your PATH environment variable (related help) ‣ Download https://phar.phpunit.de/phpunit-6.1.phar and save the file as C: binphpunit.phar ‣ Open a command line (e.g., press Windows+R » type cmd » ENTER) ‣ Create a wrapping batch script (results in C:binphpunit.cmd): ‣ C:Usersusername> cd C:bin ‣ C:bin> echo @php "%~dp0phpunit.phar" %* > phpunit.cmd ‣ C:bin> exit ‣ Open a new command line and confirm that you can execute PHPUnit from any path: ‣ C:Usersusername> phpunit --version ‣ PHPUnit x.y.z by Sebastian Bergmann and contributors. 3
  • 4.
    PHPUNIT EXAMPLE TEST CASE <?php classUser { protected $name; public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } public function talk() { return "Hello world!"; } } 4 class UserTest extends PHPUnit_Framework_TestCase { // test the talk method public function testCanTalk() { // make an instance of the user $user = new User(); // use assertEquals to ensure the greeting is what you $expected = "Hello world!"; $actual = $user->talk(); $this->assertEquals($expected, $actual); } }
  • 5.
    PHPUNIT SETUP AND TEARDOWN <?php //The setUp() and tearDown() methods are called by PHPUnit before and after each test, so you can skip instantiating the user in the test method class UserTest extends PHPUnit_Framework_TestCase { protected $user; public function testCanTalk() { $expected = "Hello world!"; $actual = $this->user->talk(); $this->assertEquals($expected, $actual); } protected function setUp() { $this->user = new User(); $this->user->setName("Tom"); } protected function tearDown() { unset($this->user); } } 5
  • 6.
    PHPUNIT RUNNING TESTS phpunit --bootstrapsrc/User.php tests/UserTest PHPUnit 6.2.0 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 70 ms, Memory: 10.00MB OK (3 tests, 3 assertions) TestDox example: phpunit --bootstrap src/Email.php --testdox tests PHPUnit 6.2.0 by Sebastian Bergmann and contributors. User [x] Can talk [x] Can set name [x] Can get name 6
  • 7.
    PHPUNIT RUNNING TESTS -FAILURE phpunit UnitTest UserTest.php PHPUnit 3.6.3 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.75Mb There was 1 failure: 1) UserTest::testTalk Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -Hello World! +blubb /PHPUnit introduction/tests/UserTest.php:23 FAILURES! Tests: 1, Assertions: 1, Failures: 1. 7
  • 8.
    PHPUNIT MOST USED ASSERTIONS ‣AssertTrue/AssertFalse - Check the input to verify it equals true/false ‣ AssertEquals - Check the result against another input for a match ‣ AssertGreaterThan - Check the result to see if it’s larger than a value (there’s also LessThan, GreaterThanOrEqual, and LessThanOrEqual) ‣ AssertContains - Check that the input contains a certain value ‣ AssertType - Check that a variable is of a certain type ‣ AssertNull - Check that a variable is null ‣ AssertFileExists - Verify that a file exists ‣ AssertRegExp - Check the input against a regular expression 8
  • 9.
    PHPUNIT EXAMPLE <?php namespace phpUnitTutorialTest; use phpUnitTutorialPayment; classPaymentTest extends PHPUnit_Framework_TestCase { public function testProcessPaymentReturnsTrueOnSuccessfulPayment() { $paymentDetails = array( 'amount' => 123.99, 'card_num' => '4111-1111-1111-1111', 'exp_date' => '03/2013', ); $payment = new Payment(); $result = $payment->processPayment($paymentDetails); $this->assertTrue($result); } } 9
  • 10.
    PHPUNIT MOCKING ‣ PHPUnit comeswith a very powerful feature to help us handle outside dependencies. ‣ It basically involves replacing the actual object with a fake, or 'mock', object that we fully control, removing all dependencies on outside systems or code that we really have no need to test. ‣ In the AuthorizeNetAIM class we know that the method ::authorizeAndCapture() brings some serious problems to our testing code - in that it pings an outside server that we neither have control over nor desire to control. 10
  • 11.
    PHPUNIT MOCKING EXAMPLE <?php public functionprocessPayment(array $paymentDetails) { $transaction = new AuthorizeNetAIM(self::API_ID, self::TRANS_KEY); $transaction->amount = $paymentDetails['amount']; $transaction->card_num = $paymentDetails['card_num']; $transaction->exp_date = $paymentDetails['exp_date']; $response = $transaction->authorizeAndCapture(); if ($response->approved) { return $this->savePayment($response- >transaction_id); } throw new Exception($response->error_message); } 11 public function processPayment(AuthorizeNetAIM $transaction, array $paymentDetails) { $transaction->amount = $paymentDetails['amount']; $transaction->card_num = $paymentDetails['card_num']; $transaction->exp_date = $paymentDetails['exp_date']; $response = $transaction->authorizeAndCapture(); if ($response->approved) { return $this->savePayment($response->transaction_id); } throw new Exception($response->error_message); }
  • 12.
    PHPUNIT MOCKING EXAMPLE <?php namespace phpUnitTutorialTest; usephpUnitTutorialPayment; class PaymentTest extends PHPUnit_Framework_TestCase { public function testProcessPaymentReturnsTrueOnSuccessfulPayment() { $paymentDetails = array( 'amount' => 123.99, 'card_num' => '4111-1111-1111-1111', 'exp_date' => '03/2013', ); $payment = new Payment(); $authorizeNet = new AuthorizeNetAIM($payment::API_ID, $payment::TRANS_KEY); $result = $payment->processPayment($authorizeNet, $paymentDetails); $this->assertTrue($result); } } 12 <?php namespace phpUnitTutorialTest; use phpUnitTutorialPayment; class PaymentTest extends PHPUnit_Framework_TestCase { public function testProcessPaymentReturnsTrueOnSuccessfulPayment() { $paymentDetails = array( 'amount' => 123.99, 'card_num' => '4111-1111-1111-1111', 'exp_date' => '03/2013', ); $payment = new Payment(); $authorizeNet = $this->getMockBuilder('AuthorizeNetAIM') ->setConstructorArgs(array($payment::API_ID, $payment::TRANS_KEY)) ->getMock(); $result = $payment->processPayment($authorizeNet, $paymentDetails); $this->assertTrue($result); } }
  • 13.
    PHPUNIT MOCKING ‣ One ofthe most powerful tools available to you is the getMock() method - it allows you to create a new class that passes our two major requirements above, all on the fly. You do not need to create separate files for each class, you do not have to worry about maintaining a steadily-growing file structure. ‣ public function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE, $cloneArguments = TRUE) ‣ A few versions ago PHPUnit introduced a handy helper: getMockBuilder(). It is little more than a wrapper around the getMock() method above, but it provides a much more human-readable format of chained methods, making creating mocked objects a breeze. 13
  • 14.
    PHPUNIT MOCKING EXAMPLE <?php public functionauthorizeAndCapture($amount = false, $card_num = false, $exp_date = false) { ($amount ? $this->amount = $amount : null); ($card_num ? $this->card_num = $card_num : null); ($exp_date ? $this->exp_date = $exp_date : null); $this->type = "AUTH_CAPTURE"; return $this->_sendRequest(); } 14 <?php // A stub method is a method that mimics the origin method in two ways - same name and same parameters accepted. What makes a stub method special, however, is that all the code within it has been erased. public function authorizeAndCapture($amount = false, $card_num = false, $exp_date = false) { return null; }
  • 15.
    PHPUNIT MOCKING EXAMPLE <?php namespace phpUnitTutorialTest; usephpUnitTutorialPayment; class PaymentTest extends PHPUnit_Framework_TestCase { public function testProcessPaymentReturnsTrueOnSuccessfulPayment() { $paymentDetails = array( 'amount' => 123.99, 'card_num' => '4111-1111-1111-1111', 'exp_date' => '03/2013', ); $payment = new Payment(); $response = new stdClass(); $response->approved = true; $response->transaction_id = 123; $authorizeNet = $this->getMockBuilder('AuthorizeNetAIM') ->setConstructorArgs(array($payment::API_ID, $payment::TRANS_KEY)) ->getMock(); $authorizeNet->expects($this->once()) ->method('authorizeAndCapture') ->will($this->returnValue($response)); $result = $payment->processPayment($authorizeNet, $paymentDetails); $this->assertTrue($result); } } 15
  • 16.
    PHPUNIT BEST PRACTICES ‣ Oneassertion per test ‣ Write real unit tests - A real unit test verifies the bahavior of a single code unit isolated from its dependencies ‣ Decouple test code & data ‣ Use a configuration file ‣ Be descriptive about what you are testing ‣ Be explicit about what you are testing ‣ Use the most specific assertion possible 16
  • 17.
    PHPUNIT PHPUNIT AND SELENIUM ‣Selenium Server is a test tool that allows you to write automated user- interface tests for web applications in any programming language against any HTTP website using any mainstream browser. ‣ It performs automated browser tasks by driving the browser's process through the operating system. ‣ Selenium tests run directly in a browser, just as real users do. These tests can be used for both acceptance testing (by performing higher-level tests on the integrated system instead of just testing each unit of the system independently) and browser compatibility testing (by testing the web application on different operating systems and browsers). 17
  • 18.
    PHPUNIT EXAMPLE <?php require_once 'PHPUnit/Extensions/SeleniumTestCase.php'; class WebTestextends PHPUnit_Extensions_Selenium2TestCase { protected $captureScreenshotOnFailure = TRUE; protected $screenshotPath = '/var/www/localhost/htdocs/screenshots'; protected $screenshotUrl = 'http://localhost/screenshots'; protected function setUp() { $this->setBrowser('*firefox'); $this->setBrowserUrl('http://www.example.com/'); } public function testTitle() { $this->open('http://www.example.com/'); $this->assertTitle('Example WWW Page'); } public function testElement() { $this->open('http://www.example.com/'); $elements = $this->elements($this->using('.class')->value('div')); $this->assertEquals(4, count($elements)); } } 18
  • 19.
    PHPUNIT PHPUNIT RESOURCES ‣ PHPUnitdocumentation ‣ https://phpunit.de/getting-started.html ‣ PHPUnit book ‣ https://phpunit.de/manual/current/en/phpunit-book.pdf ‣ PHPUnit & Selenium ‣ https://github.com/giorgiosironi/phpunit-selenium ‣ PHPUnit tutorials ‣ https://jtreminio.com/2013/03/unit-testing-tutorial-introduction-to-phpunit/ ‣ https://www.sitepoint.com/getting-started-with-phpunit/ ‣ https://www.sitepoint.com/tutorial-introduction-to-unit-testing-in-php-with-phpunit/ 19