CHAPTER 20

image

Continuous Integration

In previous chapters, you’ve seen a plethora of tools that are designed to support a well-managed project. Unit testing, documentation, build, and version control are all fantastically useful. But tools, and testing in particular, can be bothersome.

Even if your tests only take a few minutes to run, you’re often too focused on coding to bother with them. Not only that, but you have clients and colleagues waiting for new features. The temptation to keep on coding is always there. But bugs are much easier to fix close to the time they are hatched. That’s because you’re more likely to know which change caused the problem and are better able to come up with a quick fix.

In this chapter, I introduce Continuous Integration, a practice that automates test and build, and brings together the tools and techniques you’ve encountered in recent chapters.

This chapter will cover:

  • Defining Continuous Integration
  • Preparing a project for CI
  • Looking at Jenkins: a CI server
  • Customizing Jenkins for PHP projects with specialized plugins

What Is Continuous Integration?

In the bad old days, integration was something you did after you’d finished the fun stuff. It was also the stage at which you realized how much work you still had to do. Integration is the process by which all of the parts of your project are bundled up into packages that can be shipped and deployed. It’s not glamorous, and it’s actually hard.

Integration is tied up also with QA. You can’t ship a product if it isn’t fit for purpose. That means tests. Lots of tests. If you haven’t been testing much prior to the integration stage, it probably also means nasty surprises. Lots of them.

You know from Chapter 18 that it’s best practice to test early and often. You know from Chapters 15 and 19 that you should design with deployment in mind right from the start. Most of us accept that this is the ideal, but how often does the reality match up?

If you practice test-oriented development (a term I prefer to test-first development, because it better reflects the reality of most good projects I’ve seen), then the writing of tests is less hard than you might think. After all, you write tests as you code anyway. Every time you develop a component, you create code fragments, perhaps at the bottom of the class file, that instantiate objects and call their methods. If you gather up those throwaway scraps of code, written to put your component through its paces during development, you’ve got yourself a test case. Stick them into a class and add them to your suite.

Oddly, it’s often the running of tests that people avoid. Over time, tests take longer to run. Failures related to known issues creep in, making it hard to diagnose new problems. Also, you suspect someone else committed code that broke the tests, and you don’t have time to hold up your own work while you fix issues that are someone else’s fault. Better to run a couple of tests related to your work than the whole suite.

Failing to run tests, and therefore to fix the problems that they could reveal, makes issues harder and harder to address. The biggest overhead in hunting for bugs is usually the diagnosis and not the cure. Very often, a fix can be applied in a matter of minutes, set against perhaps hours searching for the reason a test failed. If a test fails within minutes or hours of a commit, however, you’re more likely to know where to look for the problem.

Software build suffers from a similar problem. If you don’t install your project often, you’re likely to find that, although everything runs fine on your development box, an installed instance falls over with an obscure error message. The longer you’ve gone between builds, the more obscure the reason for the failure will likely be to you.

It’s often something simple: an undeclared dependency upon a library on your system, or some class files you failed to check in. Easy to fix if you’re on hand. But what if a build failure occurs when you’re out the office? Whichever unlucky team member gets the job of building and releasing the project won’t know about your setup and won’t have easy access to those missing files.

Integration issues are magnified by the number of people involved in a project. You may like and respect all of your team members, but we all know that they are much more likely than you are to leave tests unrun. And then they commit a week’s work of development at 4 p.m. on Friday, just as you’re about to declare the project good to go for a release.

Continuous Integration (CI) reduces some of these problems by automating the build and test process.

CI is both a set of practices and tools. As a practice, it requires frequent commits of project code (at least daily). With each commit, tests should be run and any packages should be built. You’ve already seen some of the tools required for CI, in particular PHPUnit and Phing. Individual tools aren’t enough, however. A higher-level system is required to coordinate and automate the process.

Without the higher system, a CI server, it’s likely that the practice of CI will simply succumb to our natural tendency to skip the chores. After all, we’d rather be coding.

