C H A P T E R  6

image

Refactoring Tools

Without tools helping us with refactoring, doing a good job could be very difficult. We need a test framework to perform all refactoring activities and some automated tools to increase our productivity.

In this chapter we will see which features we should have in our integrated development environment (IDE) for our refactoring activities, and we'll see how to write unit tests with the PHPUnit testing framework and functional tests with Selenium. We'll see also how integrate the two tools through an extension of PHPUnit to completely automate the execution of our build test.

PHP IDE

In the PHP programming world there are several Integrated Development Environments (IDE). Each of us surely has a favorite. Refactoring activities can be done with a text editor like “vi” but if we have an IDE that supports the automation of some refactoring activities, certainly it could increase our productivity in this kind of job.

Our intention isn't to promote one IDE rather than another, so we will stay neutral, considering only the features that an IDE should have to support us in some refactoring activities.

Refactoring Activities

When you refactor a very large application, there are some costly operations that can be easily automated using the right IDE:

  • Renaming parameters, methods, and classes
  • Moving parameters, methods, and classes
  • Encapsulating the parameters of a class
  • Making override methods
  • Safely removing
Rename

“Rename” means the ability of an IDE to automatically rename a parameter of a class, a method of a class, or the class itself, wherever it is invoked or used in our code.

Move

“Move” refers to the ability of an IDE to automatically move a class parameter or method within a hierarchy, or from a class to another linked.

Encapsulate Field

“Encapsulate field” refers to the ability of an IDE to automatically create getter and setter methods for the public properties of a certain class in order to be able to change the visibility.

Override

“Override” means the ability of an IDE to automatically create override methods from a parent class in child classes.

Safely Remove

“Safely remove” means the ability of an IDE to delete a file from a project virtually and not physically, so that you can restore it in case there are errors caused by the absence of this file.

Cross-Platform Open-Source IDE

There is a lot of open-source and closed-source software that has these features, but to be neutral, we want to talk only about two IDEs that are open-source and multi-platform. These two applications have some of the features mentioned, and the developers are working to integrate all of them in future releases. The first software is NetBeans, a very complete IDE that was made to develop Java applications. It is sponsored by Oracle, and the latest version supports PHP language and some refactoring activities.1

1NetBeans, NetBeans IDE 6.8 Release Information, http://netbeans.org/community/releases/68/

The other is PHP Development Tools (PDT), a module for the Eclipse open-source software, developed by the open-source community, Zend, and IBM, which incorporates many features for development with PHP and refactoring activities.2

2Eclipse, PHP Development Tools Project, www.eclipse.org/pdt

For more information on this software and its refactoring tools, we recommend the official documentation.

Unit Tests with PHPUnit

PHPUnit is a framework of the xUnit family that allows testing units of PHP code. Through the available tools we can test classes, methods, and functions, run the tests from the console, and create reports to analyze the results.

What Is It?

The PHPUnit framework is the complete port of JUnit 3.8.1, a Java testing framework, to PHP5. Through this framework, the following is also possible:3

3PHPUnit, Features, www.phpunit.de/wiki/Features, 11/13/2008

__________

  • Testing the database
  • Using the mock object to test the behavior of classes that depend on other classes
  • Organizing your own tests in suites and groups
  • Filtering the tests to be run
  • Performing custom operations at the end and beginning of each test
  • Logging the tests running in various formats (XML, JSON, TAP, GraphViz, etc.)
  • Creating functional tests for Selenium RC
  • Integrating testing with third-party software (Apache Maven, Bamboo, Bitten, CruiseControl, Parabuild, etc.)

The minimum requirements4 to use PHPUnit are

4PHPUnit, Requirement, www.phpunit.de/wiki/Requirements, 07/17/200

  • PHP 5.1.4 or later (5.2 is recommended)
  • DOM, PCRE, and SPL extensions

Installation

The latest version of PHPUnit, at the time of publication, is 5.1.4. This version requires PHP 5.1.4 or later, and the 5.3.2 version (or later) is strongly recommended.

We can install the framework with the PEAR distribution system or do it manually, downloading the source code from the official repository.

