Test-driven development and unit testing are the latest methods to ensure that the code can still work as expected after modification and major adjustments. In this article, you will learn how to perform unit tests on your PHP code at the module, database, and UI layers.
It is AM. How can we know that our code is still working?
The Web application runs 24x7 without interruption, so whether or not my application is still running will keep bothering me at night. Unit testing has helped me build enough confidence in my code-so that I can have a good night's sleep.
Unit testing is a framework for writing test cases for code and automatically running these tests. Test-driven development is a unit test method. The idea is that you should first compile the test program and verify that these tests can discover errors before writing the code that needs to pass these tests. When all tests are passed, the features we developed will be completed. The value of these unit tests is that we can run them at any time-either after major modifications, or after being deployed to a running system before code is checked in.
PHP unit test
For PHP, the unit test framework is phpunit2. You can use the PEAR command line as a PEAR module to install the system: % pear install PHPUnit2.
After installing this framework, You can compile unit tests by creating a test class derived from PHPUnit2_Framework_TestCase.
Module unit test
I found that the best place to start a unit test is in the business logic module of the application. I used a simple example: this is a function that sums two numbers. To start the test, we first compile the test case, as shown below.
Listing 1. TestAdd. php
<? Phprequire_once Add. php; require_once PHPUnit2/Framework/TestCase. php; class TestAdd extends PHPUnit2_Framework_TestCase {function test1 () {$ this-> assertTrue (add (1, 2) = 3);} function test2 () {$ this-> assertTrue (add (1, 1) = 2) ;}}?>
This TestAdd class has two methods, both of which use the test prefix. Each method defines a test, which can be as simple and complex as listing 1. In this example, in the first test, we simply concluded that 1 and 2 are equal to 3. In the second test, 1 and 1 are equal to 2.
The PHPUnit2 system defines the assertTrue () method, which is used to test whether the condition value in the parameter is true. Then, we wrote the Add. php module, which initially generated an error.
Listing 2. Add. php
<? Phpfunction add ($ a, $ B) {return 0 ;}?>
When you run the unit test, both tests will fail.
Listing 3. Test failed
% Phpunit TestAdd. phpPHPUnit 2.2.1 by Sebastian Bergmann. FFTime: 0.0031270980834961 There were 2 failures: 1) test1 (TestAdd) 2) test2 (TestAdd) FAILURES !!! Tests run: 2, Failures: 2, Errors: 0, Incomplete Tests: 0.
Now I know that both tests can work normally. Therefore, you can modify the add () function to actually do things.
Now both tests can pass.
<? Phpfunction add ($ a, $ B) {return $ a + $ B;}?>
Listing 4. Test passed
% Phpunit TestAdd. phpPHPUnit 2.2.1 by Sebastian Bergmann... Time: 0.00236797332761_ OK (2 tests) %
Although this test-driven development example is very simple, we can understand its ideas. We first created a test case and had enough code to run the test, but the result was incorrect. Then we verify that the test is indeed a failure, and then implement the actual code to make the test pass.
I found that when implementing code, I always add code until I have a complete test that covers all code paths. At the end of this article, you will see some suggestions on what tests to write and how to write them.
Database Test
After the module test, you can perform the database access test. The database access test brings two interesting questions. First, we must restore the database to a known point before each test. Second, we should note that such recovery may cause damage to the existing database. Therefore, we must test the non-production database, or note that the content of the existing database cannot be affected when writing test cases.
Unit Tests of databases start with databases. To address this problem, we need to use the following simple mode.
Listing 5. Schema. SQL
Drop table if exists authors; create table authors (id mediumint not null AUTO_INCREMENT, name text not null, primary key (id ));
Listing 5 is an authors table. Each record has a related ID.
Next, you can write test cases.
Listing 6. TestAuthors. php
<? Phprequire_once dblib. php; require_once PHPUnit2/Framework/TestCase. php; class TestAuthors extends PHPUnit2_Framework_TestCase {function test_delete_all () {$ this-> assertTrue (Authors: delete_all ();} function test_insert () {$ this-> assertTrue (Authors:: delete_all (); $ this-> assertTrue (Authors: insert (Jack);} function test_insert_and_get () {$ this-> assertTrue (Authors :: delete_all (); $ this- > AssertTrue (Authors: insert (Jack); $ this-> assertTrue (Authors: insert (Joe); $ found = Authors: get_all (); $ this-> assertTrue ($ found! = Null); $ this-> assertTrue (count ($ found) = 2) ;}}?>
This set of tests covers the ability to delete an author from a table, insert an author to a table, and verify that the author exists while inserting the author. This is a cumulative test, and I found it useful for finding errors. Observe which tests can work normally, and which tests cannot work normally, you can quickly find out where errors have occurred, and then you can further understand the differences between them.
The following figure shows the access code version of The dblib. php database that initially failed to be generated.
Listing 7. dblib. php
<? Phprequire_once (DB. php); class Authors {public static function get_db () {$ dsn = mysql: // root: password @ localhost/unitdb; $ db = & DB: Connect ($ dsn, array (); if (PEAR: isError ($ db) {die ($ db-> getMessage ();} return $ db;} public static function delete_all () {return false;} public static function insert ($ name) {return false;} public static function get_all () {return null ;}}?>
Executing a unit test on the code in listing 8 shows that all three tests failed:
Listing 8. dblib. php
% Phpunit TestAuthors. phpPHPUnit 2.2.1 by Sebastian Bergmann. FFFTime: 0.007500171661377 There were 3 failures: 1) equals (TestAuthors) 2) test_insert (TestAuthors) 3) equals (TestAuthors) FAILURES !!! Tests run: 3, Failures: 3, Errors: 0, Incomplete Tests: 0.%
Now we can start to add the code that correctly accesses the database -- add one method and one method -- until all three tests can pass. The final version of dblib. php code is as follows.
Listing 9. Complete dblib. php
<? Phprequire_once (DB. php); class Authors {public static function get_db () {$ dsn = mysql: // root: password @ localhost/unitdb; $ db = & DB: Connect ($ dsn, array (); if (PEAR: isError ($ db) {die ($ db-> getMessage ();} return $ db;} public static function delete_all () {$ db = Authors: get_db (); $ something = $ db-> prepare (delete from authors); $ db-> execute ($ th); return true ;} public static function insert ($ na Me) {$ db = Authors: get_db (); $ something = $ db-> prepare (insert into authors VALUES (null ,?) ); $ Db-> execute ($ th, array ($ name); return true;} public static function get_all () {$ db = Authors: get_db (); $ res = $ db-> query ("SELECT * FROM authors"); $ rows = array (); while ($ res-> fetchInto ($ row )) {$ rows [] = $ row;} return $ rows ;}}?>
HTML Testing
The next step for testing the entire PHP application is to test the front-end Hypertext Markup Language (HTML) interface. To perform this test, we need a Web page as shown below.
Listing 10. TestPage. php
<? Phprequire_once HTTP/Client. php; require_once PHPUnit2/Framework/TestCase. php; class TestPage extends PHPUnit2_Framework_TestCase {function get_page ($ url) {$ client = new HTTP_Client (); $ client-> get ($ url); $ resp = $ clie