SlideShare a Scribd company logo
Building Testable PHP
     Applications



         Chris Hartjes
       January 13, 2012
$progStartYear = 1982;
$firstComputer = ‘VIC-20’;
  $phpStartYear = 1998;
High traffic online dating




Sports data integration



Social commerce platform
Sorry, but a lot of this
might not make sense
if you are a beginning
     programmer
Testing? GOOD



Testable Applications? BETTER
Some applications WILL
resist all attempts to test
You don’t always get
  what you want...
What is in
my testing toolkit?
Effective testing is always
automated because people
       make mistakes
“Simple systems can display
complex behaviour but
complex systems can only
display simple behaviour”.
The gold-standard for
 unit testing in PHP
public function testSaveUpdatesDatabase()
{
    $mapper = new IBLFranchiseMapper($this->_conn);
    $franchise = new IBLFranchise();
    $franchise->setId(25);
    $franchise->setNickname('TST');
    $franchise->setName('Test Team');
    $franchise->setConference('Conference');
    $franchise->setDivision('Division');
    $franchise->setIp(0);
    $mapper->save($franchise);

    // Update existing model
    $franchise->setIp(35);
    $mapper->save($franchise);

    // Reload Franchise record and compare them
    $franchise2 = $mapper->findById($franchise->getId());
    $this->assertEquals(35, $franchise2->getIp());

    // Clean up the franchise
    $mapper->delete($franchise);
}
My preferred
   Behaviour-Driven
Development framework
Feature: ls
  In order to see the directory structure
  As a UNIX user
  I need to be able to list the current
directory's contents

  Scenario: List 2 files in a directory
    Given I am in a directory "test"
    And I have a file named "foo"
    And I have a file named "bar"
    When I run "ls"
    Then I should get:
      """
      bar
      foo
      """
Excuses are like
  nipples, everyone has
       at least one
       “Writing tests will slow me down.”

“Why can’t we just manually test in the browser?”

 “I’d be done with this already if I didn’t have to
                write the tests!”
“A project using TDD would take 20 to 40%
    longer to complete but resulted in 40 to 90%
        fewer bugs discovered in production.”




http://www.infoq.com/news/2009/03/TDD-Improves-Quality
Testing Code




               Architecture
Which one of these things
       is not like the other?




Development     Staging     Production
Vagrant




Virtualbox + Ruby + (Chef | Puppet | Shell)
The only difference between environments
     should be the data that they use
Static Code Analysis

     PHP Code Sniffer

    PHP Mess Detector

 PHP Copy Paster Detector
PHP Code Sniffer
  Enforcement of code to expected standard


  Create your own “code sniffs” if required



Great if team having problems with consistency
chartjes@yggdrasil [09:39:37] [~/Sites/local.ibl/lib/ibl] [master *]
-> % phpcs FranchiseMapper.php
FILE: /Users/chartjes/Sites/local.ibl/lib/ibl/FranchiseMapper.php
FOUND 13 ERROR(S) AND 5 WARNING(S) AFFECTING 18 LINE(S)
2 | ERROR | Missing file doc comment
5 | ERROR | Missing class doc comment
6 | ERROR | Expected 0 spaces before opening brace; 1 found
10 | ERROR | You must use "/**" style comments for a function comment
| Missing function doc comment
15 | WARNING | Line exceeds 85 characters; contains 86 characters
20 | ERROR | Missing function doc comment
36 | ERROR | Missing function doc comment
53 | ERROR | Missing function doc comment
74 | ERROR | Missing function doc comment
95 | ERROR | Missing function doc comment
139 | WARNING | Line exceeds 85 characters; contains 90 characters
142 | WARNING | Line exceeds 85 characters; contains 181 characters
149 | ERROR   | Missing function doc comment
152 | WARNING | Line exceeds 85 characters; contains 117 characters
154 | WARNING | Line exceeds 85 characters; contains 86 characters
PHP Mess Detector

 Identifies code that is complex



  Make suggestions on changes
> % phpmd lib/vendor/Moontoast/RpcApi.php text
    codesize,design,naming,unusedcode
RpcApi.php:12    The class RpcApi has an overall complexity of 77 which
    is very high. The configured complexity threshold is 50
high. The configured complexity threshold is 50.
RpcApi.php:12    The class RpcApi has a coupling between objects value
    of 14. Consider to reduce to number of dependencies under 13
RpcApi.php:27 Avoid unused parameters such as '$params'.
RpcApi.php:114 The method getProduct() has a Cyclomatic Complexity of 14.
RpcApi.php:114 The method getProduct() has an NPath complexity of 655.
RpcApi.php:114 Avoid really long methods.
RpcApi.php:114 Avoid variables with short names like $id
RpcApi.php:234 Avoid unused parameters such as '$params'.
RpcApi.php:282 The method getStore() has a Cyclomatic Complexity of 12.
RpcApi.php:282 Avoid really long methods.
RpcApi.php:302 Avoid unused local variables such as '$price'.
RpcApi.php:303 Avoid unused local variables such as '$previousPrice'.
RpcApi.php:398 Avoid unused parameters such as '$csc'
RpcApi.php:477 The method saveCart() has a Cyclomatic Complexity of 26
RpcApi.php:477 Avoid really long methods
RpcApi.php:477 Avoid variables with short names like $id
RpcApi.php:477 Avoid excessively long variable names like
    $useBillingForShipping
RpcApi.php:588 The method saveCart() has an NPath complexity of 5644802.
RpcApi.php:608   Avoid excessively long variable names like
    $shippingAddressObject
RpcApi.php:671 Avoid unused local variables such as '$gateway'.
RpcApi.php:702 Avoid variables with short names like $q
RpcApi.php:707 Avoid variables with short names like $mm
PHP Copy Paster Detector


  Just how lazy are you and your team?
-> % phpcpd lib/services/AMF_Checkout.php
phpcpd 1.3.3 by Sebastian Bergmann.

Found 2 exact clones with 52 duplicated lines
in 1 files:
  - AMF_Checkout.php:60-68
    AMF_Checkout.php:576-584

  - AMF_Checkout.php:88-132
    AMF_Checkout.php:602-646