PEAR Installation

Before we install PHPUnit via PEAR it is necessary to record two new channels with the following command:

$ pear channel-discover pear.phpunit.de
$ pear channel-discover pear.symfony-project.com

This should be done only the first time. Once registered, the new channels will always be set in our environment. Then execute the following command to install the framework:

$ pear install phpunit/PHPUnit

When the installation is completed, we find the folder PHPUnit in the PEAR directory. We will also have available the “phpunit” command that we can run from our shell.

Manual Installation

The PEAR installer isn't the only way to install PHPUnit. We can also install the PHPUnit framework manually, following these steps:

  1. Download the latest release archive from http://pear.phpunit.de/get/.
  2. Extract it to a directory that is listed in the include_path of your php.ini configuration file.
  3. Go into the PHPUnit directory and rename the phpunit.php file in phpunit.
  4. Open the file phpunit, just renamed, and replace the @php_bin@ string with your command line PHP interpreter path (usually /usr/bin/php).
  5. Copy the phpunit file into one of the filesystem paths included in the environment PATH variable (usually /usr/local/bin) and make it executable (chmod +x phpunit).
  6. Go again into the PHPUnit directory and replace the @php_bin@ string in the Util/PHP.php file with your command line PHP interpreter path (usually /usr/bin/php).
  7. Run the “phpunit” command and check that it works well.

How to Write Unit Tests

Unit tests, as the name implies, are used to test the code unit, which is the smallest part of our code that can be tested. In PHP, by units we mean the classes and the functions. A unit test should test a single-unit independent piece of code, decoupled from its dependencies.

A PHPUnit test corresponds to a single class that extends the class PHPUnit_Framework_TestCase. The methods of this class are the tests of our code. The name of the methods that represent our tests must begin with the prefix “test,” or have the @test annotation in the method comment.

The class we extend gives us all the assertion methods needed to write our tests easily. For example, we can check if a result of a certain method is equal to a given string, or if it is true or false, in a very simple manner.

In the next example, we create a unit test to verify some array properties.

<?php

class ArrayTest extends PHPUnit_Framework_TestCase
{
  public function testArray()
  {
    $empty_array = array();
    $array = array(
                     'fruit' => 'apple',
                     'color' => 'red',
                     'name' => 'james',
                    'boolean' => false
                   );

    // Check array items number
    $this->assertEquals(0, count($empty_array));
    $this->assertEquals(4, count($array));

    // Check array values
    $this->assertEquals('apple', $array['fruit']);
    $this->assertEquals('red', $array['color']);
    $this->assertEquals('james', $array['name']);
    $this->assertEquals(false, $array['boolean']);
    $this->assertFalse($array['boolean']);

    // Check array keys
    $this->assertArrayHasKey('fruit', $array);
    $this->assertArrayHasKey('color', $array);
    $this->assertArrayHasKey('name', $array);
    $this->assertArrayHasKey('boolean', $array);

    // Check array value
    $this->assertContains('apple', $array);
    $this->assertContains('red', $array);
    $this->assertContains('james', $array);
    $this->assertContains(false, $array);
  }
}
?>

Testing arrays seems easy enough. To create a more complex unit test we test the unit of the class Person.

<?php
class Person
{
  public $firstname;
  public $lastname;
  public $address;
  public $country;
  public $city;

  public function __toString()
  {
    return $this->firstname.' '.$this->lastname;
  }

  public function fromArray($array)
  {
    $this->firstname = isset($array['firstname']) ? $array['firstname'] : $this->firstname;
    $this->lastname  = isset($array['lastname']) ? $array['lastname'] : $this->lastname;
    $this->address   = isset($array['address']) ? $array['address'] : $this->address;
    $this->country   = isset($array['country']) ? $array['country'] : $this->country;
    $this->city      = isset($array['city']) ? $array['city'] : $this->city;
  }

  public function toArray()
  {
    $array = array();
    $array['firstname'] = $this->firstname;
    $array['lastname']  = $this->lastname;
    $array['address']   = $this->address;
    $array['country']   = $this->country;
    $array['city']      = $this->city;

    return $array;
  }
}