Having a system like this in place  offers clear benefits. First, your project gets built and tested frequently. That’s the ultimate aim and good of CI. That it’s automated, however, adds two further dimensions. The test and build happens in a different thread to that of development. It happens behind the scenes and doesn’t require that you stop work to run tests. Also, as with testing, CI encourages good design. In order for it to be possible to automate installation in a remote location, you’re forced to consider ease of installation from the start.

I don’t know how many times I’ve come across projects where the installation procedure was an arcane secret known only to a few developers. “You mean you didn’t set up the URL rewriting?” asks one old hand with barely concealed contempt. “Honestly, the rewrite rules are in the Wiki, you know. Just paste them into the Apache config file.”

Developing with CI in mind means making systems easier to test and install. This might mean a little more work up front, but it makes our lives easier down the line. Much easier.

So, to start off, I’m going to lay down some of that expensive groundwork. In fact, you’ll find that in most of the sections to come, you’ve encountered these preparatory steps already.

Preparing a Project for CI

First of all, of course, I need a project to integrate continuously. Now, I’m a lazy soul, so I’ll look for some code that comes with tests already written. The obvious candidate is the project I created in Chapter 18 to illustrate PHPUnit. I’m going to name it userthing, because it’s a thing, with a User object in it.

First of all, here is a breakdown of my project directory. See Figure 20-1.

9781430260318_Fig20-01.jpg

Figure 20-1. Part of a sample project to illustrate CI

As you can see, I’ve tidied up the structure a little, adding some package directories. Within the code, I’ve supported the package structure with the use of namespaces.

Now that I have a project, I should add it to a version control system.

CI and Version Control

Version control is essential for CI. A CI system needs to acquire the most recent version of a project without human intervention (at least once things have been set up).

I create a repository on my git server as I did in Chapter 17:

$ sudo mkdir /var/git/userthing
$ sudo chown git:git /var/git/userthing
$ sudo su git
$ cd /var/git/userthing
$ git --bare init

I created the userthing directory, and changed its owner and group. Then, as the git user, I created the repository. Now to import the simple userthing codebase from my local development machine.

$ cd /home/mattz/work/userthing
$ git init
$ git add .
$ git commit -m 'first commit'
$ git remote add origin [email protected]:/var/git/userthing
$ git push origin master

I navigated to my development directory and initialized it. Then I added the origin remote, to which I pushed the code. I like to confirm that everything is working by performing a fresh clone:

$ git clone [email protected]:/var/git/userthing
Cloning into 'userthing'...
Enter passphrase for key '/home/mattz/.ssh/id_rsa':
remote: Counting objects: 61, done.
remote: Compressing objects: 100% (56/56), done.
remote: Total 61 (delta 14), reused 0 (delta 0)
Receiving objects: 100% (61/61), 9.09 KiB, done.
Resolving deltas: 100% (14/14), done.

Now I have a userthing repository, and a local clone. Time to automate build and test.

Phing

We encountered Phing in Chapter 19. Here is how I installed the library:

$ pear channel-discover pear.phing.info
$ pear install phing/phing

I will be using this essential build tool as the glue for my project’s CI environment. I will define targets for building and testing the code and for running the various other quality assurance tools you will meet in this chapter.

image Note  Be sure to check the output of PEAR when installing a package. You may encounter error mssages that prompt you do acquire dependencies before your target can be installed. The pear application is helpful and will tell you exactly what you need to do in order to install dependencies, but it is easy to miss the fact that more work is required on your part if you don’t check the application’s commandline output.

Let’s start with the basics:

<project name="userthing" default="build">
    <property name="build" value="./build" />
    <property name="src" value="./src" />
    <property name="version" value="1.1.1" />
 
    <target name="build">
        <mkdir dir="${build}" />
        <copy todir="${build}/userthing">
            <fileset dir="${src}/userthing">
            </fileset>
        </copy>
 
        <copy todir="${build}/test">
            <fileset dir="${src}/test">
            </fileset>
        </copy>
    </target>
 
    <target name="clean">
        <delete dir="${build}" />
    </target>
    <!-- ... -->
</project>

I set up three properties. build refers to the directory in which I will assemble my files before generating a package. It’s in here that I’ll ultimately run my tests and the other tools that this chapter covers. src refers to the source directory. version defines the version number for the package.