7.21% duplicated lines out of 721 total lines
of code.
Time: 0 seconds, Memory: 6.50Mb
The Law of Demeter
 The Law of Demeter for functions
 states that any method of an object
 should call only methods belonging to
 itself, any parameters that were passed
 in to the method, any objects it
 created, and any directly held
 component objects.


        (from the best book EVER for programmers
               “The Pragmatic Programmer”)
Tightly-coupled code will
   go out of it’s way to
 make it difficult to test
Dependency Injection


Pass objects and functions information
about the other objects and functions
     that are required for the task
<?php

include 'test_bootstrap.php';

class FranchiseModelTest extends PHPUnit_Framework_TestCase
{
    protected $_conn;

    public function setUp()
    {
        $this->_conn = new PDO('pgsql:host=localhost;dbname=ibl_stats', 'stats', 'st@ts=Fun');
    }

    public function tearDown()
    {
        unset($this->_conn);
    }

    public function testSaveUpdatesDatabase()
    {
        $mapper = new IBLFranchiseMapper($this->_conn);
        $franchise = new IBLFranchise();
        $franchise->setId(25);
        $franchise->setNickname('TST');
        $franchise->setName('Test Team');
        $franchise->setConference('Conference');
        $franchise->setDivision('Division');
        $franchise->setIp(0);
        $mapper->save($franchise);

        // Update existing model
        $franchise->setIp(35);
        $mapper->save($franchise);

        // Reload Franchise record and compare them
        $franchise2 = $mapper->findById($franchise->getId());
        $this->assertEquals(35, $franchise2->getIp());

        // Clean up the franchise
        $mapper->delete($franchise);
    }
}
public function save(IBLFranchise $franchise)
    {
        if ($this->findById($franchise->getId())) {
            $this->_update($franchise);
        } else {
            $this->_insert($franchise);
        }
    }

    protected function _insert(IBLFranchise $franchise)
    {
        try {
            $sql = "
                INSERT INTO franchises
                (nickname, name, conference, division, ip, id)
                VALUES(?, ?, ?, ?, ?, ?)";
            $sth = $this->_conn->prepare($sql);
            $binds = array(
                $franchise->getNickname(),
                $franchise->getName(),
                $franchise->getConference(),
                $franchise->getDivision(),
                $franchise->getIp(),
                $franchise->getId()
            );
            $sth->execute($binds);
        } catch(PDOException $e) {
            echo "A database problem occurred: " . $e-
>getMessage();
        }
    }
<?php

namespace Grumpy;

class Acl {
    protected $_acls;

    ...

    public function accessAlllowed()
    {
        $request = GrumpyContext::getRequest();

          return ($this->_acls[$request->getUri()]
              >= $_SESSION['user_level']);
    }

}

// Meanwhile inside your controller

$acl = new GrumpyAcl();

if (!$acl->accessAllowed()) {
    GrumpyView::render('access_denied.tmpl');
} else {
    GrumpyView::render('show_stolen_cc.tmpl');
}
<?php

namespace Grumpy;

class Acl {
    protected $_acls;
    protected $_request;
    protected $_userLevel;

    ...

    public function __construct($request, $userLevel)
    {
        ...

          $this->_request = $request;
          $this->_request = $userLevel;
    }

    public function accessAlllowed()
    {
        return ($this->_acls[$this->_request->getUri()]
            >= $this->_userLevel);
    }

}

// Meanwhile inside your controller

$acl = new GrumpyAcl();