?>

We write a PersonTest class test where we test each method and each class attribute.

<?php

include_once('Person.php'),

class PersonTest extends PHPUnit_Framework_TestCase
{
  /**
  * Initialize a Person instance
  */
  public function setUp()
  {
    $this->person = new Person();
    $this->person->firstname = 'James';
    $this->person->lastname = 'Doe';
    $this->person->address = '56 Rupert St';
    $this->person->city = 'London';
    $this->person->country = 'UK';
  }

  /**
  * Test Person attributes
  */
  public function testAttributes()
  {
    $this->assertEquals('James', $this->person->firstname);
    $this->assertEquals('Doe', $this->person->lastname);
    $this->assertEquals('56 Rupert St', $this->person->address);
    $this->assertEquals('London', $this->person->city);
    $this->assertEquals('UK', $this->person->country);

    $this->assertClassHasAttribute('firstname', 'Person'),
    $this->assertClassHasAttribute('lastname', 'Person'),
    $this->assertClassHasAttribute('address', 'Person'),
    $this->assertClassHasAttribute('city', 'Person'),
    $this->assertClassHasAttribute('country', 'Person'),
  }

  /**
  * Test for Person::__toString() method
  */
  public function testToString()
  {
    $this->assertEquals('James Doe', (string) $this->person);
  }

  /**
  * Test for Person::fromArray() method
  */
  public function testFromArray()
  {
    $array = array('firstname' => 'John', 'lastname' => 'Marshall'),

    $this->person->fromArray($array);
    $this->assertEquals('John', $this->person->firstname);
    $this->assertEquals('Marshall', $this->person->lastname);
    $this->assertEquals('56 Rupert St', $this->person->address);

    $this->assertEquals('London', $this->person->city);
    $this->assertEquals('UK', $this->person->country);

  }

  /**
  * Test for Person::toArray() method
  * @test
  */
  public function toArray()
  {
    $array = $this->person->toArray();
    $this->assertEquals(5, count($array));
    $this->assertEquals('James', $array['firstname']);
    $this->assertEquals('Doe', $array['lastname']);
    $this->assertEquals('56 Rupert St', $array['address']);
    $this->assertEquals('London', $array['city']);
    $this->assertEquals('UK', $array['country']);
  }
}
?>

The framework provides two methods—setUp() and tearDown()—that are executed for each test execution. The setUp() method is run at the beginning of each method, and tearDown() is run at the end of each method. We can use these two methods to initialize and destroy our fixtures. In this way we can isolate the execution of each test.

A test method can accept arbitrary attributes as arguments. This content is provided by a data provider method, which can be defined by the notation @dataProvider in the method comment. The method must be public and should return an array of arrays or an object that implements the Iterator interface. The test will be executed many times for the values of the data provider array.

<?php

class MaxTest extends PHPUnit_Framework_TestCase
{
  /**
  * @dataProvider provider
  */
  public function testMax($a, $b, $result)
  {
    $this->assertEquals($result, max($a, $b));
  }

  public function provider()
  {
    return array(
      array(1, 10, 10),
      array(100, 20, 100),
      array(1, 2, 2),
      array(12.4, 12.55, 12.55),
    );
  }
}

?>

Sometimes we have to test that a class method throws an exception. To test the exceptions just use the annotation @expectedException, with the value of the exception we expect, in the remarks of the method.

<?php

class ExceptionTest extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException InvalidArgumentException
     */
    public function testException()
    {
    }
}

?>

How to Run Tests

PHPUnit provides a shell script to run our tests and to provide immediate feedback of results. During installation we copied the script “phpunit” in our executable path and we made it executable. To execute the tests we run the script phpunit from the shell and pass a folder path where our test files are, or the direct test file path to run.

For example, we have all the tests previously presented in the same folder Test.

|- Test
|  |- ArrayTest.php
|  |- ExceptionTest.php
|  |- MaxTest.php
|  |- PersonTest.php

In this case, we can go inside the folder and run the following command:

$ phpunit .
PHPUnit 3.4.1 by Sebastian Bergmann.

.........

