Core Web Application Development with PHP and MySQL
Writing the code and content for our web applications is unfortunately only half of the battle. Once written, we need to verify that it works properly. Although web application developers usually do take the time to make sure that their scripts "work," the details of making sure that they truly work for all possible inputs and boundary (limit) conditions and correctly handle incorrect input can be extremely time-consuming. Testing is thus often one of the most neglected aspects of projects. Why Bother to Test?
As we have looked at technologies, features, and techniques in this book to write our web applications, we have tried to consider as many variations on input, problems, and security issues as possible, including code for these in our designs and scripts as often as possible. Yet, the question must be askedhow often do developers really look to see what happens when the database with which their application works fills up? What happens if the user enters a username that is 120 characters longdoes our application print an error, just truncate it and continue, or do something else? What happens if the URL to our site gets posted to a community web site and we suddenly find ourselves with thousands of people trying to connect at the samame time? The list goes on, and it should be pretty easy to convince ourselves that verifying the robust functioning of a web site requires a commitment to testing. Unfortunately, against this comes the reality that many people who sign the checks for a particular project are not easily convinced of the value of such an expenditure. In many people's minds, programmers are paid to write code and should do it properly, which means no bugs in their code. Tragically, as mentioned earlier in this book, programmers typically make terrible testers. Although they do verify that the code works correctly with the correct input, they do not always verify that it works correctly with invalid input, with extremes of possible input values, or try out all possible combinations of client browsers on various operating systems to see exactly how the application behaves. You should definitely spend time during the planning phase of your application trying to convince the powers that be that testing is a worthwhile expenditure. Compare the money spent on testing an application to the money that could be lost if all your customers were to go elsewhere because of a buggy and aggravating web application. At a bare minimum, you can do a couple of things to help maintain a higher level of quality for your application. First, regular code reviews are a huge help. Although some might find it a frustrating waste of time to have a small group of people sitting in a conference room going over code page by page, the results are nearly always positive. People who are not familiar with your code will likely ask questions that you would have not thought about otherwisethey can often suggest new and better ways of doing things that you had not seen before. They can also occasionally identify outright bugs that you otherwise would have missed. The trick to code reviews is to keep them in a positive environment in which their code is not on trial. When done properly, it should be a learning experience for everybody. A second way to help maintain quality in a web application is to use something called unit testing. Unit Testing
In the buzzword and community-excitement department, unit testing ranks up there with some of the most white-hot concepts to arrive in the past decade or so. Upon closer examination, however, you can see that it is something rather unspectacular. In this methodology, you just write small tests to ensure the correct operation of the core functionality of your code, usually in the form of a series of classes that implement functions that perform these tests. The one thing we do to maximize the number of tests we write, however, is to write unit tests for code as soon as we write it. (Purists would argue that the tests should be written before we write a single line of code.) Although this might seem an inefficient use of time, it has the advantage of keeping our coding at a reasonable pace, making us look back at what we wrote one more time, and actually making sure that a section of code we just wrote works properly. Even better, as long as we run these tests on a regular basis, we can make sure that any new code we write is not breaking older code that we have not touched in a while. As a perfect example of something for which we might want to use unit testing, we can look at the UserManager class we wrote in Chapter 20, "User Authentication." Over the course of a web application, we will add a number of methods to this class. Having some tests to verify their functionality will be invaluable to us. Establishing a testing framework can be somewhat tedious and time-consuming, so it should come as a relief to know that a number of unit testing frameworks are already available to us for use in our web applications. One of the more interesting of these is the SimpleTest framework, available at http://simpletest.sourceforge.net. (You can download the code for this from http://www.sourceforge.net/projects/simpletest.) If you unpack the downloaded archive somewhere in your include path, such as C:\php\includes or /usr/local/lib/php, you can then include the SimpleTest classes with the following line: require_once('simpletest/unit_tester.php'); This package, in addition to unit testing classes, includes a number of other features for actually testing the correct functionality of web pageseven letting you fill in fields in an HTML form and clicking hyperlinks for you. The unit testing portions of SimpleTest are sufficiently similar to other frameworks so that even if you use something else, the basic usage is familiar. The basics of creating a unit test are to create a class that inherits from the core base class; in the SimpleTest case, this is the UnitTestCase class. To add a test to the class, we just add a method that begins with the word test. We can then use any of a number of methods provided by the framework to verify output from various commands: <?php require_once ('simpletest/unit_tester.php'); require_once ('../webapp/lib/user_manager.inc'); class UserManagerTest extends UnitTestCase { // // give our parent class the name for this set of tests // function __construct() { parent::__construct('User Manager Tests'); } // // let's verify that this class actually validates // usernames properly. // public function testValidUserNames() { $um = new UserManager(); // // the assertEquals function causes the test to fail if // the two arguments do not equal each other. // $this->assertEqual($um->isValidUserName('squirrelsamurai', TRUE)); $this->assertEqual($um->isValidUserName('Squirrel Samurai', TRUE)); $this->assertEqual($um->isValidUserName('Squirrel_Samurai', TRUE)); $this->assertEqual($um->isValidUserName('Squirrel-Samurai', TRUE)); $this->assertEqual($um->isValidUserName('-Squirrels Rock-', TRUE)); $this->assertEqual($um->isValidUserName('?? Who\'s this??', FALSE)); $this->assertEqual($um->isValidUserName( '"asdfasdfasfdsafd"', FALSE)); // etc. ... we should test a ton of other possible cases } } ?>
In addition to methods like assertEqual shown previously, some methods let us verify that no PHP error was signaled (assertNoErrors) or that an error was indeed signaled (assertError), and others matchutput against patterns. We can add as many tests to a unit test class as we want, as long their names all begin with test. The simplest way to run a series of tests is to use the GroupTest class provided by SimpleTest. You create this class and then add to it instances of any other unit test classes you want to run using the addTestCase method. When you're done adding test cases, you can begin running the tests by calling the run method: <?php require_once('simpletest/unit_tester.php'); require_once('simpletest/reporter.php'); require_once('usermanagertests.inc'); include('usermanagertests.inc'); // // create a group tester, and then give it an instance of // our UserManagerTest object whose tests we wish to run. We // could also add other objects if we had more tests here, // too. // $tester = new GroupTest('Full Test Suite'); $tester->addTestCase(new UserManagerTest()); $tester->run(new HtmlReporter()); ?>
The HtmlReporter class, we pass to the run method is in charge of generating the output for the test results. It prints nice visual output to a web browser, but it is less suited to automated nightly runs of tests. For this, we would be far better off using something like the Textreporter class instead, which would let us write to a file. The output of our basic test suite shown earlier, assuming everything ran as expected, would be similar to that shown in Figure 29-4. Figure 29-4. Successful unit test run.
If one of our tests were to fail, however, we would see output similar to that shown in Figure 29-5. Figure 29-5. Failed unit test run.
The framework is indeed significantly more powerful and flexible than this. We have covered only the most simple of usages. The documentation provided on the various web sites supporting it is very thorough and will help you develop a full set of tests for your web application. Performance and Load Testing
Chapter 13, "Web Applications and the Internet," distinguished between performance and scalability, noting that the former measured how quickly a request could be handled and the latter measured how performance degrades as load increases on a server. The obvious issue, of course, is how on earth do you measure such things. Performance Testing
We can do extremely crude timings in our PHP scripts by doing something like this: <?php // // the TRUE flag says return the value as a float! // $start = microtime(TRUE); // // time this function // $usermanager->isValidUserName('bobo the clown'); $end = microtime(TRUE); echo 'Total Execution Time: ' . $end - $start . 'sec.<br/>'; ?>
The problem with this is that there are many reasons why one invocation of a function would be somewhat slow. It would be much better to run the function many times (even with different names) and return the average: <?php $names = array( 'chip', 'happy monkey man', '__really neat name__', 'frank', 'li-jin', 'michelle', 'bobo1950', 'spammy', 'horacio', 'luigi', 'really long user name with a few spaces', 'other user name __ with uscores' ); $total = 0; for ($x = 0; $x < 1000; $x++) { // // pick a random name. The rand function generates a // random number between the two specified args. // $name = array(random(0, count(array) - 1)); // start time. $start = microtime(TRUE); $userman->isValidUserName($name); $end = microtime(TRUE); $total += $end - $start; } $avg = $total / 1000.0; echo "Average running time over 1000 runs: $avg<br/>\n"; ?>
Much better than both of these solutions, however, is to run a profiling tool on our scripts and have them tell us which functions are taking the most time. This output tells us whether we are spending too much time doing things such as processing regular expressions, or whether the database is taking most of our timein which case we might ask ourselves whether the queries we are executing are inefficient or whether our entire table layout is suboptimal. The most reliable profilers are the ones included with the various debugging tools available for PHP, which were mentioned in Chapter 18, "Error Handling and Debugging." Load Testing
Testing the scalability of your server and its capability to scale as traffic increases is not something that can be done manually. Imagine setting up 1,000 computers each with an operator and then trying to have all of them load the same page in a browser at nearly the same time. This is definitely an arena where automated tools are necessary. This is also, unfortunately, an area where which tool you use depends exactly on which server environment your web application uses. There are a number of different tools for IIS and Apache's HTTP Server, some of which are available for no cost; others are expensive commercial products. To get a taste for using these, users of IIS should look into the Web Application Stress Tool (WAS), which ships with the Windows Server Resource Kit. This tool offers some very useful functionality to load test your server. The most popular choice for Apache HTTP users is currently JMeter (http://jarkarta.apache.org/jmeter), a freely available load-testing tool written in the Java programming language. Although the documentation and web pages for this tool are very Java intensive, many PHP web application authors use it to test their applications, because the tool's functionality has long been expanded to include more than just Java testing. Regardless of which tool you use, having some understanding of how your web application degrades under stress is important to planning how to set up your hardware in your data center. Combined with profiling data, this information gives you important clues as to how to optimize your scripts. Pest Control
After you have found a bug in your web application, it is important to keep track of it. Bugs that are merely scribbled down on a piece of paper on somebody's desk have a tendency to get lost and perhaps not get fixed. Even worse, your testers might not know this bug existed in the first place and will not be able to add a test to make sure it never happens again. A bug or problem that has been fixed and then suddenly breaks again is often called a regression in testing lingo. Your goal when developing a web application and tests to accompany it is to limit the number of these regressions. Many development organizations have testers create a test case as soon as a bug is fixed. By using a proper bug management system, you can keep track of which bugs remain active in your application, which are fixed and verified by testing, and which bugs are actually misunderstandings that point to design problems. Most bug tracking systems also let you organize bugs by feature area, which would also permit you to investigate which areas of the application are proving the most problematic, which also might be a clue to design issues. Literally dozens of bug management systems are available, both free of cost and commercial, and all of them have their own merits for selection. Those readers looking for an open source system or one free of cost are encouraged to look at Bugzilla (http://www.bugzilla.org), one of the more popular systems currently in use. |
Категории