• 用phpunit调试PHP程序(转)

    2007-10-30 13:10:25

    调试程序是一个漫长的过程,程序越长越复杂,调试起来就愈加困难。如果你调试的是php程序,那么不妨采用phpUnit,它可以大大加快你的调试速度。
    何谓PhpUnit
    Phpunit 脱胎于Fred Yankowski编写的著名的Junit测试框架。你可以到它的网站 http://www.ontosys.com/phiki/phpunit 下载最新的版本。你可以利用phpUnit编写一套测试软件包。保证你的程序代码正确无误。只需一步便可自动完成所有的测试。
    如果监测到bug,你就可以再写一小段测试代码来找出错误之所在。日后若再有相同的bug出现,只要运行你先前的测试包,马上就可以抓到它。经常运行测试包便可以保证你的程序代码的强壮性。
    开 始
    假设我们有一个银行账务处理程序。现在需要为Account (账户) 类编写一个测试软件包。
    以下是Account类 源代码:
    <?php
     
    class Account{
    var $balance;
    function Account($initialBalance=0){
    $this->balance = $initialBalance;
    }
    function withdraw($amount){
    $this->balance -= $amount;
    }
    function deposit($amount){
    $this->balance += $amount;
    }
    function getBalance(){
    return $this->balance;
    }
    function transferFrom(&$sourceAccount,$amount){
    $sourceAccount->withdraw($amount);
    $this->deposit($amount);
    }
    ?>
     
    创建一个测试类
     
    首 先,我们建立一个测试类AccountTest,它是一个由PhpUnit提供的TestCase的子类。在这个TestCase类中有2个基本的方法: setUp和tearDown。 这2个方法的实现在父类中是空过程,必须由我们自己去重载。其中SetUp 用于进行AccountTest类的初始化处理。在本例中,我们对一些在测试中用到的账号进行初始化。tearDown 则用于AccountTest类的清空处理,在本例中无需使用。因此,就不对它进行重载。这样AccountTester类的源代码如下:
     
    <?php
     
    class AccountTester extends TestCase{
    var $_ac1;
    var $_ac2;
    var $_ac3;
    var $_ac4;
     
    function AccountTester($name){
    $this->TestCase($name); // call parent constructor
    }
    function setUp(){
    $this->_ac1 = new Account(100); // data for testWithdraw
    $this->_ac2 = new Account(20); // data for testDeposit
    $this->_ac3 = new Account(30); // data for testTransferFrom
    $this->_ac4 = new Account(50);
    }
    }
    ?>
     
    加入专门的测试代码
    现在,我们可以往向AccountTester类加入测试代码了。
     
    <?php
     
    // Make a withdrawal of 25 units from _ac1.
    // _ac1's initial balance is 100
     
    function testWithdraw(){
    $this->_ac1->withdraw(25);
    $this->assert($this->_ac1->getBalance() == 75); // 100 - 25 = 75
    }
     
    // Make a deposit of 10 units into _ac2.
    // _ac1's initial balance is 20
     
    function testDeposit(){
    $this->_ac2->deposit(10);
    $this->assertEquals(30,$this->_ac2->getBalance()); //20 +10 = 30
    }
      
    // Tranfers 10 units from _ac3 to _ac4
    // _ac3's initial balance is 30
    // _ac4's initial balance is 50
     
    function testTransferFrom(){
    $this->_ac4->transferFrom(&$this->_ac3,10);
    $this->assertEquals(20,$this->_ac3->getBalance(),"Source account balance incorrect"); // 30 - 10 = 20
    $this->assertEquals(60,$this->_ac4->getBalance(),"Target account balance incorrect"); // 50 + 10 = 60
    }
     
    ?>
     
    这 段代码中,assert(如同C里的断言)方法是测试的关键部分。如果在assert中的条件表达式为真,那么测试通过。否则返回错误。由于assert 方法大都用于判断两个变量的值是否相等。因此,testclass类引入了assertEquals方法专门实现这个功能。AssertEquals方法 中有3个参数,依次分别为:期望值,测试值,两值不相等时返回的消息提示串。
    运行测试过程
    好了,现在可以运行一下我们编好的测试程序了。我们还必须建立一个runtest.php测试程序来运行所有的测试过程。
     
    runtest.php源代码如下:
     
    <?php
    $tSuite = new TestSuite(); //creation of the test suite object 创建测试套件对象
    $tSuite->addtest(new AccountTester("testWithdraw")); //Add inidividual tests
    $tSuite->addtest(new AccountTester("testDeposit")); //加入专门测试方法。
    $tSuite->addtest(new AccountTester("testTransferFrom"));
    $res = new TextTestResult(); //Creation of the Result 建立一个测试结果类
    $tSuite->run(&$res); //Run of the test 运行测试
    $res->report(); //Print results 输出测试结果。
    ?>
     
    程序说明:
    首 先创建测试套件对象tSuite,然后逐一加入专门测试方法,addtest方法的参数是测试方法的 再创建测试报告对象,随之运行测试。测试发现错误的结果由TestResult类捕捉,TestResult可以定制一套text/html的错误报告。 如果有必要你也可以自己编写输出部分。测试结果封装在TestResult类中,为了输出测试结果。我们采用了phpUnit提供的另外一个类 TextTestResult类,它可以输出文本或超文本格式的报告。当然我们也可以自己定制一个新的TestResult的子类控制更复杂的输出格式。
     
    提示和技巧
    1、在编写一个新的测试套件完成之后,我们可以先引入一个小小的bug以证明测试套件可以正常运行。
    比如,在本例account类中,我们故意引入一个有问题的函数。
    <?php
    function withdraw($amount){
    $this->balance -= $Amount;
    // 变量名大小写错误,本意是调用$amount参数,结果引入一个新变量$Amount。
    }
    ?>
    好,现在让我们运行测试套件,如果不出意外的话,我们将很快发现错误之处。
    2.要指出的是,并非所有的方法都需要测试。你只需对相关的方法进行测试。
    3.如果在开始编码前就写好测试代码,会使你更进一步明白你的程序到底需要完成什么样的任务。
    现在,通过引入phpUnit的测试套件类,你可以发现找bug的时间缩短了,而你作为一个程序员的工作效率也提高了。
    那么,尽情享受抓虫子的乐趣吧。祝您好胃口。<
  • PDT的调试画面

    2007-10-14 02:08:49

        PDT继成功地运行在我的机器上之后,现在已设置完成DEBUG的功能,发个小图庆祝一下:

  • 写一个把调试信息输出到磁盘文件的DEBUG程序(原创)

    2007-10-02 22:23:43

        昨天提到过,在下正在看OPENCART的源代码,随着工作的深入,局面日益复杂起来,写一些如echo,print_r等语句直接把变量显示在浏览器上,不仅干扰了正常的系统显示,因为SESSION的使用,也会造成系统运行时的干扰。要想个办法能把调试信息保存在一个文件里,运行过后调出来慢慢看,不是一个很好的办法吗?

       突然想起前两天发到论坛里的一篇文章:http://www.phpchina.com/bbs/thread-37420-1-2.html,里面不是有一个好的调试类吗?找出来一看,果然可以利用上,就搬出来做到一个文件里,代码如下

    <?php

    /* usage:
         write in:
            ZFDemo_Log::log("PHP extensions loaded = \n    " . implode("\n    ", $extensions));
         or:
            ZFDemo_Log::log("The '$ext' extension is required, but not currently loaded by PHP.");
           
            ZFDemo_Log::log("Zend_Session.ini=" . print_r($sessionConfig->toArray(), true));
            
         output:
            ZFDemo_Log::show();  

    require_once('zfdemo_log.php');

    */

    class ZFDemo_Log
    {
        // very simple running log of "debug" messages to highlight inner workings of demo
        public static $log = '';


        /**
         * Accumulate log messages, but also append them to a running log file for easy viewing.
         */
        public static function log($msg, $before = null)
        {
            static $flushed = false;
            if ($before) {
                self::$log = "$msg\n" . self::$log;
            } else {
                self::$log .= "$msg\n";
            }

            $logfile = 'log.txt';
            // performance is not an issue, so just sync to disk everytime
            if (isset($logfile)) {
                if ($flushed) {
                    file_put_contents($logfile, "$msg\n", FILE_APPEND);
                } else {
                    file_put_contents($logfile, self::$log);
                }
                $flushed = true;
            }
        }


        /*
         * Useful if you modify the demo, and need to quickly see the debug log in your browser.
         */
        public static function show()
        {
            echo "<html><head><title>ZF Demo Debug Log</title></head>\n<body>\n<pre>";
            echo htmlentities(self::$log, ENT_QUOTES, 'UTF-8');
            echo '</pre></body></html>';
        }


        public static function get()
        {
            return self::$log;
        }
    }

    保存到你正在开发的文件夹中,可以放在一个子目录下,如/DEBUG/zfdemo_log.php,然后在你需要调试的文件里进行调用,例示代码如下:

    <pre>
    <?php
    require_once('debug/zfdemo_log.php');
    $a = array (1, 2, array ("a", "b", "c"));

    $b = array (3, 4, array ("d", "f", "g"));


    ZFDemo_Log::log('$a=' . print_r($a, true));

    ZFDemo_Log::log('$b=' . print_r($b, true));

    ZFDemo_Log::show(); 

    ?>
    </pre> 

    执行后系统正常运行,然后在同一目录下可以找到一个文本文件log.txt,内容如下:

    $a=Array
    (
        [0] => 1
        [1] => 2
        [2] => Array
            (
                [0] => a
                [1] => b
                [2] => c
            )

    )

    $b=Array
    (
        [0] => 3
        [1] => 4
        [2] => Array
            (
                [0] => d
                [1] => f
                [2] => g
            )

    )

     

    在对复杂的系统进行调试时,这种方法不失高效方便。当然如果需要直接在屏幕上查看调试信息的话,可在相应位置写入代码:

      ZFDemo_Log::show();  

    即可实现浏览器上直接显示调试信息。

       (当时发贴时,二楼一个家伙马上就说这个教程“误人子弟”,其目的无非是想打压ZF,不过大家可以看到,这个例子中可以提出这么好的调试类代码,怎么说也会对大伙有益吧,如果你觉得这份东东对你有帮助,请留言支持一下吧

Open Toolbar