Time: 0 seconds

OK (9 tests, 41 assertions)

PHPUnit will automatically find all test files inside the directory executing all test methods. If some of our tests fail, PHPUnit will notice which tests failed, with an “F” character instead of a “.” char, and with an error message above with an execution stack trace.

$ phpunit .
PHPUnit 3.4.1 by Sebastian Bergmann.

.F......F

Time: 0 seconds

There were 2 failures:

1) MaxTest::testMax with data set #1 (100, 20, 20)
Failed asserting that <integer:100> matches expected <integer:20>.

/Users/cphp/Dropbox/Progetti/Libri/Apress/ProPHPRefactoring/drafts/chapter05 -
Tools/code/MaxTest.php:10

2) ArrayTest::testArray
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-banana
+apple

/Users/cphp/Dropbox/Progetti/Libri/Apress/ProPHPRefactoring/drafts/chapter05 - image
Tools/code/arrayTest.php:15

FAILURES!
Tests: 9, Assertions: 29, Failures: 2.

How to Organize Our Tests

The easiest way to organize our test suite is to order our test files in folders and subfolders. PHPUnit is able to recursively traverse the filesystem and find all our test files.

For example, we have a directory of classes grouped by packages.

Src
|- Component
|  |- Autoloader
|  |  |- Autoloader.php
|  |  |- Exception.php
|  |- Cli
|  |  |- Application.php
|  |  |- Cli.php
|  |  |- Exception.php
|  |  |- Task.php
|  |- Deploy
|  |  |- Deploy.php
|  |  |- Exception.php
|  |  |- History.php
|  |  |- Repository.php
|  |- FileSystem
|  |  |- FileSystem.php
|  |  |- Interface.php
|  |  |- Local.php
|  |- Net
|  |  |- Connection.php
|  |  |- Local.php
|  |  |- Ssh2.php
|  |- Util
|  |  |- Rsync.php
|  |  |- Time.php

We can arrange the relative test suites as follows:

Test
|- Component
|  |- Autoloader
|  |  |- AutoloaderTest.php
|  |  |- ExceptionTest.php
|  |- Cli
|  |  |- ApplicationTest.php
|  |  |- CliTest.php
|  |  |- ExceptionTest.php
|  |  |- TaskTest.php
|  |- Deploy
|  |  |- DeployTest.php
|  |  |- ExceptionTest.php
|  |  |- HistoryTest.php
|  |  |- RepositoryTest.php
|  |- FileSystem
|  |  |- FileSystemTest.php
|  |  |- InterfaceTest.php
|  |  |- LocalTest.php
|  |- Net
|  |  |- ConnectionTest.php
|  |  |- LocalTest.php
|  |  |- Ssh2Test.php
|  |- Util
|  |  |- RsyncTest.php
|  |  |- TimeTest.php

Running the PHPUnit command and passing as a parameter the folder Test, the script will find all the tests in the folder and its subfolders and will run them.

$phpunit Tests
PHPUnit 3.4.2 by Sebastian Bergmann.

............................................................ 60 / 81
.....................

Time: 0 seconds

OK (81 tests, 212 assertions)

The problem of organizing the test suite through the filesytem is that we can't decide in which order to run them.

A more elegant and effective way to organize the test suite is through the class PHPUnit_Framework_TestSuite. This class allows you to create a hierarchy of ordered tests. We can create a suite class for each package, and a generic file suite that includes all other package suites, so you can run all tests or just test for a certain package.

Returning to the preceding example, we could have a suite class for each package.

Test
|- Component
|  |- Autoloader
|  |  |- ...
|  |  |- AutoloaderSuite.php
|  |- Cli

|  |  |- ...
|  |  |- CliSuite.php
|  |- Deploy
|  |  |- ...
|  |  |- DeploySuite.php
|  |- FileSystem
|  |  |- ...
|  |  |- FileSystemSuite.php
|  |- Net
|  |  |- ...
|  |  |- NetSuite.php
|  |- Util
|  |  |- ...
|  |  |- UtilSuite.php
|  |- ComponentSuite.php

The ComponentSuite class is the class that includes the suite test of each package.