The build target copies the userthing and test directories into the build environment. In a more complex project I might also perform transformations, generate configuration files, and assemble binary assets at this stage. This target is the project’s default.

The clean target removes the build directory and anything it contains.

Let’s run a build:

$ phing
Buildfile: /home/mattz/work/userthing/build.xml
userthing > build:
    [mkdir] Created dir: /home/mattz/work/userthing/build
     [copy] Created 4 empty directories in /home/mattz/work/userthing/build/userthing
     [copy] Copying 3 files to /home/mattz/work/userthing/build/userthing
     [copy] Created 1 empty directory in /home/mattz/work/userthing/build/test
     [copy] Copying 2 files to /home/mattz/work/userthing/build/test
 
BUILD FINISHED
 
Total time: 0.7534 seconds

Unit Tests

Unit tests are the key to continuous integration. It’s no good successfully building a project that contains broken code. I covered unit testing with PHPUnit in Chapter 18. If you’re reading out of order, however, you’ll want to install this invaluable tool before proceeding.

$ pear config-set auto_discover 1
$ pear install --alldeps pear.phpunit.de/phpunit

Also in Chapter 18 I wrote tests for a version of the userthing code I’ll be working with in this chapter. Here I run them once again (from within the src directory), to make sure my reorganization has not broken anything new.

$ phpunit test/ 3.7.24 by Sebastian Bergmann.
 
.....
 
Time: 317 ms, Memory: 3.75Mb
 
OK (5 tests, 5 assertions)

So this confirms that my tests work. However, I would like to invoke them with Phing.

Phing provides an exec task which we might use to invoke the phpunit command. However, it’s always best to use a specialized tool if there’s one available.  There is a builtin task for this job:

<target name="test" depends="build">
        <phpunit>
            <formatter type="plain" usefile="false"/>
            <batchtest classpath="${build}">
                <fileset dir="${build}/test">
                    <include name="**/*Test.php"/>
                </fileset>
            </batchtest>
        </phpunit>
    </target>

The test target depends upon the build target. Because of this, it can be sure that there’s an environment in place under the build directory with which to work. Among many other attributes, the phpunit task accepts a printsummary attribute, which causes an overview of the test process to be output.

Much of this task’s functionality is configured using nested elements. The formatter element manages the way that test information is generated. In this case, I have opted to output basic human-readable data. batchtest lets you define multiple test files using the nested fileset element. Note that batchtest accepts a classpath attribute. This sets the include path for your tests (the name derives from a Java term).

image Note  The phpunit task is highly configurable. The Phing manual provides full documentation at http://www.phing.info/docs/guide/stable/chapters/appendixes/AppendixC-OptionalTasks.html#PHPUnitTask.

Here, I run the tests with Phing:

$ phing test
Buildfile: /home/mattz/work/userthing/build.xml
userthing > build:
userthing > test:
 
  [phpunit] Testsuite: ValidatorTest
 
  [phpunit] Tests run: 2, Failures: 0, Errors: 0, Incomplete: 0, Skipped: 0, Time elapsed: 0.07081 s
  [phpunit] Testsuite: UserStoreTest
  [phpunit] Tests run: 3, Failures: 0, Errors: 0, Incomplete: 0, Skipped: 0, Time elapsed: 0.02879 s
  [phpunit] Total tests run: 5, Failures: 0, Errors: 0, Incomplete: 0, Skipped: 0, Time elapsed: 0.10772 s
 
BUILD FINISHED
 
Total time: 1.0921 second

Documentation

Transparency is one of the principles of CI. When you’re looking at a build in a Continuous Integration environment, therefore, it’s important to be able to check that the documentation is up to date, and covers the most recent classes and methods. I examined phpDocumentor in Chapter 16, so I’ve already run an install like this:

$ pear channel-discover pear.phpdoc.org
$ pear install phpdoc/phpdocumentor

I’d better invoke the tool just to be sure, this time from the build directory:

$ mkdir docs
$ phpdoc --directory=userthing --target=docs --title=userthing --template=abstract

This generates some pretty bare documentation. Once it's published on a CI server, I'm sure to be shamed into writing some real inline documentation.

