Prev Next

Chapter 19. Continuous Integration

 

Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily, leading to multiple integrations per day. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible. Many teams find that this approach leads to significantly reduced integration problems and allows a team to develop cohesive software more rapidly.

 
  --Martin Fowler

This chapter provides an overview of Continuous Integration summarizing the technique and its application with PHPUnit.

CruiseControl

Continuous Integration demands a fully automated and reproducible build, including testing, that runs many times a day. This allows each developer to integrate daily thus reducing integration problems. While this can be achieved by setting up a cronjob that makes a fresh checkout from the project's source code repository at regular intervals, runs the tests, and publishes the results a more comfortable solution may be desired.

This is where a framework for continuous build processes such as CruiseControl comes into play. It includes, but is not limited to, plugins for email notification, Apache Ant, and various source control tools. A web interface is provided to view the details of the current and previous builds.

The following example assumes that CruiseControl has been installed into /usr/local/cruisecontrol.

  1. cd /usr/local/cruisecontrol
    
  2. mkdir -p projects/BankAccount/build/logs
    
  3. cd projects/BankAccount
    
  4. svn co svn://svn.phpunit.de/phpunit/phpunit/branches/release/3.2/PHPUnit/Samples/BankAccount source
    
  5. Edit the build.xml file.

Example 19.1: projects/BankAccount/build.xml

<project name="BankAccount" default="build" basedir=".">
 <target name="checkout">
  <exec dir="${basedir}/source/" executable="svn">
   <arg line="up"/>
  </exec>
 </target>

 <target name="test">
  <exec dir="${basedir}/source" executable="phpunit" failonerror="true">
   <arg line="--log-xml ${basedir}/build/logs/bankaccount.xml BankAccountTest"/>
  </exec>
 </target>

 <target name="build" depends="checkout,test"/>
</project>

  1. cd /usr/local/cruisecontrol
    
  2. Edit the config.xml file.

Example 19.2: config.xml

<cruisecontrol>
  <project name="BankAccount" buildafterfailed="false">
    <plugin
    name="svnbootstrapper"
    classname="net.sourceforge.cruisecontrol.bootstrappers.SVNBootstrapper"/>
    <plugin
    name="svn"
    classname="net.sourceforge.cruisecontrol.sourcecontrols.SVN"/>

    <listeners>
      <currentbuildstatuslistener file="logs/${project.name}/status.txt"/>
    </listeners>

    <bootstrappers>
      <svnbootstrapper localWorkingCopy="projects/${project.name}/source/"/>
    </bootstrappers>

    <modificationset>
      <svn localWorkingCopy="projects/${project.name}/source/"/>
    </modificationset>

    <schedule interval="300">
      <ant
      anthome="apache-ant-1.7.0"
      buildfile="projects/${project.name}/build.xml"/>
    </schedule>

    <log dir="logs/${project.name}">
      <merge dir="projects/${project.name}/build/logs/"/>
    </log>

    <publishers>
      <currentbuildstatuspublisher
      file="logs/${project.name}/buildstatus.txt"/>

      <email
      mailhost="localhost"
      buildresultsurl="http://cruise.example.com/buildresults/${project.name}"
      skipusers="true"
      spamwhilebroken="true"
      returnaddress="project@example.com">
        <failure address="dev@lists.example.com" reportWhenFixed="true"/>
      </email>
    </publishers>
  </project>
</cruisecontrol>

Now we can (re)start the CruiseControl server.

  1. ./cruisecontrol.sh
    
  2. Open http://localhost:8080/ in your webbrowser.

phpUnderControl

phpUnderControl is an extension for CruiseControl that integrates several PHP development tools, such as PHPUnit for testing, PHP_CodeSniffer for static code analysis, and PHPDocumentor for API documentation generation. It comes with a powerful command-line tool that can, among other things, automatically create CruiseControl's XML configuration files for your project. The following example assumes that CruiseControl has been installed into /usr/local/cruisecontrol.

  1. pear install --alldeps phpunit/phpUnderControl
    
  2. phpuc install /usr/local/cruisecontrol
    
  3. phpuc project --version-control svn
                  --version-control-url svn://svn.phpunit.de/phpunit/phpunit/branches/release/3.3/PHPUnit/Samples/BankAccount
                  --test-case BankAccountTest
                  --test-file BankAccountTest.php
                  --test-dir .
                  --project-name BankAccount
                  /usr/local/cruisecontrol
    

The above command creates the project directory and the project's build.xml configuration file, performs the initial checkout from the source repository, and adds the new project to the global config.xml configuration file. Now we can (re)start the CruiseControl server.

  1. cd /usr/local/cruisecontrol
    
  2. ./cruisecontrol.sh
    
  3. Open http://localhost:8080/ in your webbrowser.

Apache Maven

Apache Maven is a software project management and comprehension tool. Based on the concept of a Project Object Model (POM), Apache Maven can manage a project's build, reporting and documentation from a central place of information.

The single XML logfile that PHPUnit's XML logging facility (see the section called “XML Format”) generates needs to be split into separate XML logfiles, one for each test suite, before it can be processed by Apache Maven's surefire plugin. This plugin is used during the test phase of the build lifecycle to execute the unit tests of an application. Example 19.4 shows an XSLT stylesheet that performs this splitting. Example 19.3 shows an example pom.xml configuration file.

