Let’s start with a confession: you don’t write tests. You know you should, but you don’t. I can’t fault you for it – there are many common issues that prevent developers from writing tests.
One misconception though is that unit testing is irrelevant. Here’s how it usually plays out: the developer thinks, “I need to do unit tests, and I should use PHPUnit because it’s a standard. I don’t know much about it, though.” Then he visits the PHPUnit site and reads the first chapter of the documentation, then the second, then the third… and is left scratching his head. “Does everyone write calculators in PHP? I develop web applications. Why am I shown how to test calculators?” What happens next? The developer closes documentation thinking that testing is somehow related to calculators and is too complex for him understanding at the moment.
Maybe something similar happened to you. Maybe not. But you really should know what to test and how to test it. Such knowledge comes from experience, so in this article I’ll share some of my experience with unit testing. (Please accept my apologies beforehand should I exaggerate a little and am somewhat inaccurate at times; my goal is to help you start writing tests and a little puffery helps the cause.)
Functional Testing
There is no problem with PHPUnit, and there are no problems with the professional developer who decided to write that first unit test. There is a problem with the assumption that unit testing is always the right thing to start with. What is your project? I suppose it is a web application, and you should test it as a web application! How a web application is tested? You open a web page, click on some links, fill in a form, and see the expected results. That’s what your customers do, so why would you start testing your application any other way? Of course as a developer you can improve testing by automating the process. And that’s the main idea of acceptance or functional testing.
Wikipedia says this about functional testing:
Functional testing is a type of black box testing that bases its test cases on the specifications of the software component under test. Functions are tested by feeding them input and examining the output, and internal program structure is rarely considered.
For sure, PHPUnit can be used for functional testing. But PHPUnit is not the only tool available. As you may know, Selenium IDE is widely used. We could use it too, but let’s face it… we’re PHP developers and prefer PHP tools for testing.
Codeception is a modern testing framework. A simple acceptance (or functional) test can be written with it using a very simple PHP DSL (domain-specific language). Here’s an example:
<?php
$I = new WebGuy($scenario);
$I->wantTo("send a feedback");
$I->amOnPage("/");
$I->click("Feedback");
$I->see("Submit your feedback");
$I->fillField("Body","Your site is great!");
$I->click("Submit");
$I->see("Thanks for your feedback!");
The code checks that we can submit feedback from a site. It executes or emulates a browser and performs defined actions within the site. All assertions are based on page content. If we don’t see the text “Thanks for your feedback” in the last response, the test fails. Simple, right?
Why not start by describing all your features as tests? Just write the common usage scenarios and execute them on your site. Functional testing proves a user is able to reproduce your steps and see the same results and thus your application functions well.
As an alternative, you could consider using other popular tools like PHPUnit and Mink, Behat, or Selenium IDE.
Unit Testing
But what about unit tests… when do they come in handy? Let’s start with a definition, again from Wikipedia:
Unit testing is a method by which individual units of source code are tested to determine if they are fit for use. A unit is the smallest testable part of an application. In procedural programming a unit could be an entire module but is more commonly an individual function or procedure. In object-oriented programming a unit is often an entire interface, such as a class, but could be an individual method.
Unit testing is about testing your code and your internal APIs. There is really one rule when it comes to deciding when to do unit testing: when you write your own classes, modules, libraries, frameworks, etc. it is absolutely necessary to cover them with unit tests.
But what about the “calculator problem?” Let’s state it again. We don’t write calculators, rather we build web sites on top of MVC frameworks and CMSs. Frameworks delegate most of the routine tasks, and as a result our code does nothing more than trigger methods of the framework’s API. But the framework’s API is already tested by its developers, so why should we test it again?
What you see here is a problem with most popular MVC frameworks; there is just no simple convention as to where the business logic is stored. If we want to test the feature “a page can be created”, we should test a controller that takes parameters from a form that passes it to a model that validates and saves it. There is no unit responsible for the page creation, that is, there is no single function or class that creates a page. To test one feature we need three or more unit-tests in different modules. Is that rational? I’d say it’s a waste of time. One functional test to check that “a page can be created” is sufficient.
If your functional tests already check everything a user can do, unit tests are great to test things user is forbidden to do. It’s impossible to predict all the actions user can perform on the site, but within unit tests you can verify particular actions aren’t carried out – a user isn’t saved into the database without an email address, no two users can be created with the same login, etc. Write unit tests to check the validation rules for your forms and models, security rules for your controller, and so on.
There are various circumstances that can’t be reproduced with functional tests. Let’s say for example every 100th member of your site receives some sort of bonus. Should you register 100 users manually just to check the last one will receive her bonus? Probably not. A unit test for such a feature is needed. Here instead is a sample PHPUnit test case:
<?php
class BonusTest extends PHPUnit_Framework_TestCase
{
public function setUp() {
$this->bonus = new Bonus();
}
public function testProvideBonusTo100thUser() {
// let's check there are 99 losers
for ($i=0; $i < 99; $i++) {
$user = new User();
$user->setName(uniqid());
$user->save();
$this->assertFalse($this->bonus->hasBonus($user));
}
// and only one winner
$user = new User();
$user->setName("Winner!");
$user->save();
$this->assertTrue($this->bonus->hasBonus($user));
}
}
As a tool for unit-testing, you can use either PHPUnit, or another modern testing framework like Atoum or EnhancePHP. They are designed to be less “enterprisey,” and as such are easier to get acquainted with for novices. If you prefer to test in Ruby style, check out PHPSpec or Spectrum. Moreover, the aforementioned testing framework Codeception can be used to test your code in a similar manner as it tests your site.
Summary
Write your tests pragmatically. Use functional (acceptance) tests for testing your application’s behavior and use unit tests for testing its code. It’s important to test common usage, situations of improper usage, and rare events. But remember, even 100% code coverage won’t guarantee code quality or stability.
Image via John Kwan / Shutterstock
Michael "Davert" Bodnarchuk is a web developer from Kiev, Ukraine. He runs the small outsourcing company Codegyre. He's developed web applications since 2004 with PHP and its major frameworks: Seagull Framework, Symfony, and Zend Framework. His goal is to bring great and usable products to the world, keeping the internal structure of the project well-organized and comprehensive. Davert is also passionate about travelling and tourism.