Once again, I want to add this to my build.xml document. Although there is a task named phpdoc2 that is designed to integrate with PHPDocumentor, at the time of this writing this wrapper is broken. Never mind, however; I can use the exec task to invoke the command line phpdoc tool:

    <target name="doc" depends="build">
        <mkdir dir="${build}/docs" />
        <exec executable="phpdoc" dir="${build}">
          <arg line="--directory=userthing --target=docs --title=userthing --template=abstract" />
        </exec>
    </target>

Again, my doc target depends upon the build target. I create the build/docs output directory, and then invoke the exec task. I specify phpdoc in the executable attribute. The dir attribute defines the directory from which the command is to be run. exec accepts a nested arg element, which will pass any further arguments to the command. Where there is a single argument , you can set the arg element’s value attribute. In this case, however, I have a list of arguments and the line attribute, which accepts multiple values separated by spaces, is more appropriate.

image Note  The full documentation for the exec task is available at http://www.phing.info/docs/guide/stable/chapters/appendixes/AppendixB-CoreTasks.html#ExecTask.

image Note  It’s possible that, by the time you read this, the phpdoc2 task will once again work with PHPDocumentor. You can find the task documented at http://www.phing.info/docs/guide/stable/chapters/appendixes/AppendixC-OptionalTasks.html#PhpDocumentor2Task.

Code Coverage

It’s no good relying on tests if they don’t apply to the code you have written. PHPUnit includes the ability to report on code coverage. Here’s an extract from PHPUnit’s usage information.

  --coverage-html <dir>    Generate code coverage report in HTML format.
  --coverage-clover <file> Write code coverage data in Clover XML format.

In order to use this feature, you must have the Xdebug extension installed. You can find more about this at http://pecl.php.net/package/Xdebug (installation information at http://xdebug.org/docs/install). You may also be able to install directly using your Linux distribution’s package management system. This should work for you in Fedora, for example:

$ yum install php-pecl-xdebug

Here I run PHPUnit from the src/ directory with code coverage enabled:

$ mkdir /tmp/coverage
$ phpunit --coverage-html /tmp/coverage test
 
PHPUnit 3.7.24 by Sebastian Bergmann.
 
.....
 
Time: 3.36 seconds, Memory: 4.75Mb
 
OK (5 tests, 5 assertions)
Generating code coverage report in HTML format ... done

9781430260318_Fig20-02.jpg

Figure 20-2. The code coverage report

Now you can see the report in your browser. See Figure 20-2.

It’s important to note that achieving full coverage is not the same as adequately testing a system. On the other hand, it’s good to know about any gaps in your tests. As you can see from Figure 20-2, I’ve still got some work to do.

Having confirmed that I can check coverage from the commandline, I need to add this functionality to my build document:

<target name="citest" depends="build">
    <mkdir dir="${build}/reports/coverage" />
 
    <coverage-setup database="${build}/reports/coverage.db">
        <fileset dir="${build}/userthing">
            <include name="**/*.php"/>
        </fileset>
    </coverage-setup>
 
        <phpunit codecoverage="true">
            <formatter type="plain" usefile="false"/>
            <formatter type="xml" outfile="testreport.xml" todir="${build}/reports" />
            <formatter type="clover" outfile="cloverreport.xml" todir="${build}/reports" />
            <batchtest classpath="${build}">
                <fileset dir="${build}/">
                    <include name="test/**"/>
                </fileset>
            </batchtest>
        </phpunit>
 
        <coverage-report outfile="${build}/reports/coverage.xml">
           <report todir="${build}/reports/coverage" />
        </coverage-report>
    </target>

I have created a new task named citest. Much of it is a reproduction of the test task you have already seen.

I start by creating a reports directory and a coverage subdirectory.

I use the coverage-setup task to provide configuration information for the coverage feature. I specify where raw coverage data should be stored using the database attribute. The nested fileset element defines the files that should be subject to coverage analysis.

I have added two formatter elements to the phpunit task. The formatter of type xml will generate a file named testreport.xml, which will contain the test results. The clover formatter will generate coverage information, also in XML format.

Finally, in the citest target, I deploy the coverage-report task. This takes existing coverage information, generates a new XML file and then outputs an HTML report.

Coding Standards

I can argue all day about the best place to put a brace, whether to indent with tabs or spaces, or how to name a private property variable. Wouldn’t it be nice if I could enforce my prejudices with a tool? Thanks to PHP_CodeSniffer, I can. CodeSniffer can apply one of a set of coding standards to a project and generate a report, telling you just how bad your style is.

That might sound like a massive pain in the rear end. In fact, it can be just that. But there are sensible nonpassive aggressive uses for a tool like this. I’ll get to these, but first I’ll put the tool through its paces. Installation first:

$ sudo pear install PHP_CodeSniffer

Now I’m going to apply the Zend coding standard to my code:

$ phpcs --standard=Zend build/userthing/persist/UserStore.php
FILE: ...userthing/build/userthing/persist/UserStore.php
--------------------------------------------------------------------------------
 
FOUND 9 ERROR(S) AFFECTING 8 LINE(S)
--------------------------------------------------------------------------------
  6 | ERROR | Opening brace of a class must be on the line after the definition
  7 | ERROR | Private member variable "users" must contain a leading underscore
  9 | ERROR | Opening brace should be on a new line
 13 | ERROR | Closing parenthesis of a multi-line function call must be on a
    |       | line by itself
 ...

image Note  Wes Hunt, who is the technical reviewer of this edition, reports some problems installing CodeSniffer on Windows:  “Pear ... thought it was already installed. I had to clear out C:Users{username}AppDataLocalTemppearcache. Then CodeSniffer installed fine.”

Clearly, I’d have to adjust my style to submit code to Zend!

It makes sense however, for a team to define coding guidelines. In fact, the decision as to which set of rules you choose is probably less important than the decision to abide by a common standard in the first place. If a codebase is consistent, then it’s easier to read, and therefore easier to work with. Naming conventions, for example, can help to clarify the purpose of variables or properties.

Coding conventions can play a role in reducing risky or bug-prone code as well.

This is a dangerous area, though. Some style decisions are highly subjective, and people can be disproportionately defensive about their way of doing things. CodeSniffer allows you to define your own rules, so I suggest that you get buy-in from your team on a set of rules so that no one feels that their coding life has become a coding nightmare.

Another benefit of an automated tool is its impersonal nature. If your team does decide to impose a set of coding conventions, it’s arguably better having a humorless script correcting your style than a humorless coworker doing the same thing.

As you might expect by now, I would like to add a CodeSniffer target to my build file:

<target name="sniff" depends="build">
        <mkdir dir="${build}/reports" />
        <phpcodesniffer standard="Zend">
            <fileset dir="${build}/userthing">
                <include name="**/*.php"/>
            </fileset>
            <formatter type="checkstyle" outfile="${build}/reports/checkstyle.xml"/>
            <formatter type="default" usefile="false"/>
        </phpcodesniffer>
    </target>

The phpcodesniffer task will do this job for me. I use the standard attribute to specify Zend rules. I define the files to check using the nested fileset element. I define two formatter elements. The first, with a checkstyle type attribute, will generate an XML file in the reports directory. The second, with  a type of default, outputs summary information to the commandline.

image Note  You may wish to define your own rules for codesniffer. There is a guide for this at the PEAR website: http://pear.php.net/manual/en/package.php.php-codesniffer.coding-standard-tutorial.php.

Building a package

Having tested the codebase, and run various reports on it, I should also check that I can build a package.

In this case I will generate a PEAR package using the pearpkg2 task:

<target name="makepackagefile" depends="build">
    <pearpkg2 name="userthing" dir="${build}">
       <option name="packagefile" value="userthing_package.xml"/>
       <option name="packagedirectory" value="${build}"/>
       <option name="baseinstalldir" value="/"/>
       <option name="channel" value="pear.php.net"/>
       <option name="summary" value="blah blah"/>
       <option name="description" value="blah blah blah"/>
       <option name="apiversion" value="1.1.0"/>
       <option name="apistability" value="beta"/>
       <option name="releaseversion" value="${version}"/>
       <option name="releasestability" value="beta"/>
       <option name="license" value="none"/>
       <option name="phpdep" value="5.4.0"/>
       <option name="pearinstallerdep" value="1.4.6"/>
       <option name="packagetype" value="php"/>
       <option name="notes" value="notes notes notes"/>
       <mapping name="maintainers">
           <element>
               <element key="handle" value="mattz"/>
               <element key="name" value="matt"/>
               <element key="email" value="[email protected]"/>
               <element key="role" value="lead"/>
           </element>
       </mapping>
       <fileset dir="${build}">
       <include name="userthing/**" />
       </fileset>
    </pearpkg2>
</target>

The various elements in this should be pretty self-explanatory, especially with reference to the package file structure that you encountered in Chapter 15. Note that I use the version property that I created at the head of the build file to set the value of the releaseversion option element. Also note that I specify the files to include in the package using a fileset element.

Thanks to the packagefile option, the makepackagefile target will generate a file named userthing_package.xml. In order to transform this into an actual package I need to resort to exec again:

<target name="buildpackage" depends="makepackagefile">
    <exec dir="build" checkreturn="true"  executable="/usr/bin/pear">
        <arg value="package" />
        <arg value="userthing_package.xml" />
    </exec>
</target>

This use of exec is equivalent to typing:

pear package userthing_package.xml

from within the build directory. The result is an archive named userthing-1.1.1.tgz.

image Note  You can also create a package archive using the TarTask, which is documented at http://www.phing.info/docs/guide/stable/chapters/appendixes/AppendixC-OptionalTasks.html#TarTask.

Notice that I used an attribute named checkreturn within the exec element. This directive causes Phing to alert you if the command you are calling fails to return a successful status code. Without this attribute, exec could fail silently if, for example, the pear executable is not found in the path I specified. Let’s run the buildpackage target:

$ phing buildpackage
Buildfile: /home/mattz/work/userthing/build.xml
 
userthing > build:
 
userthing > makepackagefile:
 
 [pearpkg2] Creating [default] package.xml file in base directory.
Analyzing userthing/domain/User.php
Analyzing userthing/persist/UserStore.php
Analyzing userthing/util/Validator.php
userthing > buildpackage:
 
BUILD FINISHED
 
Total time: 1.7685 second

Let’s test the package by deploying it:

$ pear install --force build/userthing-1.1.1.tgz

So I have a lot of useful tools that I can use to monitor my project. Of course, left to myself I’d soon lose interest in running them, even with my useful Phing build file. In fact, I’d probably revert to the old idea of an integration phase, and pull out the tools only when I’m close to a release, by which time their effectiveness as early warning systems will be irrelevant. What I need is a CI server to run the tools for me.

Jenkins

Jenkins (formerly named Hudson) is an open source continuous integration server. Although it is written in Java, Jenkins is easy to use with PHP tools. That’s because the continuous integration server stands outside of the projects it builds, kicking off and monitoring the results of various commands. Jenkins also integrates well with PHP because it is designed to support plugins, and there is a highly active developer community working to extend the server’s core functionality.