<?php

include_once('Autoloader/AutoloaderSuite.php'),
include_once('Cli/CliSuite.php'),
include_once('Deploy/DeploySuite.php'),
include_once('FileSystem/FileSystemSuite.php'),
include_once('Net/NetSuite.php'),
include_once('Util/UtilSuite.php'),

class ComponentSuite
{
  public static function suite()
  {
    $suite = new PHPUnit_Framework_TestSuite('Component'),
    $suite->addTest(AutoloaderSuite::suite());
    $suite->addTest(CliSuite::suite());
    $suite->addTest(DeploySuite::suite());
    $suite->addTest(FileSystemSuite::suite());
    $suite->addTest(NetSuite::suite());
    $suite->addTest(UtilSuite::suite());

    return $suite;
  }
}

?>

The internal class suites, instead, collect test class files for each package, for example:

<?php

include_once('AutoloaderTest.php'),
include_once('ExceptionTest.php'),

class AutoloaderSuite
{
  public static function suite()
  {

    $suite = new PHPUnit_Framework_TestSuite('Autoloader'),
    $suite->addTestSuite('AutoloaderTest'),
    $suite->addTestSuite('ExceptionTest'),

    return $suite;
  }
}

?>

The AutoloaderTest class and ExceptionTest class are the test classes that extend PHPUnit_Framework_TestCase. In this way, deciding when to add a class test, we can decide the execution order. To execute all tests, we have to run the following command:

$ phpunit ComponentSuite
PHPUnit 3.4.2 by Sebastian Bergmann.

............................................................ 60 / 81
.....................

Time: 0 seconds

OK (81 tests, 212 assertions)

To run the tests of Autoload Package, we will run

$ phpunit Autoload/AutoloadSuite
PHPUnit 3.4.2 by Sebastian Bergmann.

....

Time: 0 seconds

OK (4 tests, 20 assertions)

Test Doubles

“Sometimes it is just plain hard to test the system under test (SUT) because it depends on other components that cannot be used in the test environment. This could be because they aren't available, they will not return the results needed for the test, or executing them would have undesirable side effects. In other cases, our test strategy requires us to have more control or visibility of the internal behavior of the SUT.

“When we are writing a test in which we cannot (or choose not to) use a real depended-on component (DOC), we can replace it with a Test Double. The Test Double doesn't have to behave exactly like the real DOC; it merely has to provide the same API as the real one so that the SUT thinks it is the real one!”

—Gerard Meszaros [MES07]

When we do refactoring, we should tests interdependent classes; dependent classes due to poor design or to real domain dependencies. Since the test should be individual and independent, testing interdependent classes may create side effects due to their dependencies. For example, a test may fail not because it changes the class tested, but because it has changed its dependent class.

When the system under test depends on external components, we introduce test doubles. Through this technique we can simulate the behavior of dependent classes without really using them, like a film director using stuntmen, rather than the real actors, for difficult roles.

PHPUnit provides tools to create a Stub Object and a Mock Object. Meszaros [MES07] defines a Stub Object as a real component on which the system under test depends so that the test has a control point for the indirect inputs of the system underneath. This allows the test to force the system under test down paths it might not otherwise execute.

Conversely, he defines a Mock Object as an observation point that is used to verify the indirect outputs of the system under test as it is exercised. Typically, the Mock Object also includes the functionality of a Test Stub in that it must return values to the system under test if it hasn't already failed the tests, but the emphasis is on the verification of the indirect outputs. Therefore, a Mock Object is a lot more than just a Test Stub plus assertions; it is used in a fundamentally different way.

Stub Object

For example, if we want to simulate the following class in one of our tests, because our system under test depends on it, we can use a stub object instead of it.

<?php

class Foo
{
  public function bar()
  {
    // do something
  }
}

?>

We can replace the Foo class with a stub object calling the method getMock() of the PHPUnit_Framework_TestCase super class.

<?php