Example 19.3: pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
  http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <!-- ... -->
  <prerequisites>
    <maven>2.0.7</maven>
  </prerequisites>
  <!-- ... -->
  <build>
  <!-- ... -->
    <plugins>
      <plugin>
      <dependencies>
        <dependency>
          <groupId>org.apache.ant</groupId>
          <artifactId>ant-trax</artifactId>
          <version>1.7.0</version>
        </dependency>
        <dependency>
          <groupId>net.sf.saxon</groupId>
          <artifactId>saxon</artifactId>
          <version>8.7</version>
        </dependency>
      </dependencies>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-antrun-plugin</artifactId>
      <version>1.2-SNAPSHOT</version>
      <executions>
        <execution>
          <id>codecoverage</id>
          <phase>pre-site</phase>
          <goals>
            <goal>run</goal>
          </goals>
          <configuration>
            <tasks>
              <property name="phpunit.codecoverage"
                        location="${project.reporting.outputDirectory}/phpunit/codecoverage" />
              <property name="surefire.reports"
                        location="${project.build.directory}/surefire-reports" />

              <mkdir dir="${phpunit.codecoverage}"/>
              <mkdir dir="${surefire.reports}"/>

              <!-- ${ant.phpunit} path to PHPUnit executable -->
              <!-- ${ant.pear};... this is the include path for your test execution -->
              <!-- ${test.AllTests} PHPUnit cmd line args like 'AllTests de/dmc/dashboard/AllTests.php' -->
              <exec executable="${ant.phpunit}" dir="${basedir}">
                <arg line="-d include_path=${ant.pear};${project.build.sourceDirectory};${project.build.testSourceDirectory}
                           --report ${phpunit.codecoverage} ${test.AllTests}" />
              </exec>

              <xslt in="${phpunit.codecoverage}/logfile.xml"
                    out="${surefire.reports}/xslt.info"
                    style="src/test/config/phpunit_to_surefire.xslt"
                    processor="trax">
                <!-- this is the output folder for surefire like XML Reports -->
                <param name="outputDir" expression="${surefire.reports}"/>
              </xslt>
            </tasks>
          </configuration>
        </execution>
      </executions>
      </plugin>
    </plugins>
  </build>
  <reporting>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-report-plugin</artifactId>
        <version>2.4-SNAPSHOT</version>
        <reportSets>
          <reportSet>
            <reports>
              <report>report-only</report>
            </reports>
          </reportSet>
        </reportSets>
      </plugin>
    </plugins>
  </reporting>
</project>

Example 19.4: phpunit_to_surefire.xsl

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:fn="http://www.w3.org/2005/xpath-functions">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:param name="outputDir">.</xsl:param>

  <xsl:template match="testsuites">
    <xsl:apply-templates select="testsuite"/>
  </xsl:template>

  <xsl:template match="testsuite">
    <xsl:if test="testcase">
      <xsl:variable name="outputName" select="./@name"/>
      <xsl:result-document href="file:///{$outputDir}/{$outputName}.xml" method="xml">
        <xsl:copy-of select="."/>
      </xsl:result-document>
    </xsl:if>

    <xsl:apply-templates select="testsuite"/>
  </xsl:template>
</xsl:stylesheet>


Prev Next
1. Automating Tests
2. PHPUnit's Goals
3. Installing PHPUnit
4. Writing Tests for PHPUnit
Data Providers
Testing Exceptions
Testing PHP Errors
5. The Command-Line Test Runner
6. Fixtures
More setUp() than tearDown()
Variations
Sharing Fixture
7. Organizing Test Suites
Suite-Level Setup
8. TestCase Extensions
Testing Output
Testing Performance
9. Database Testing
Datasets
Flat XML Data Set
XML Data Set
Operations
Database Testing Best Practices
10. Incomplete and Skipped Tests
Incomplete Tests
Skipping Tests
11. Mock Objects
Self-Shunting
Stubs
12. Testing Practices
During Development
During Debugging
13. Test-First Programming
BankAccount Example
14. Code Coverage Analysis
Specifying Covered Methods
Ignoring Code Blocks
Including and Excluding Files
15. Other Uses for Tests
Agile Documentation
Cross-Team Tests
16. Logging
XML Format
Code Coverage (XML)
JavaScript Object Notation (JSON)
Test Anything Protocol (TAP)
GraphViz Markup
Test Database
17. Skeleton Generator
Annotations
18. PHPUnit and Selenium
Selenium RC
PHPUnit_Extensions_SeleniumTestCase
19. Continuous Integration
CruiseControl
phpUnderControl
Apache Maven
20. PHPUnit's Implementation
21. PHPUnit API
Overview
PHPUnit_Framework_Assert
PHPUnit_Framework_Test
PHPUnit_Framework_TestCase
PHPUnit_Framework_TestSuite
PHPUnit_Framework_TestResult
Package Structure
22. Extending PHPUnit
Subclass PHPUnit_Framework_TestCase
Assert Classes
Subclass PHPUnit_Extensions_TestDecorator
Implement PHPUnit_Framework_Test
Subclass PHPUnit_Framework_TestResult
Implement PHPUnit_Framework_TestListener
New Test Runner
A. Assertions
B. The XML Configuration File
Test Suite
Groups
Including and Excluding Files for Code Coverage
Logging
PMD Rules
Setting PHP INI settings and Global Variables
C. PHPUnit for PHP 4
D. Index
E. Bibliography
F. Copyright