if (!$acl->accessAllowed()) {
    GrumpyView::render('access_denied.tmpl');
} else {
    GrumpyView::render('show_stolen_cc.tmpl');
}
Dependency Injection
    Containers




             Pimple
  (http://pimple.sensiolabs.com)
<INSERT INNUENDO-LADEN
JOKE ABOUT INITIALS HERE >



Acts as a global registry for code


Increases ability to reuse objects
<?php

// Inside our bootstrap

...

// Create our container
$container = new Pimple();

// Inject our DB connection object for later reuse
$container['db_connection'] = function ($c) {
    return new PDO(
       'pgsql:host=localhost;dbname=ibl_stats',
       'stats',
       'st@ts=Fun'
    );
};


// Then in a controller somewhere...
$mapper = new IBLFranchiseMapper($container['db_connection']);
<?php

// In the bootstrap, after having already added
// in the database connection

$container['franchise_mapper'] = function ($c) {
   return new IBLFranchiseMapper($c['db_container']);
};
$container['game_mapper'] = function ($c) {
   return new IBLGameMapper($c['db_container']);
};
<?php

class Omega
{
   protected $foo;
   protected $bar;
   protected $bizz;

    public function __construct($foo, $bar, $bizz)
    {
      $this->foo = $foo;
      $this->bar = $bar;
      $this->bizz = $bizz;
    }
}

$dep1 = new Foo();
$dep2 = new Bar();
$dep3 = new Bizz();
$newOmega = new Omega($dep1, $dep2, $dep3);
<?php

class Omega
{
   protected $foo;
   protected $bar;
   protected $bizz;

    public function __construct($container)
    {
      $this->foo = $container['foo'];
      $this->bar = $container['bar'];
      $this->bizz = $container['biz'];
    }
}

$container['foo'] = new Foo();
$container['bar'] = new Bar();
$container['bizz'] = new Bizz();
$newOmega = new Omega($container);
Application-wide configuration
  values allow you to make
environment-specific decisions
Mock your objects




before they mock you
Mock Objects for
testing purposes
 3rd-party web services


 Accessing data sources


 Environment independent
<?php

// Now here is our test case

include 'test_bootstrap.php';

class AclTest extends PHPUnit_Framework_TestCase
{
   ...

    public function testDashboardAcl()
    {
      // Create a mock object
      $testRequest = $this->getMock(
          'GrumpyRequest',
          array('getUri')
      );

        // Create our test response
        $testRespect
           ->expects()
           ->method('getUri')
           ->with('/dashboard');

        $testUserLevel = 3;
        $testAcl = new GrumpyAcl($testRequest, $testUserLevel);
        $response = $testAcl->accessAllowed();
        $this->assertTrue($response);
    }

}
Mock objects force you to
 test code, not it’s ability
to talk to outside sources
Like an onion,
you application
   has layers
Building Testable Models
<?php
include 'test_bootstrap.php';

$conn = new PDO(
   'pgsql:host=localhost;dbname=ibl_stats',
   'stats',
   'st@ts=Fun'
);
echo "Collecting all games in our season...n";
$mapper = new IBLFranchiseMapper($conn);
$allFranchises = $mapper->findAll();
echo "Writing franchise objects into fixture file...n";
file_put_contents(
   './fixtures/franchises.txt',
serialize($allFranchises));
echo "Donen";
<?php

$testGames = unserialize(file_get_contents('./fixtures/games.txt'));
$conn = new PDO(
   'pgsql:host=localhost;dbname=ibl_stats',
   'stats',
   'st@ts=Fun'
);
$testFranchiseMapper = new IBLFranchiseMapper($conn);
$testStandings = new IBLStandings($testGames, $testFranchiseMapper);
$testResults = $testStandings->generateBasic();
$this->assertTrue(count($testResults) > 0);
$testResult = $testResults['AC']['East'][0];
$this->assertEquals(1, $testResult['teamId'], 'Got expected team ID');
$this->assertEquals(97, $testResult['wins'], 'Got expected win total');
$this->assertEquals(65, $testResult['losses'], 'Got expected loss total');
$this->assertEquals('--', $testResult['gb'], 'Got expected GB total');
<?php
include 'test_bootstrap.php';

$conn = new PDO(
   'pgsql:host=localhost;dbname=ibl_stats',
   'stats',
   'st@ts=Fun'
);
echo "Collecting all fixtures for our league...n";
$mapper = new IBLFranchiseMapper($conn);
$allFranchises = $mapper->findAll();
echo "Writing franchise objects into fixture file...n";
file_put_contents('./fixtures/franchises.txt', serialize($allFranchises));
echo "Donen";
<?php
include './test_bootstrap.php';

class StandingsTest extends PHPUnit_Framework_TestCase
{
   public function testGenerateRegular()
   {
      $data = file_get_contents('./fixtures/games.txt');
      $testGames = unserialize($data);
      $data = file_get_contents('./fixtures/franchises.txt');
      $testFranchises = unserialize($data);
      $testStandings = new IBLStandings($testGames, $testFranchises);

        // Rest of the test is the same
    }
}
Work as hard as you can
 to remove the database
as a dependency for tests
Testing 3rd Party APIS
<?php
// Assume $facebookUser is the object representing info
// about our user

$_SESSION['email'] = $facebookUser->getEmail();
$_SESSION['name'] =
   $facebookUser->getFirstName() .
   ''.
   $facebookUser->getLastName();

// In other parts of the app when I need to check if the user is
// authenticated properly

if (isset($_SESSION['email'])) {
    // Execute the desired functionality
} else {
    // Redirect user to log in with Facebook Connect
}
<?php
// New object that holds info about your Authenticated user

class AuthUser
{

    ...

    public function check()
    {
      return (isset($_SESSION['email']));
    }
}

// Then in the controller where you check authorization...

$authUser = new AuthUser();

if ($authUser->check() === true) {
    // Execute the desired functionality
} else {
    // Redirect user to log in with Facebook Connect
}
<?php

class AuthUser
{
   protected $_sessionObject;

    public function __construct($sessionContents)
    {
      $this->_sessionContents = $sessionContents;
    }

    public function check()
    {
      return (isset($this->_sessionContents['email']));
    }
}
BONUS ROUND!
      <?php
      // Inside your unit test
      $facebookMock = Mockery::mock(
          'FacebookAPIFacebook',
          array(
             'getEmail' => 'test@littlehart.net',
             'getName' => 'Testy McTesterson'
          )
      );

      $sessionContents['email'] = $facebookMock->getEmail();
      $auth = new AuthUser($sessionContents);
      $this->assertTrue($auth->check());




Mockery - https://github.com/padraic/mockery
If it’s not yours,
    wrap it up
  and mock it!
A brief aside about
testing controllers...
Building Testable Views
To test your output
you must capture it
function render($template, $data)
<?php

function render($template, $data) {
   $tokens = array_keys($data);
   $values = array_values($data);

    return str_replace($tokens, $values, $template);
}

$template = "
%%FOO%%<br>
%%BAR%%<br>
%%BAZ%%<br>
";

$data = array(
   'FOO' => "This is foo",
   'BAR' => "Followed by bar",
   'BAZ' => "Finally ended with baz"
);

echo render($template, $data);
Twig - http://twig.sensiolabs.org
<?php
include 'bootstrap.php';

...

echo $twig->render(
   'index.html',
   array(
      'currentWeek' => $currentWeek,
      'currentResults' => $currentResults,
      'currentRotations' => $currentRotations,
      'currentSchedules' => $currentSchedules,
      'franchises' => $franchises,
      'rotationWeek' => $rotationWeek,
      'scheduleWeek' => $scheduleWeek,
      'standings' => $regularStandings,
   )
);
public function setUp()
 {
   // also include our libraries installed using Composer
   include APP_ROOT . 'vendor/.composer/autoload.php';

    // We are using Twig for templating
    $loader = new Twig_Loader_Filesystem(APP_ROOT . 'templates');
    $this->_twig = new Twig_Environment($loader);
    $this->_twig = new Twig_Environment($loader, array('debug' => true));
    $this->_twig->addExtension(new Twig_Extensions_Extension_Debug());
}
$gameMapper = new IBLGameMapper();
$franchiseMapper = new IBLFranchiseMapper();
$rotationMapper = new IBLRotationMapper();
$scheduleMapper = new IBLScheduleMapper();
$games = unserialize(file_get_contents('./fixtures/games.txt'));
$franchises = unserialize(
   file_get_contents('./fixtures/franchises.txt')
);
$standings = new IBLStandings($games, $franchises);
$regularStandings = $standings->generateRegular();
$currentWeek = 27;
$currentGames = unserialize(
   file_get_contents('./fixtures/games-27.txt')
);
$currentResults = $gameMapper->generateResults(
   $currentGames,
   $franchises
);
$rotations = unserialize(
    file_get_contents('./fixtures/rotations-27.txt')
 );
 $currentRotations = $rotationMapper->generateRotations(
    $rotations,
    $franchises
 );
 $rawSchedules = unserialize(
    file_get_contents('./fixtures/raw-schedules-27.txt')
 );
 $franchiseMap = unserialize(
    file_get_contents('./fixtures/franchise-mappings.txt')
 );
 $currentSchedules = $scheduleMapper->generate(
    $rawSchedules,
    $franchiseMap
 );
$response = $this->_twig->render(
   'index.html',
   array(
      'currentWeek' => $currentWeek,
      'currentResults' => $currentResults,
      'currentRotations' => $currentRotations,
      'currentSchedules' => $currentSchedules,
      'franchises' => $franchises,
      'rotationWeek' => $currentWeek,
      'scheduleWeek' => $currentWeek,
      'standings' => $regularStandings,
   )
);
$standingsHeader = "Standings through week 27";
$resultsHeader = "Results for week 27";
$rotationsHeader = "Rotations for Week 27";
$scheduleHeader = "Schedule for Week 27";
$rotation = "KC Greinke, CHN Lilly -2, CHN Wells -2";
$this->assertTrue(stripos($response, $standingsHeader) !== false);
$this->assertTrue(stripos($response, $resultsHeader) !== false);
$this->assertTrue(stripos($response, $rotationsHeader) !== false);
$this->assertTrue(stripos($response, $scheduleHeader) !== false);
<?php
// $response contains the output
$domQuery = new Zend_Dom_Query($response);
// Find the div for our standings display
$node = $domQuery
    ->queryXpath('//div[@id="standings"]');
$this->assertTrue($node != "");
How do we test this?!?
Sahi
Feature: Do a Google search
  In order to find pages about Behat
  As a user
  I want to be able to use google.com to locate
search results

    @javascript
    Scenario: I search for Behat
     Given I fill in "Behat github" for "q"
      When I press "Google Search"
      Then I should see "Trying to use Mink"
Testing tools like BMSP
are at the front-end of
     Web Pi tesing
Why do we test?




Because programming
       is hard
Don’t settle for
untestable applications!
http://leanpub.com/grumpy-testing
Sample Application!

https://github.com/chartjes/building-testable-
                 applications

Contains way more code than in the slides

   Full test suite for one-page application
Stay Grumpy!



• http://www.littlehart.net/atthekeyboard
• @chartjes

More Related Content

What's hot

Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnit
Michelangelo van Dam
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013
Michelangelo van Dam
 
UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013
Michelangelo van Dam
 
PHPUnit best practices presentation
PHPUnit best practices presentationPHPUnit best practices presentation
PHPUnit best practices presentation
Thanh Robi
 
PhpUnit Best Practices
PhpUnit Best PracticesPhpUnit Best Practices
PhpUnit Best Practices
Edorian
 
Unit Testing Presentation
Unit Testing PresentationUnit Testing Presentation
Unit Testing Presentationnicobn
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12
Michelangelo van Dam
 
Unit Testing using PHPUnit
Unit Testing using  PHPUnitUnit Testing using  PHPUnit
Unit Testing using PHPUnitvaruntaliyan
 
Testing Code and Assuring Quality
Testing Code and Assuring QualityTesting Code and Assuring Quality
Testing Code and Assuring Quality
Kent Cowgill
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Michelangelo van Dam
 
Test your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceTest your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practice
Sebastian Marek
 
Introduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnitIntroduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnit
Michelangelo van Dam
 
New Features PHPUnit 3.3 - Sebastian Bergmann
New Features PHPUnit 3.3 - Sebastian BergmannNew Features PHPUnit 3.3 - Sebastian Bergmann
New Features PHPUnit 3.3 - Sebastian Bergmanndpc
 
Advanced PHPUnit Testing
Advanced PHPUnit TestingAdvanced PHPUnit Testing
Advanced PHPUnit Testing
Mike Lively
 
Unit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDDUnit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDD
Paweł Michalik
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEnterprise PHP Center
 
PHPUnit: from zero to hero
PHPUnit: from zero to heroPHPUnit: from zero to hero
PHPUnit: from zero to hero
Jeremy Cook
 
Test Driven Development with PHPUnit
Test Driven Development with PHPUnitTest Driven Development with PHPUnit
Test Driven Development with PHPUnit
Mindfire Solutions
 
Diving into HHVM Extensions (PHPNW Conference 2015)
Diving into HHVM Extensions (PHPNW Conference 2015)Diving into HHVM Extensions (PHPNW Conference 2015)
Diving into HHVM Extensions (PHPNW Conference 2015)
James Titcumb
 
Testing in Laravel
Testing in LaravelTesting in Laravel
Testing in Laravel
Ahmed Yahia
 

What's hot (20)

Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnit
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013
 
UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013
 
PHPUnit best practices presentation
PHPUnit best practices presentationPHPUnit best practices presentation
PHPUnit best practices presentation
 
PhpUnit Best Practices
PhpUnit Best PracticesPhpUnit Best Practices
PhpUnit Best Practices
 
Unit Testing Presentation
Unit Testing PresentationUnit Testing Presentation
Unit Testing Presentation
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12
 
Unit Testing using PHPUnit
Unit Testing using  PHPUnitUnit Testing using  PHPUnit
Unit Testing using PHPUnit
 
Testing Code and Assuring Quality
Testing Code and Assuring QualityTesting Code and Assuring Quality
Testing Code and Assuring Quality
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
 
Test your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceTest your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practice
 
Introduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnitIntroduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnit
 
New Features PHPUnit 3.3 - Sebastian Bergmann
New Features PHPUnit 3.3 - Sebastian BergmannNew Features PHPUnit 3.3 - Sebastian Bergmann
New Features PHPUnit 3.3 - Sebastian Bergmann
 
Advanced PHPUnit Testing
Advanced PHPUnit TestingAdvanced PHPUnit Testing
Advanced PHPUnit Testing
 
Unit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDDUnit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDD
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
 
PHPUnit: from zero to hero
PHPUnit: from zero to heroPHPUnit: from zero to hero
PHPUnit: from zero to hero
 
Test Driven Development with PHPUnit
Test Driven Development with PHPUnitTest Driven Development with PHPUnit
Test Driven Development with PHPUnit
 
Diving into HHVM Extensions (PHPNW Conference 2015)
Diving into HHVM Extensions (PHPNW Conference 2015)Diving into HHVM Extensions (PHPNW Conference 2015)
Diving into HHVM Extensions (PHPNW Conference 2015)
 
Testing in Laravel
Testing in LaravelTesting in Laravel
Testing in Laravel
 

Similar to Building Testable PHP Applications

Quality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormQuality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStorm
Michelangelo van Dam
 
Fatc
FatcFatc
PHPSpec BDD Framework
PHPSpec BDD FrameworkPHPSpec BDD Framework
PHPSpec BDD Framework
Marcello Duarte
 
Zend Certification PHP 5 Sample Questions
Zend Certification PHP 5 Sample QuestionsZend Certification PHP 5 Sample Questions
Zend Certification PHP 5 Sample Questions
Jagat Kothari
 
Ch ch-changes cake php2
Ch ch-changes cake php2Ch ch-changes cake php2
Ch ch-changes cake php2
markstory
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
Hisateru Tanaka
 
Zend framework 03 - singleton factory data mapper caching logging
Zend framework 03 - singleton factory data mapper caching loggingZend framework 03 - singleton factory data mapper caching logging
Zend framework 03 - singleton factory data mapper caching logging
Tricode (part of Dept)
 
Modernising Legacy Code
Modernising Legacy CodeModernising Legacy Code
Modernising Legacy Code
SamThePHPDev
 
PHPSpec BDD for PHP
PHPSpec BDD for PHPPHPSpec BDD for PHP
PHPSpec BDD for PHP
Marcello Duarte
 
Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4
Jeff Carouth
 
Living With Legacy Code
Living With Legacy CodeLiving With Legacy Code
Living With Legacy Code
Rowan Merewood
 
My Development Story
My Development StoryMy Development Story
My Development Story
Takahiro Fujiwara
 
Review unknown code with static analysis - bredaphp
Review unknown code with static analysis - bredaphpReview unknown code with static analysis - bredaphp
Review unknown code with static analysis - bredaphp
Damien Seguy
 
symfony on action - WebTech 207
symfony on action - WebTech 207symfony on action - WebTech 207
symfony on action - WebTech 207patter
 
Advanced Php - Macq Electronique 2010
Advanced Php - Macq Electronique 2010Advanced Php - Macq Electronique 2010
Advanced Php - Macq Electronique 2010
Michelangelo van Dam
 
Automated code audits
Automated code auditsAutomated code audits
Automated code audits
Damien Seguy
 
Creating "Secure" PHP Applications, Part 1, Explicit Code & QA
Creating "Secure" PHP Applications, Part 1, Explicit Code & QACreating "Secure" PHP Applications, Part 1, Explicit Code & QA
Creating "Secure" PHP Applications, Part 1, Explicit Code & QAarchwisp
 
Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13
Jason Lotito
 
Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)
andrewnacin
 
Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2
Shinya Ohyanagi
 