class StubTest extends PHPUnit_Framework_TestCase
{
  public function testStub()
  {
      // Create a stub for the Foo class
      $stub = $this->getMock('Foo', array('bar'));

      // Configure the stub
      $stub->
        expects($this->any())->
        method('bar')->
        will($this->returnValue('something'));

      // Test that calling $stub->doSomething() will now return 'something'

      $this->assertEquals('something', $stub->bar());
  }
}

?>

Thus PHPUnit generates on the fly a class Foo, which returns a static value “something” whenever its bar() method is invoked, without putting at stake the original class Foo. The stub class can be fully configured and can return static values, objects, callbacks, exceptions, or arguments passed to the method.

Mock Object

If we want to create a Mock Object with PHPUnit, we use the same method getMock(), but we can configure the returned object verifying that a certain method is called, when it is called, and how often.

In the next example we want to test the Order class that depends on the Item class. When I call the getTotal() method of the Order class I want to verify that also the getTotal() method of the Item class is called and how often, without introducing a real instance of the Item class.

<?php

class Order
{
  public function addItem(Item $item)
  {
    $this->items[] = $item;
  }

  public function getTotal()
  {
    $total = 0;
    foreach($this->items as $item)
    {
      $total += $item->getTotal(true);
    }
    return $total;
  }
}

class Item
{
  public $quantity;
  public $price;

  public function getTotal()
  {
    return $quantity*$price;
  }
}

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function testGetTotal()
  {
    $item = $this->getMock('Item', array('getTotal'));

    $item->expects($this->once())->
      method('getTotal')->
      with($this->equalTo(true))->
      will($this->returnValue(10));

    $order = new Order();
    $order->addItem($item);
    $this->assertEquals(10, $order->getTotal());
  }
}

?>

Without putting at stake the real Item class on which the Order class depends, we test that by adding an item to order, the getTotal() method of the Item class is called once and that the right argument is passed. We also configure the methods to return a static value, so we can test that the getTotal() method of the Order class returns the right result. If the getTotal() method is called twice instead of once, the test fails, throwing an exception.

PHPUnit Conclusion

PHPUnit is a very powerful and complete framework that provides many other features to support our software development process:

  • Code coverage analysis
  • Behavior-driven development
  • Mock per web services e-file system
  • Agile documentation
  • Skeleton generator
  • Logging

To explore all the functionality of PHPUnit you can consult the online documentation at www.phpunit.de/manual/3.4/en/index.html. PHPUnit is an open-source project developed by Sebastian Bergmann and licensed under BSD-style as free software.

Functional Test with Selenium

Selenium provides a set of components to automate the creation of functional tests of web applications.

What Is It?

“Selenium is a portable software testing framework for web applications. Selenium provides a record/playback tool for authoring tests without learning a test scripting language.”

—Wikipedia5

__________

Selenium provides a set of components to automate the creation of functional tests of web applications. With Selenium you can automate the navigation of a web site by creating assertions for testing the presence of certain elements in the DOM. Selenium can test Javascript routines also. One of the main strengths of this tool is that you can run tests on multiple browsers, like Internet Explorer, Firefox, Safari, or Chrome. This way you can test the cross-browser compatibility of web applications.

Selenium provides three main components:

  • Selenium IDE
  • Selenium RC
  • Selenium Grid
Selenium IDE

Selenium IDE is an integrated development environment to make test cases with Selenium. It is a Firefox extension and provides simple tools to build and run individual test cases or entire test suites.

Through Selenium IDE we can record the user's entire browsing sessions, which may be automatically run later. When recording by right-clicking any part of the page, Selenium IDE provides a context menu through which you can choose to add various command assertions to the test, such as how to verify the presence of a certain text or DOM element. Through the IDE, when you finish recording, you can also change all the commands automatically created to make them more specific or better for that type of testing.

Suites created with Selenium IDE can be run through the IDE itself, or through the server Selenium Remote Control.

Selenium RC

Selenium Remote Control (RC) is the Selenium server written in Java. Through this server, you can schedule and automate the execution of tests written with Selenium IDE. We can use it, for example, to integrate the test suites into a Continuous Integration System.

Besides automating the tests, server provides an API and libraries in various programming languages, including PHP, to integrate the execution of the test with other testing frameworks. PHPUnit integrates the execution of Selenium functional tests through Selenium RC.

