Before examining the ins and outs of the PHPUnit package, take a closer look at the idea of unit testing in general. First, take a look at a traditional component test workflow. Does the following approach seem familiar?
Write the class.
Produce a short test script that require()s the class, instantiates it, calls some methods, and spits out some output.
Run the script.
Check the output of the script against what you expected (in your head).
If the output looks good, move on; if not, adjust the class and repeat steps 3 and 4 until the output appears to work.
Integrate the component into the mother application.
It sounds almost comical, but this kind of process is actually more widespread than you might think.
The problem with approaching your component testing this way is that it's neither thorough nor systematic. In other words, not only are you potentially testing only a small subset of your component, you're also taking longer than you really need to do so. Furthermore, if you do find problems, your short test script won't be much help when it comes to tracking them down. You'll have to make the decision on whether your test is successful or not every time you test.
Thankfully, there is another way. Revise the component test workflow to look more like this:
Design the interface (but not the implementation) of the class.
Create a test suite for the (empty) class, and check that it tests okay.
Write the implementation of the class.
Run the test suite again.
Fix any errors that are causing unexpected results and then go back to step 4.
Integrate the component into the other application.
Now take a look at these steps in more detail and discover exactly what we mean by a test suite. The concept of the test case is covered later in the chapter.
Let's recap some OOP (Object-Oriented Programming) edicts for a moment. In OOP design, it's often said that the most important part of your object hierarchy is the interface and the least important part is the implementation.
This might sound odd, but think of it as follows. If you're designing a user class, what's actually going through your head?
"I'll need to have username, first name, last name, and encrypted password properties. And I'll need methods to tell my application which security groups this user belongs to, and a method to work out how long it's been since their last login, and.....''
You're being smart without even realizing it. You're listing to yourself the methods and member variables of the object. What you're not trying to work out is how you'll go about figuring out how long it's been since the user's last login. In fact, there's probably more than one way to do it. It doesn't really matter. Whichever way you go about it won't affect your interface.
When you consider the interface, you know it's pretty much set in stone as a result of your knowledge of the application's business requirements. It's just not open for discussion. When you consider the implementation, it's safe to say that it's really just detail. As a PHP professional, you might prefer to have a junior software engineer (if you're fortunate enough to have one working under you) handling this detail.
Approaching this in terms of the unit-testing friendly workflow is simple. At first, leave all the methods of the class completely blank. Use nothing but whitespace between opening and closing braces. You'll look at this in more detail later.
With the skeleton class to test, it's not a bad idea to go ahead and create your test suite. Right now, you learn about the theory; the specifics of how to implement this with a particular testing framework are covered in the next section.
The function of a test suite is to provide a simple, black-box solution for testing your class. When invoked, it hooks directly into the important functionality of your class, performs a series of tests, and then states whether those tests were successful. It is truly an appliance.
The nature of these tests is important to understand. You implement a test suite by extending what is known as the test case class. This is provided as part of the Unit Testing framework you choose to use (in this case, PHPUnit, which is covered in much more detail in the next section). You must take care of certain administrative bits and pieces in the extended class (largely relating to instructing the test unit how to instantiate and destroy the test class), but after that it's up to you to provide a test method for every actual method in the test class.
The test methods must be named according to the Unit Testing framework's requirements so that it knows to execute them as part of the test suite's runtime behavior. They are responsible for testing the core functionality of the test class.
Each test method follows the same basic pattern:
Decide upon some input parameters for the test class method.
Determine what the result expected to be returned from the test class method is based on those input parameters.
Call that method, with those input parameters, and trap the result.
Assert the result to be equal to what was determined to be the expected result.
You don't need to worry about doing anything else; you should assume that the Test Suite functionality provided by your Unit Testing framework knows what to do when that assertion is or isn't true. This is certainly true of PHPUnit, which you'll meet shortly.
By creating your test case class you have effectively provided the modus operandi that is, the operational inner workings for your test suite.
At this stage, you would run your black box for the first time and watch it fail spectacularly. It will fail, of course, because you don't have any implementation in your class yet. You should, however, at least be able to get your first idea of what the test results will roughly look like when the implementation has been taken care of.
Without altering the interface of your class, go about writing your code to make it work. There's no need to test anything as you go; this is what the test suite is for. However, you may want to do a syntax check as you go to make sure that you haven't made any typos. A test suite is really for rooting out logic errors rather than compilation errors.
In case you've not encountered it before, you can perform a syntax check at the command line by using php with the -l parameter, followed by the filename of the class or script whose syntax you want to check.
If your class talks to a database, it may not be a bad idea to also check any database SQL queries before you walk away from the implementation. Because PHP isn't closely linked into the database server, rooting out malformed SQL syntax may be easier to do now than after you discover that your test suite fails on one or more methods. In addition, keep in mind that because data in a database can change independently from your code, you'll find it tricky to accurately predict the expected output of methods that do query a database.
Take a look at the following sample query, pulled from a typical user class:
$sql = "SELECT group_id FROM user_group WHERE user_id = $user_id";
Substitute the $user_id for a sensible value and check that the query works in the PostgreSQL (or other) console. If it does, you're in business.
Assuming that your PHP syntax and SQL queries check out okay and you've implemented all your methods, you can go ahead and start testing.
Fire up your test suite black box once more. Now that you've implemented your class, you should have more luck with the test results than last time you ran it.
If your class didn't pass all the tests, it's time to retrace your steps. This is where traditional debugging methods may come in handy. These are covered in some detail in Chapter 23, "Quality Assurance.''
After your test suite runs with a 100 percent success rate, you can be fully confident that your class is ready for the production environment, and you can accordingly integrate it into the mother application.