首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 單元測試實戰:從 PHPUnit 入門到數據庫與 UI 測試

     編輯:PHP 相關知識

為什麼需要單元測試?

凌晨 3 點,你是否擔心剛上線的代碼會崩潰?單元測試通過自動化測試用例,在重構、重大變更、部署前後快速驗證代碼行為,建立信心,讓你安心入睡。測試驅動開發 (TDD) 更主張「先寫測試、再寫實現」,確保每行代碼都有測試覆蓋。

環境搭建

# 安裝 PHPUnit(現代項目建議用 Composer)
composer require --dev phpunit/phpunit ^9.5

# 或舊版 PEAR 安裝(本文範例基於 PHPUnit 2.x 風格)
pear install PHPUnit2

模塊層測試:純邏輯單元

步驟 1:編寫失敗的測試 (Red)

<?php
// TestAdd.php
require_once 'Add.php';
require_once 'PHPUnit2/Framework/TestCase.php';

class TestAdd extends PHPUnit2_Framework_TestCase {
    public function testAddPositive() {
        $this->assertEquals(3, add(1, 2));
    }
    public function testAddSame() {
        $this->assertEquals(2, add(1, 1));
    }
}
?>

步驟 2:編寫最小實現使測試通過

<?php
// Add.php - 故意寫錯
function add($a, $b) { return 0; }
?>
$ phpunit TestAdd.php
# 顯示 2 failures - 測試有效,能捕獲錯誤

步驟 3:修正實現 (Green)

<?php
// Add.php - 正確實現
function add($a, $b) { return $a + $b; }
?>
$ phpunit TestAdd.php
OK (2 tests) - 所有測試通過

數據庫層測試:隔離與數據固定

核心挑戰:每次測試前將數據庫重置到已知狀態,且不可污染生產數據。

Schema 定義

-- schema.sql
DROP TABLE IF EXISTS authors;
CREATE TABLE authors (
    id MEDIUMINT NOT NULL AUTO_INCREMENT,
    name TEXT NOT NULL,
    PRIMARY KEY (id)
);

測試用例:遞增式驗證

<?php
// TestAuthors.php
require_once 'dblib.php';
require_once 'PHPUnit2/Framework/TestCase.php';

class TestAuthors extends PHPUnit2_Framework_TestCase {
    public function testDeleteAll() {
        $this->assertTrue(Authors::delete_all());
    }
    public function testInsert() {
        $this->assertTrue(Authors::delete_all());
        $this->assertTrue(Authors::insert('Jack'));
    }
    public function testInsertAndGet() {
        $this->assertTrue(Authors::delete_all());
        $this->assertTrue(Authors::insert('Jack'));
        $this->assertTrue(Authors::insert('Joe'));
        $found = Authors::get_all();
        $this->assertNotNull($found);
        $this->assertCount(2, $found);
    }
}
?>

實現演進:從失敗到通過

<?php
// dblib.php - 最終版
require_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 = self::get_db();
        $sth = $db->prepare('DELETE FROM authors');
        $db->execute($sth);
        return true;
    }
    public static function insert($name) {
        $db = self::get_db();
        $sth = $db->prepare('INSERT INTO authors VALUES (null, ?)');
        $db->execute($sth, array($name));
        return true;
    }
    public static function get_all() {
        $db = self::get_db();
        $res = $db->query('SELECT * FROM authors');
        $rows = array();
        while ($res->fetchInto($row)) $rows[] = $row;
        return $rows;
    }
}
?>

UI 層測試:HTTP 客戶端模擬

<?php
// TestPage.php
require_once 'HTTP/Client.php';
require_once 'PHPUnit2/Framework/TestCase.php';

class TestPage extends PHPUnit2_Framework_TestCase {
    private $client;
    
    protected function setUp() {
        $this->client = new HTTP_Client();
    }
    
    public function testHomePage() {
        $this->client->get('https://localhost/');
        $this->assertEquals(200, $this->client->currentResponse()->status);
        $this->assertStringContainsString('Welcome', $this->client->currentResponse()->body);
    }
}
?>

最佳實踐總結

  1. 測試金字塔:大量單元測試 + 適量集成測試 + 少量 UI 測試
  2. 獨立性:測試間無依賴,可任意順序並行運行
  3. 可讀性:測試名遵循 test[Method]_[Scenario]_[Expected] 命名
  4. 數據構建:使用 Factory 或 Fixture 構建測試數據,避免硬編碼
  5. 持續集成:併入 CI 流水線,提交必跑測試,覆蓋率門檻 80%+

結語

單元測試不是負擔,而是重構的安全網、文檔的活樣本、設計的審查員。從今天為每個新功能寫第一個測試開始,漸進式提升覆蓋率,你的代碼庫將變得更健壯、更自信。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved