News Archive
PhpRiot Newsletter
Your Email Address:

More information

Integration Testing

Note: This article was originally published at Planet PHP on 8 December 2011.
Planet PHP

At some point in our respective programming careers, most of us have heard that Test-Driven Development (TDD) is what we should be doing.

The TDD thesis is that we write tests (known as unit tests) that consume the functions, classes, and interfaces of the code that we intend to write before we write it, and that doing this improves the quality of our code due to increased foresight and better ability to catch a specific type of bug known as a regression, which is the technical term your boss uses for something that worked fine before you added that last bit of code.

The gist of TDD is:

  • Each test should test one unit of code.
  • Tests should not cross process boundaries in a program.
  • Tests should not establish network connections.

If you're writing tests that don't follow these rules, tsk tsk, you're doing unit testing wrong! The mantra is that instead of doing this, you need to tease apart the code so that you can introduce mocks and fake access to those other components.

While I'm on board with the idea that we should be writing code with testing in mind, it is all too easy to get swept away with the purist TDD approach, anda-abefore you know ita-ayou're not actually writing code anymore; you're thinking about how to change your real code so that you can write fake code to test your real code. (As a humorous semi-diversion, getting lost in JUnit-derived test frameworks just makes me think of building a spice rack.)

The problem I see with this approach, especially if you're retrofitting test facilities to a project, is that the cost of implementing all the mocks and fakes can be prohibitive, and it may not even be an effective use of time! Are your users going to run your fake code? What if there's a bug in your fake code that doesn't match up to the real world? Will your tests save you from that problem?

Integration Testing

There's another type of test known as an integration test that describes testing the ability of your program to talk to other modules, databases, and services. The idea is that your test suite starts up instances of its own services (web server, database, &c.) for the test programs to execute against.

Running its own services guarantees that everyone who runs the tests are running with a consistent environment; this is how you eliminate the aoworks for mea class of problem.

I've recently started to build up a test suite for Mtrack (yes, it is long overdue) that uses this approach. Mtrack has a couple of key interfaces:

  • Web UI (PHP, with a bit of Backbone.js for the frontend)
  • SSH access to repositories

It also relies on a couple of services, depending on how it has been configured:

  • Zend Framework Lucene Full Text search index
  • Solr Full Text search index
  • PostgreSQL database
  • SQLite database

A driver script called runtests sets up an Apache web server, initializes a SQLite database, starts Solr, starts an SSH server, and launches the Selenium server. It then runs the Perl prove tool to execute the test programs.

I tend to use a Test::More-derived test framework in my integration tests (because we work with Perl and other systems a lot); you can certainly use PHPUnit if you're more comfortable with that. I like the Test::More style, because it feels very lightweight.

With the services up, we can then write tests that exercise our code via the REST API; these tests can use PHP's streams or cURL functions to talk to the web server. The test driver for Mtrack exports the web server port number to an environment variable, so the test program knows which server it should talk to.

The Mtrack REST API tests look something like this (simplified for the sake of this article):

'my test ticket', 'description' = 'the body of the ticket')); if (!is($st, 200, 'created ticket')) { // Failed; this is not something we expect, so dump // out the body. diag($body); }

Truncated by Planet PHP, read more at the original (another 3359 bytes)