Selenium Grid

Selenium Grid is a component that lets you scale the performance of Selenium functional tests. With this component it is possible to parallelize the execution of tests in various browsers and operating systems, across multiple instances of Selenium RC.

Installation

Selenium IDE is a plug-in for Firefox, the open-source browser developed by the Mozilla Foundation. The minimum requirement to install the plug-in is version 1.5 to 3.6.* of Firefox installed on your computer.

To install the plug-in you can:

  1. Open Firefox.
  2. Go to http://release.seleniumhq.org/selenium-ide.
  3. Enter the latest release folder.
  4. Click on the selenium-ide-*.xpi file.
  5. Confirm the installation.

Once the add-on is installed, we find a new link, “Selenium IDE,” in the Tools menu.

To install Selenium RC you must download the package from http://seleniumhq.org/download and uncompress the package. Since the server is written in Java you must have Java Virtual Machine version 1.5 or later installed on your machine. Assuming you downloaded the package on a Linux OS and uncompressed it in the path “/usr/local/selenium-rc,” to start the server you can run the following shell command:

$ java -jar /usr/local/selenium-rc/selenium-server-1.0.*/selenium-server.jar

For other operating systems follow the online documentation at http://seleniumhq.org/docs/05_selenium_rc.html#installation.

How to Record and Run Functional Tests

To write a functional test with Selenium IDE, open Firefox and click on the submenu “Selenium IDE” of the Tools menu. The IDE is opened in a contextual window already recording. From now on, all the navigation actions we perform with the browser will be registered from the IDE. To stop recording, simply click the red record button in the upper-right corner of IDE window. To resume the recording, click it once again.

image

Figure 6-1. Selenium IDE.

As we navigate the page and the IDE is recording, we can check the presence of text or DOM elements on the web page. Clicking any component of the page will open a contextual menu with suggestions for various commands to be used to add assertions to our tests.

image

Figure 6-2. Selenium IDE contextual menu.

When we finish the test session, we stop recording and save our test in HTML format, clicking the menu File -> Save Test Case As. Once saved, you can export the test for many different languages and testing frameworks, including PHPUnit for PHP.

To run the test you must click the button “Play current test case,” and the system will automatically run the same navigation session, checking elements where necessary. If something goes wrong, the IDE will notice us the number of failed tests and will underline them with red color.

image

Figure 6-3. Selenium IDE failed error.

The IDE allows you to add additional tests or modify those already entered, suggesting the commands we want to use. Selenium uses XPath to identify elements within the page. Almost all commands take as a first argument the position of elements that can be provided with the ID or the XPath address.

For the complete list of commands and for more information, please read the official documentation at http://seleniumhq.org/docs/03_selenium_ide.html.

How to Organize Selenium Tests

In large applications, it's useful to create more functional tests, divided according to some criteria relating to the application type, such as functional modules in the case of an intranet, or sections of content in the case of web sites. Selenium IDE allows us to organize our tests in suites. A suite is the description of a set of functional tests, so you can automatically run an entire group of related functional tests.

To create a suite with Selenium IDE, first we create more functional tests, save them to disk, and then save the test suite by clicking the File menu and then the “Save test suite as” button.

Now when we open the IDE, we can load the entire group of tests created by simply opening the suite. To run the full set of tests we need to click the button “Play Entire test suite.”

Both test suites and single functional tests are saved in a simple HTML format, which can be changed through our preferred IDE or text editor if needed.

Automated Test Execution with Selenium RC

With Selenium IDE it is impossible to automate testing in a Continuous Integration System, as the test execution takes place manually. To do this we can use the Selenium RC server. Through the server you can run a test suite by shell command and create a report file with the tests results.

To run a scheduled execution of our test suite it is sufficient to schedule the following command:

$ java -jar selenium-server.jar -htmlSuite "*firefox" "http://www.google.com"
"/absolute/path/to/my/HTMLSuite.html" "/absolute/path/to/my/results.html"

With this command we run the HTMLSuite.html suite in the Firefox browser with the base path www.google.com and the results are saved in the file results.html.