Similar to Building Testable PHP Applications (20)

Quality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormQuality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStorm
 
Fatc
FatcFatc
Fatc
 
PHPSpec BDD Framework
PHPSpec BDD FrameworkPHPSpec BDD Framework
PHPSpec BDD Framework
 
Zend Certification PHP 5 Sample Questions
Zend Certification PHP 5 Sample QuestionsZend Certification PHP 5 Sample Questions
Zend Certification PHP 5 Sample Questions
 
Ch ch-changes cake php2
Ch ch-changes cake php2Ch ch-changes cake php2
Ch ch-changes cake php2
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
 
Zend framework 03 - singleton factory data mapper caching logging
Zend framework 03 - singleton factory data mapper caching loggingZend framework 03 - singleton factory data mapper caching logging
Zend framework 03 - singleton factory data mapper caching logging
 
Modernising Legacy Code
Modernising Legacy CodeModernising Legacy Code
Modernising Legacy Code
 
PHPSpec BDD for PHP
PHPSpec BDD for PHPPHPSpec BDD for PHP
PHPSpec BDD for PHP
 
Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4
 
Living With Legacy Code
Living With Legacy CodeLiving With Legacy Code
Living With Legacy Code
 
My Development Story
My Development StoryMy Development Story
My Development Story
 