image Note  Why Jenkins? Jenkins very easy to use and extend. It is well established, and it has an active user community. It’s free and open source. Plugins that support integration with PHP (and most build and test tools you might think of) are available. There are many CI server solutions out there, however. A previous version of this book focused on CruiseControl (http://cruisecontrol.sourceforge.net/ ), and this remains a good option. If you’re looking for a native PHP implementation, you should definitely take a look at Xinc (http://code.google.com/p/xinc/).

Installing Jenkins

Jenkins is a Java system, so you will need to have Java installed. How you go about this will vary from system to system. On a Fedora distribution you might do something like: 
 
$ yum install java

Otherwise, you can get Java directly from www.java.com.

You can confirm that you have java by running it from the commandline:

$ java -version
java version "1.7.0_60"
OpenJDK Runtime Environment (fedora-2.4.2.0.fc19-x86_64)
OpenJDK 64-Bit Server VM (build 24.0-b56, mixed mode)

You can get Jenkins from the project homepage at http://jenkins-ci.org/. It can be installed via a Java Web Archive (WAR) file, or there are native packages for most distributions, all linked from the homepage. I will use the Fedora option:

$ wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
$ rpm --importhttp://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
$ yum install jenkins

The Jenkins site provides installation instructions for most distributions.

Once Jenkins is installed you could run it directly via java like this:

$ sudo java -jar /usr/lib/jenkins/jenkins.war

However, you may run in to trouble later on if you do this. It’s almost certainly better to use a startup script, which will run Jenkins as the jenkins user. In the case of Fedora you can start Jenkins like this:

$ service jenkins start

You can also find a generic startup script at https://wiki.jenkins-ci.org/display/JENKINS/JenkinsLinuxStartupScript.

By default, Jenkins runs on port 8080.

You can find out whether you’re ready to proceed by firing up your browser and visiting http://yourhost:8080/. You should see something like the screen in Figure 20-3.

9781430260318_Fig20-03.jpg

Figure 20-3. The Jenkins Dashboard screen

Installing Jenkins Plugins

Jenkins is highly customizable, and I will need quite a few plugins to integrate with the features I have described so far in this chapter. From within the Jenkins web interface I click on Manage Jenkins and then Manage Plugins. Beneath the Available tab I find a long list. I select the checkboxes in the Install column for all plugins that I wish to add to Jenkins.

Table 20-1 describes the plugins that I will be using.

Table 20-1. Some Jenkins Plugins

Plugin

Description

Git Plugin Allows interation with Git repositories
xUnit Plugin Integration with the xUnit family of tools including PHPUnit
Phing Plugin Invoke Phing targets
Clover PHP Plugin Accesses clover XML file  and HTML files generated by PHPUnit and generates report
HTML Publisher Plugin Integrates HTML reports. Used for PHPDocumentor output
Checkstyle Plugin Accesses the XML file generated by PHPCodeSniffer and generates report

You can see the Jenkins plugin page in Figure 20-4.

9781430260318_Fig20-04.jpg

Figure 20-4. The Jenkins Plugin screen

Having installed these plugins, I’m almost ready to create and configure my project.

Setting up the Git Public Key

Before I can use the Git plugin, I need to ensure that I have access to a git repository. In Chapter 17 I described the process of generating a public key in order to access a remote Git repository. We need to repeat this process here. But where does Jenkins call home?

This location is configurable, but naturally Jenkins will clue you in. I Click on Configure Jenkins and then Configure System. I find Jenkins’ home directory listed there. Of course, I could also check the /etc/passwd file for information relating to the jenkins user. In my case, the directory is /var/lib/jenkins.

Now to configure an ssh directory:

$ sudo su jenkins -s /bin/bash
$ cd ∼
$ mkdir .ssh
$ chmod 0700 .ssh
$ ssh-keygen

I switch to the jenkins user, specifying the shell to use (because shell access may be deactivated by default). I change to this user’s home directory. The ssh-keygen command generates the ssh keys. When prompted for a password, I just hit return, so Jenkins will be authenticated by its key only. I make sure that the file generated at .ssh/id_rsa is neither world- nor group-readable:

$ chmod 0600 .ssh/id_rsa

Now I can acquire the public key from .ssh/id_rsa.pub and add it to my remote Git repository. See Chapter 17 for more on that.

I’m not quite there yet. I need to ensure that my Git server is an ssh known host. I can combine setting this up with a commandline test of my Git configuration. I make sure I’m still logged in as the jenkins user when you do this:

$ cd /tmp
$ git clone [email protected] :/var/git/userthing

I am prompted to confirm my Git host, and it’s then added to the jenkins user’s .ssh/known_hosts file. This prevents Jenkins from tripping over later when it makes its Git connection.

Installing a Project

From the Jenkins dashboard page, I click on create new jobs. From this new screen I can, at last, create my userthing project. You can see the setup screen in Figure 20-5.

9781430260318_Fig20-05.jpg

Figure 20-5. The Project Setup Screen

That leads me to the project configuration screen. My first order of business is to link up with the remote Git repository. I choose the Git radio button in the Source Code Manager section and add my repository. You can see this in Figure 20-6.

9781430260318_Fig20-06.jpg

Figure 20-6. Setting up the Version Control Repository

If all has gone well, I should be able to access my source code. In order to test that fully, however, I should also set up Phing. I can do this by choosing Invoke Phing targets from the Add build step pulldown. Then I add my Phing targets to the text field. You can see this step in Figure 20-7.

9781430260318_Fig20-07.jpg

Figure 20-7. Configuring Phing

Running the First Build

I save the configuration screen and click Build Now to run the build and test process. This is the moment of truth! A build link should appear in the Build History area of the screen. I click on that, and then on Console Output to confirm that the build went ahead as hoped. You can see this output in Figure 20-8.

9781430260318_Fig20-08.jpg

Figure 20-8. Console Output

Success! Jenkins has checked the userthing code out from the Git server, and run all the build and test targets.

Configuring the Reports

Thanks to my build file, Phing saves reports into the build/reports directory and documentation into build/docs. The plugins that I activated can be configured from the Add post-build action pulldown in the project configuration screen.

Figure 20-9 shows some of these configuration items.

9781430260318_Fig20-09.jpg

Figure 20-9. Configuring Report Plugin Items

Rather than subject you to screenshot after screenshot, it will be clearer to compress the configuration items into a table. Table 20-2 shows some post-build action fields and the corresponding values I set up in my Phing build file.

Table 20-2. Report configuration

image

You encountered all of the configuration values in Table 20-2 as I constructed the project’s build file. All, that is, apart from the last. The E-mail Notification field allows you to define a list of developers who will all receive notifications when a build fails.

With all that set up, I can return to the project screen and run another build. Figure 20-10 shows my newly enhanced output.

9781430260318_Fig20-10.jpg

Figure 20-10. The Project Screen Showing Trend Information

Over time, the project screen will plot trends for test performance, coverage, and style compliance. There are also links to the latest API Documentation, detailed test results, and full coverage information.

Triggering Builds

All of this rich information is almost useless if someone in your team must remember to kick off each build with a manual click. Naturally, Jenkins provides mechanisms by which builds can be automatically triggered.

You can set Jenkins to build at fixed intervals or to poll the version control repository, again at specified intervals. Intervals can be set using cron format, which provides fine, although somewhat arcane, control over scheduling. Luckily, Jenkins provides good online help for the format, and there are simple aliases if you don’t require precision scheduling. The aliases include @hourly, @midnight, @daily, @weekly, and @monthly. In Figure 20-11, I configure the build to run once daily, or every time the repository changes, based upon a poll for changes that should take place once an hour.

9781430260318_Fig20-11.jpg

Figure 20-11. Scheduling Builds

Test Failures

So far everything seems to be going well, even if userthing won’t be finding its way into the Zend codebase any time soon. But tests succeed when they fail, so I’d better break something to make sure that Jenkins reports on it.

Here is a part of a class named Validate in the namespace userthingutil:

public function validateUser( $mail, $pass ) {
    // make it always fail!
    return false;
 
    $user = $this->store->getUser( $mail );
    if ( is_null( $user ) ) {
        return null;
    }
 
    if ( $user->getPass() == $pass ) {
        return true;
    }
    $this->store->notifyPasswordFailure( $mail );
    return false;
}

See how I’ve sabotaged the method? As it now stands, validateUser() will always return false.

Here’s the test that should choke on that. It’s in test/ValidatorTest.php:

public function testValidate_CorrectPass() {
    $this->assertTrue(
        $this->validator->validateUser( "[email protected]", "12345" ),
        "Expecting successful validation"
         );
}

Having made my change, all I need do is commit, and wait. Sure enough, before long, the project status shows a build marked by an alarming red icon. Once I click on the build link, I find more details. You can see the screen in Figure 20-12.

9781430260318_Fig20-12.jpg

Figure 20-12. The failed build

I also receive a mail with the subject line Build failed in Jenkins: userthing #8. This provides information about my latest commit, the build process, and my test failures.

Summary

In this chapter, I brought together many of the tools that you have seen in previous chapters and glued them in place with Jenkins. I prepared a small project for CI, applying a range of tools including PHPUnit (both for testing and code coverage), PHP_CodeSniffer, PHP_CodeBrowser, phpDocumentor, and Git. Then I set up Jenkins and showed you how to add a project to the system. I put the system through its paces and, finally, showed you how to extend Jenkins so that it can bug you with e-mails, and test both build and installation.

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

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