Selenium Conclusion

In this section we introduce the reader to the basics of writing functional tests with Selenium. For further study, refer to the official documentation, http://seleniumhq.org/docs. We will use this kind of test in the next chapter, where we will talk about "Big Refactoring" techniques for web applications.

The Best of Two Worlds

The PHPUnit testing framework integrates itself very easily with the functional testing framework Selenium through the Selenium RC server.

Selenium RC and PHPUnit

The Selenium RC server provides a set of APIs to communicate with the server with different languages. Thanks to this feature, an extension for PHPUnit to run Selenium tests into PHPUnit was created.

The integration of two frameworks allows a single framework to write unit tests and functional tests, making the maintenance of the tests very simple, and extending the basic functionality of PHPUnit.

Selenium Functional Test with PHPUnit

To write a functional test with PHPUnit Selenium, simply create a new class test that extends the class PHPUnit_Extensions_SeleniumTestCase.

<?php

require_once 'PHPUnit/Extensions/SeleniumTestCase.php';

class ApressWebTest extends PHPUnit_Extensions_SeleniumTestCase
{
  protected function setUp()
  {
    $this->setBrowser('*firefox'),
    $this->setBrowserUrl('http://www.apress.com/'),
  }

  public function testTitle()
  {

    $this->open('/'),
    $this->assertTitle('APRESS.COM | Books for Professionals, by Professionals ...'),
  }
}

?>

The PHPUnit_Extensions_SeleniumTestCase class provides all the interfaces to navigate the web pages through the browser set in the setUp() method, and all methods to verify the existence of elements or text within pages that we are viewing.

In each test we must implement the method setUp(), used to configure the browsing session. The methods we can use to configure the session are listed in Table 5-1.

Table 6-1. Methods to configure session.
Method Meaning
void setBrowser(string $browser) Set the browser to be used by the Selenium RC server.
void setBrowserUrl(string $browserUrl) Set the base URL for the tests.
void setHost(string $host) Set the hostname for the connection to the Selenium RC server.
void setPort(int $port) Set the port for the connection to the Selenium RC server.
void setTimeout(int $timeout) Set the timeout for the connection to the Selenium RC server.
void setSleep(int $seconds) Set the number of seconds the Selenium RC client should sleep between sending action commands to the Selenium RC server.

PHPUnit can also capture a screenshot when a test fails. This feature is very useful for understanding what went wrong. To activate it you must configure the test class arguments $captureScreenshotOnFailure, $screenshotPath, and $screenshotUrl.

<?php

require_once 'PHPUnit/Extensions/SeleniumTestCase.php';

class ApressWebTest extends PHPUnit_Extensions_SeleniumTestCase
{
  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.apress.com/'),
  }

  public function testTitle()
  {
    $this->open('http://www.apress.com/'),
    $this->assertTitle('APRESS.COM | Books for Professionals, by Professionals ...'),
  }
}

?>

In our tests, we can use all the commands available for Selenium to navigate, make assertions, or check elements inside our web page, because PHPUnit implements a magic method that is a proxy to all of Selenium RC API. Consult the official documentation of Selenium for a complete list of commands to run: http://seleniumhq.org/docs/04_selenese_commands.html.

Writing functional tests manually is quite a complex task with complex DOM, so we can write our tests simply with the Selenium IDE recorder feature and then export them through the IDE itself in PHPUnit format. To do it, you can click the File menu of Selenium IDE and then the “Export Test case As” -> PHPUnit button.

Running functional tests written for Selenium is as simple as running unit tests. First we check that the Selenium RC server is running; if it isn't, let's start with this command:

$ java -jar selenium-server.jar

Then run the command shell “phpunit,” passing as the first argument the folder where our tests are, or the path of the test file to run.

Summary

In this chapter we have seen the basic tools needed to do refactoring work. There are IDEs that will help us to automate tedious and repetitive tasks through their refactoring features. We also presented two important test frameworks, one to test our unit of code, named PHPUnit, and the other to test web functionalities in an agnostic way, named Selenium; they will help us to write regression tests and not lose the value of our software.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.88.165