Review unknown code with static analysis - bredaphp
Review unknown code with static analysis - bredaphpReview unknown code with static analysis - bredaphp
Review unknown code with static analysis - bredaphp
 
symfony on action - WebTech 207
symfony on action - WebTech 207symfony on action - WebTech 207
symfony on action - WebTech 207
 
Advanced Php - Macq Electronique 2010
Advanced Php - Macq Electronique 2010Advanced Php - Macq Electronique 2010
Advanced Php - Macq Electronique 2010
 
Automated code audits
Automated code auditsAutomated code audits
Automated code audits
 
Creating "Secure" PHP Applications, Part 1, Explicit Code & QA
Creating "Secure" PHP Applications, Part 1, Explicit Code & QACreating "Secure" PHP Applications, Part 1, Explicit Code & QA
Creating "Secure" PHP Applications, Part 1, Explicit Code & QA
 
Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13
 
Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)
 
Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2
 

Building Testable PHP Applications

  • 1. Building Testable PHP Applications Chris Hartjes January 13, 2012
  • 2. $progStartYear = 1982; $firstComputer = ‘VIC-20’; $phpStartYear = 1998;
  • 3. High traffic online dating Sports data integration Social commerce platform
  • 4. Sorry, but a lot of this might not make sense if you are a beginning programmer
  • 5.
  • 6.
  • 8. Some applications WILL resist all attempts to test
  • 9. You don’t always get what you want...
  • 10. What is in my testing toolkit?
  • 11.
  • 12. Effective testing is always automated because people make mistakes
  • 13. “Simple systems can display complex behaviour but complex systems can only display simple behaviour”.
  • 14. The gold-standard for unit testing in PHP
  • 15. public function testSaveUpdatesDatabase() { $mapper = new IBLFranchiseMapper($this->_conn); $franchise = new IBLFranchise(); $franchise->setId(25); $franchise->setNickname('TST'); $franchise->setName('Test Team'); $franchise->setConference('Conference'); $franchise->setDivision('Division'); $franchise->setIp(0); $mapper->save($franchise); // Update existing model $franchise->setIp(35); $mapper->save($franchise); // Reload Franchise record and compare them $franchise2 = $mapper->findById($franchise->getId()); $this->assertEquals(35, $franchise2->getIp()); // Clean up the franchise $mapper->delete($franchise); }
  • 16. My preferred Behaviour-Driven Development framework
  • 17. Feature: ls In order to see the directory structure As a UNIX user I need to be able to list the current directory's contents Scenario: List 2 files in a directory Given I am in a directory "test" And I have a file named "foo" And I have a file named "bar" When I run "ls" Then I should get: """ bar foo """
  • 18. Excuses are like nipples, everyone has at least one “Writing tests will slow me down.” “Why can’t we just manually test in the browser?” “I’d be done with this already if I didn’t have to write the tests!”
  • 19. “A project using TDD would take 20 to 40% longer to complete but resulted in 40 to 90% fewer bugs discovered in production.” http://www.infoq.com/news/2009/03/TDD-Improves-Quality
  • 20. Testing Code Architecture
  • 21. Which one of these things is not like the other? Development Staging Production
  • 22.
  • 23. Vagrant Virtualbox + Ruby + (Chef | Puppet | Shell)
  • 24. The only difference between environments should be the data that they use
  • 25. Static Code Analysis PHP Code Sniffer PHP Mess Detector PHP Copy Paster Detector
  • 26. PHP Code Sniffer Enforcement of code to expected standard Create your own “code sniffs” if required Great if team having problems with consistency
  • 27. chartjes@yggdrasil [09:39:37] [~/Sites/local.ibl/lib/ibl] [master *] -> % phpcs FranchiseMapper.php FILE: /Users/chartjes/Sites/local.ibl/lib/ibl/FranchiseMapper.php FOUND 13 ERROR(S) AND 5 WARNING(S) AFFECTING 18 LINE(S) 2 | ERROR | Missing file doc comment 5 | ERROR | Missing class doc comment 6 | ERROR | Expected 0 spaces before opening brace; 1 found 10 | ERROR | You must use "/**" style comments for a function comment | Missing function doc comment 15 | WARNING | Line exceeds 85 characters; contains 86 characters 20 | ERROR | Missing function doc comment 36 | ERROR | Missing function doc comment 53 | ERROR | Missing function doc comment 74 | ERROR | Missing function doc comment 95 | ERROR | Missing function doc comment 139 | WARNING | Line exceeds 85 characters; contains 90 characters 142 | WARNING | Line exceeds 85 characters; contains 181 characters 149 | ERROR | Missing function doc comment 152 | WARNING | Line exceeds 85 characters; contains 117 characters 154 | WARNING | Line exceeds 85 characters; contains 86 characters
  • 28. PHP Mess Detector Identifies code that is complex Make suggestions on changes
  • 29. > % phpmd lib/vendor/Moontoast/RpcApi.php text codesize,design,naming,unusedcode RpcApi.php:12 The class RpcApi has an overall complexity of 77 which is very high. The configured complexity threshold is 50 high. The configured complexity threshold is 50. RpcApi.php:12 The class RpcApi has a coupling between objects value of 14. Consider to reduce to number of dependencies under 13 RpcApi.php:27 Avoid unused parameters such as '$params'. RpcApi.php:114 The method getProduct() has a Cyclomatic Complexity of 14. RpcApi.php:114 The method getProduct() has an NPath complexity of 655. RpcApi.php:114 Avoid really long methods. RpcApi.php:114 Avoid variables with short names like $id RpcApi.php:234 Avoid unused parameters such as '$params'. RpcApi.php:282 The method getStore() has a Cyclomatic Complexity of 12. RpcApi.php:282 Avoid really long methods. RpcApi.php:302 Avoid unused local variables such as '$price'. RpcApi.php:303 Avoid unused local variables such as '$previousPrice'. RpcApi.php:398 Avoid unused parameters such as '$csc' RpcApi.php:477 The method saveCart() has a Cyclomatic Complexity of 26 RpcApi.php:477 Avoid really long methods RpcApi.php:477 Avoid variables with short names like $id RpcApi.php:477 Avoid excessively long variable names like $useBillingForShipping RpcApi.php:588 The method saveCart() has an NPath complexity of 5644802. RpcApi.php:608 Avoid excessively long variable names like $shippingAddressObject RpcApi.php:671 Avoid unused local variables such as '$gateway'. RpcApi.php:702 Avoid variables with short names like $q RpcApi.php:707 Avoid variables with short names like $mm
  • 30. PHP Copy Paster Detector Just how lazy are you and your team?
  • 31. -> % phpcpd lib/services/AMF_Checkout.php phpcpd 1.3.3 by Sebastian Bergmann. Found 2 exact clones with 52 duplicated lines in 1 files: - AMF_Checkout.php:60-68 AMF_Checkout.php:576-584 - AMF_Checkout.php:88-132 AMF_Checkout.php:602-646 7.21% duplicated lines out of 721 total lines of code. Time: 0 seconds, Memory: 6.50Mb
  • 32. The Law of Demeter The Law of Demeter for functions states that any method of an object should call only methods belonging to itself, any parameters that were passed in to the method, any objects it created, and any directly held component objects. (from the best book EVER for programmers “The Pragmatic Programmer”)
  • 33. Tightly-coupled code will go out of it’s way to make it difficult to test
  • 34. Dependency Injection Pass objects and functions information about the other objects and functions that are required for the task
  • 35. <?php include 'test_bootstrap.php'; class FranchiseModelTest extends PHPUnit_Framework_TestCase { protected $_conn; public function setUp() { $this->_conn = new PDO('pgsql:host=localhost;dbname=ibl_stats', 'stats', 'st@ts=Fun'); } public function tearDown() { unset($this->_conn); } public function testSaveUpdatesDatabase() { $mapper = new IBLFranchiseMapper($this->_conn); $franchise = new IBLFranchise(); $franchise->setId(25); $franchise->setNickname('TST'); $franchise->setName('Test Team'); $franchise->setConference('Conference'); $franchise->setDivision('Division'); $franchise->setIp(0); $mapper->save($franchise); // Update existing model $franchise->setIp(35); $mapper->save($franchise); // Reload Franchise record and compare them $franchise2 = $mapper->findById($franchise->getId()); $this->assertEquals(35, $franchise2->getIp()); // Clean up the franchise $mapper->delete($franchise); } }
  • 36. public function save(IBLFranchise $franchise) { if ($this->findById($franchise->getId())) { $this->_update($franchise); } else { $this->_insert($franchise); } } protected function _insert(IBLFranchise $franchise) { try { $sql = " INSERT INTO franchises (nickname, name, conference, division, ip, id) VALUES(?, ?, ?, ?, ?, ?)"; $sth = $this->_conn->prepare($sql); $binds = array( $franchise->getNickname(), $franchise->getName(), $franchise->getConference(), $franchise->getDivision(), $franchise->getIp(), $franchise->getId() ); $sth->execute($binds); } catch(PDOException $e) { echo "A database problem occurred: " . $e- >getMessage(); } }
  • 37. <?php namespace Grumpy; class Acl { protected $_acls; ... public function accessAlllowed() { $request = GrumpyContext::getRequest(); return ($this->_acls[$request->getUri()] >= $_SESSION['user_level']); } } // Meanwhile inside your controller $acl = new GrumpyAcl(); if (!$acl->accessAllowed()) { GrumpyView::render('access_denied.tmpl'); } else { GrumpyView::render('show_stolen_cc.tmpl'); }
  • 38. <?php namespace Grumpy; class Acl { protected $_acls; protected $_request; protected $_userLevel; ... public function __construct($request, $userLevel) { ... $this->_request = $request; $this->_request = $userLevel; } public function accessAlllowed() { return ($this->_acls[$this->_request->getUri()] >= $this->_userLevel); } } // Meanwhile inside your controller $acl = new GrumpyAcl(); if (!$acl->accessAllowed()) { GrumpyView::render('access_denied.tmpl'); } else { GrumpyView::render('show_stolen_cc.tmpl'); }
  • 39. Dependency Injection Containers Pimple (http://pimple.sensiolabs.com)
  • 40. <INSERT INNUENDO-LADEN JOKE ABOUT INITIALS HERE > Acts as a global registry for code Increases ability to reuse objects
  • 41. <?php // Inside our bootstrap ... // Create our container $container = new Pimple(); // Inject our DB connection object for later reuse $container['db_connection'] = function ($c) { return new PDO( 'pgsql:host=localhost;dbname=ibl_stats', 'stats', 'st@ts=Fun' ); }; // Then in a controller somewhere... $mapper = new IBLFranchiseMapper($container['db_connection']);
  • 42. <?php // In the bootstrap, after having already added // in the database connection $container['franchise_mapper'] = function ($c) { return new IBLFranchiseMapper($c['db_container']); }; $container['game_mapper'] = function ($c) { return new IBLGameMapper($c['db_container']); };
  • 43.
  • 44. <?php class Omega { protected $foo; protected $bar; protected $bizz; public function __construct($foo, $bar, $bizz) { $this->foo = $foo; $this->bar = $bar; $this->bizz = $bizz; } } $dep1 = new Foo(); $dep2 = new Bar(); $dep3 = new Bizz(); $newOmega = new Omega($dep1, $dep2, $dep3);
  • 45. <?php class Omega { protected $foo; protected $bar; protected $bizz; public function __construct($container) { $this->foo = $container['foo']; $this->bar = $container['bar']; $this->bizz = $container['biz']; } } $container['foo'] = new Foo(); $container['bar'] = new Bar(); $container['bizz'] = new Bizz(); $newOmega = new Omega($container);
  • 46.
  • 47. Application-wide configuration values allow you to make environment-specific decisions
  • 48. Mock your objects before they mock you
  • 49. Mock Objects for testing purposes 3rd-party web services Accessing data sources Environment independent
  • 50. <?php // Now here is our test case include 'test_bootstrap.php'; class AclTest extends PHPUnit_Framework_TestCase { ... public function testDashboardAcl() { // Create a mock object $testRequest = $this->getMock( 'GrumpyRequest', array('getUri') ); // Create our test response $testRespect ->expects() ->method('getUri') ->with('/dashboard'); $testUserLevel = 3; $testAcl = new GrumpyAcl($testRequest, $testUserLevel); $response = $testAcl->accessAllowed(); $this->assertTrue($response); } }
  • 51. Mock objects force you to test code, not it’s ability to talk to outside sources
  • 52. Like an onion, you application has layers
  • 54. <?php include 'test_bootstrap.php'; $conn = new PDO( 'pgsql:host=localhost;dbname=ibl_stats', 'stats', 'st@ts=Fun' ); echo "Collecting all games in our season...n"; $mapper = new IBLFranchiseMapper($conn); $allFranchises = $mapper->findAll(); echo "Writing franchise objects into fixture file...n"; file_put_contents( './fixtures/franchises.txt', serialize($allFranchises)); echo "Donen";
  • 55. <?php $testGames = unserialize(file_get_contents('./fixtures/games.txt')); $conn = new PDO( 'pgsql:host=localhost;dbname=ibl_stats', 'stats', 'st@ts=Fun' ); $testFranchiseMapper = new IBLFranchiseMapper($conn); $testStandings = new IBLStandings($testGames, $testFranchiseMapper); $testResults = $testStandings->generateBasic(); $this->assertTrue(count($testResults) > 0); $testResult = $testResults['AC']['East'][0]; $this->assertEquals(1, $testResult['teamId'], 'Got expected team ID'); $this->assertEquals(97, $testResult['wins'], 'Got expected win total'); $this->assertEquals(65, $testResult['losses'], 'Got expected loss total'); $this->assertEquals('--', $testResult['gb'], 'Got expected GB total');
  • 56. <?php include 'test_bootstrap.php'; $conn = new PDO( 'pgsql:host=localhost;dbname=ibl_stats', 'stats', 'st@ts=Fun' ); echo "Collecting all fixtures for our league...n"; $mapper = new IBLFranchiseMapper($conn); $allFranchises = $mapper->findAll(); echo "Writing franchise objects into fixture file...n"; file_put_contents('./fixtures/franchises.txt', serialize($allFranchises)); echo "Donen";
  • 57. <?php include './test_bootstrap.php'; class StandingsTest extends PHPUnit_Framework_TestCase { public function testGenerateRegular() { $data = file_get_contents('./fixtures/games.txt'); $testGames = unserialize($data); $data = file_get_contents('./fixtures/franchises.txt'); $testFranchises = unserialize($data); $testStandings = new IBLStandings($testGames, $testFranchises); // Rest of the test is the same } }
  • 58. Work as hard as you can to remove the database as a dependency for tests
  • 60. <?php // Assume $facebookUser is the object representing info // about our user $_SESSION['email'] = $facebookUser->getEmail(); $_SESSION['name'] = $facebookUser->getFirstName() . ''. $facebookUser->getLastName(); // In other parts of the app when I need to check if the user is // authenticated properly if (isset($_SESSION['email'])) { // Execute the desired functionality } else { // Redirect user to log in with Facebook Connect }
  • 61. <?php // New object that holds info about your Authenticated user class AuthUser { ... public function check() { return (isset($_SESSION['email'])); } } // Then in the controller where you check authorization... $authUser = new AuthUser(); if ($authUser->check() === true) { // Execute the desired functionality } else { // Redirect user to log in with Facebook Connect }
  • 62. <?php class AuthUser { protected $_sessionObject; public function __construct($sessionContents) { $this->_sessionContents = $sessionContents; } public function check() { return (isset($this->_sessionContents['email'])); } }
  • 63. BONUS ROUND! <?php // Inside your unit test $facebookMock = Mockery::mock( 'FacebookAPIFacebook', array( 'getEmail' => 'test@littlehart.net', 'getName' => 'Testy McTesterson' ) ); $sessionContents['email'] = $facebookMock->getEmail(); $auth = new AuthUser($sessionContents); $this->assertTrue($auth->check()); Mockery - https://github.com/padraic/mockery
  • 64. If it’s not yours, wrap it up and mock it!
  • 65. A brief aside about testing controllers...
  • 67. To test your output you must capture it
  • 69. <?php function render($template, $data) { $tokens = array_keys($data); $values = array_values($data); return str_replace($tokens, $values, $template); } $template = " %%FOO%%<br> %%BAR%%<br> %%BAZ%%<br> "; $data = array( 'FOO' => "This is foo", 'BAR' => "Followed by bar", 'BAZ' => "Finally ended with baz" ); echo render($template, $data);
  • 71. <?php include 'bootstrap.php'; ... echo $twig->render( 'index.html', array( 'currentWeek' => $currentWeek, 'currentResults' => $currentResults, 'currentRotations' => $currentRotations, 'currentSchedules' => $currentSchedules, 'franchises' => $franchises, 'rotationWeek' => $rotationWeek, 'scheduleWeek' => $scheduleWeek, 'standings' => $regularStandings, ) );
  • 72. public function setUp() { // also include our libraries installed using Composer include APP_ROOT . 'vendor/.composer/autoload.php'; // We are using Twig for templating $loader = new Twig_Loader_Filesystem(APP_ROOT . 'templates'); $this->_twig = new Twig_Environment($loader); $this->_twig = new Twig_Environment($loader, array('debug' => true)); $this->_twig->addExtension(new Twig_Extensions_Extension_Debug()); }
  • 73. $gameMapper = new IBLGameMapper(); $franchiseMapper = new IBLFranchiseMapper(); $rotationMapper = new IBLRotationMapper(); $scheduleMapper = new IBLScheduleMapper();
  • 74. $games = unserialize(file_get_contents('./fixtures/games.txt')); $franchises = unserialize( file_get_contents('./fixtures/franchises.txt') ); $standings = new IBLStandings($games, $franchises); $regularStandings = $standings->generateRegular(); $currentWeek = 27; $currentGames = unserialize( file_get_contents('./fixtures/games-27.txt') ); $currentResults = $gameMapper->generateResults( $currentGames, $franchises );
  • 75. $rotations = unserialize( file_get_contents('./fixtures/rotations-27.txt') ); $currentRotations = $rotationMapper->generateRotations( $rotations, $franchises ); $rawSchedules = unserialize( file_get_contents('./fixtures/raw-schedules-27.txt') ); $franchiseMap = unserialize( file_get_contents('./fixtures/franchise-mappings.txt') ); $currentSchedules = $scheduleMapper->generate( $rawSchedules, $franchiseMap );
  • 76. $response = $this->_twig->render( 'index.html', array( 'currentWeek' => $currentWeek, 'currentResults' => $currentResults, 'currentRotations' => $currentRotations, 'currentSchedules' => $currentSchedules, 'franchises' => $franchises, 'rotationWeek' => $currentWeek, 'scheduleWeek' => $currentWeek, 'standings' => $regularStandings, ) );
  • 77. $standingsHeader = "Standings through week 27"; $resultsHeader = "Results for week 27"; $rotationsHeader = "Rotations for Week 27"; $scheduleHeader = "Schedule for Week 27"; $rotation = "KC Greinke, CHN Lilly -2, CHN Wells -2"; $this->assertTrue(stripos($response, $standingsHeader) !== false); $this->assertTrue(stripos($response, $resultsHeader) !== false); $this->assertTrue(stripos($response, $rotationsHeader) !== false); $this->assertTrue(stripos($response, $scheduleHeader) !== false);
  • 78. <?php // $response contains the output $domQuery = new Zend_Dom_Query($response); // Find the div for our standings display $node = $domQuery ->queryXpath('//div[@id="standings"]'); $this->assertTrue($node != "");
  • 79. How do we test this?!?
  • 80. Sahi
  • 81.
  • 82. Feature: Do a Google search In order to find pages about Behat As a user I want to be able to use google.com to locate search results @javascript Scenario: I search for Behat Given I fill in "Behat github" for "q" When I press "Google Search" Then I should see "Trying to use Mink"
  • 83. Testing tools like BMSP are at the front-end of Web Pi tesing
  • 84. Why do we test? Because programming is hard
  • 85.
  • 88.
  • 89. Sample Application! https://github.com/chartjes/building-testable- applications Contains way more code than in the slides Full test suite for one-page application