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

PHP 函數自動化測試:參數組合生成與執行驗證框架

     編輯:PHP 綜合知識

單元測試中,針對多參數函數的全組合測試往往面臨「參數爆炸」問題。本文實現一個輕量級 PHP 測試框架,自動生成參數笛卡爾積組合,執行目標函數並格式化輸出結果,支援陣列、布爾、標量等多類型參數,適合快速驗證純邏輯函數。

核心設計:參數組合生成器

輸入:二維陣列 `['param_name' => [值1, 值2, ...]]`
輸出:所有參數組合的笛卡爾積陣列

<?php
/**
 * 參數組合生成器:計算 C(n1,1) * C(n2,1) * ... * C(nk,1)
 * @param array $combinList 二維陣列 [參數名 => [可選值...]]
 * @return array 組合陣列 [組合索引 => [參數名 => 值]]
 */
function sysCombineArray(array $combinList): array {
    // 驗證輸入
    foreach ($combinList as $values) {
        if (!is_array($values)) {
            throw new InvalidArgumentException('參數值必須為陣列');
        }
    }
    
    // 計算總組合數
    $total = 1;
    foreach ($combinList as $values) {
        $total *= count($values);
    }
    
    $result = [];
    $repeat = $total;
    
    // 按參數順序生成組合
    foreach ($combinList as $paramName => $values) {
        $repeat /= count($values);  // 當前值重複次數
        $space = $total / count($values) / $repeat; // 值切換間隔
        $start = 1;
        
        foreach ($values as $value) {
            $pos = $start;
            for ($s = 0; $s < $space; $s++) {
                for ($r = 0; $r < $repeat; $r++) {
                    $result[$pos][$paramName] = $value;
                    $pos++;
                }
                $pos += $repeat * (count($values) - 1);
            }
            $start += $repeat;
        }
    }
    
    // 重建索引 (0-based)
    return array_values($result);
}

// 測試組合生成
$params = [
    'color' => ['red', 'green', 'blue'],
    'size'  => ['S', 'M', 'L'],
    'qty'   => [1, 2],
];

$combinations = sysCombineArray($params);
echo "總組合數: " . count($combinations) . " (預期 3*3*2=18)\n";
print_r($combinations);

自動化測試執行器

<?php
/**
 * 函數自動化測試
 * @param string $funcName 目標函數名
 * @param array $paramList 參數組合列表
 * @return array 測試結果記錄
 */
function sysTestFunction(string $funcName, array $paramList): array {
    if (!function_exists($funcName)) {
        throw new BadFunctionCallException("函數 $funcName 不存在");
    }
    
    $combinations = sysCombineArray($paramList);
    $results = [];
    
    echo "開始測試函數: $funcName (共 " . count($combinations) . " 組參數)\n";
    
    foreach ($combinations as $idx => $args) {
        echo "\n--- 組合 #$idx ---\n";
        
        // 顯示參數詳情
        foreach ($args as $k => $v) {
            $type = gettype($v);
            $display = $type === 'array' ? 'Array(' . count($v) . ')' : 
                       ($type === 'boolean' ? ($v ? 'true' : 'false') : 
                       ($type === 'NULL' ? 'null' : $v));
            echo "  $k ($type) = $display\n";
        }
        
        // 構建調用參數
        $callArgs = [];
        foreach ($args as $k => $v) {
            $callArgs[] = var_export($v, true);
        }
        $callStr = "$funcName(" . implode(', ', $callArgs) . ")";
        
        // 執行並捕獲結果/異常
        $start = microtime(true);
        try {
            $return = eval("return $callStr;");
            $time = microtime(true) - $start;
            
            $retType = gettype($return);
            $retDisplay = $retType === 'array' ? 'Array(' . count($return) . ')' :
                          ($retType === 'boolean' ? ($return ? 'true' : 'false') :
                          ($retType === 'NULL' ? 'null' : $return));
            
            echo "  執行: $callStr\n";
            echo "  返回 ($retType): $retDisplay\n";
            echo "  耗時: " . round($time * 1000, 3) . " ms\n";
            
            $results[] = [
                'index' => $idx,
                'args' => $args,
                'return' => $return,
                'success' => true,
                'time_ms' => round($time * 1000, 3),
            ];
        } catch (Throwable $e) {
            $time = microtime(true) - $start;
            echo "  異常: {$e->getMessage()}\n";
            echo "  耗時: " . round($time * 1000, 3) . " ms\n";
            
            $results[] = [
                'index' => $idx,
                'args' => $args,
                'error' => $e->getMessage(),
                'success' => false,
                'time_ms' => round($time * 1000, 3),
            ];
        }
    }
    
    // 統計摘要
    $passed = array_filter($results, fn($r) => $r['success']);
    $failed = array_filter($results, fn($r) => !$r['success']);
    echo "\n=== 測試摘要 ===\n";
    echo "總計: " . count($results) . "\n";
    echo "通過: " . count($passed) . "\n";
    echo "失敗: " . count($failed) . "\n";
    
    return $results;
}

// ==================== 使用示例 ====================

// 待測函數
function calculatePrice(float $base, string $grade, bool $vip, array $coupons = []): float {
    $multiplier = match($grade) {
        'A' => 1.0, 'B' => 1.1, 'C' => 1.2, default => 1.3
    };
    $price = $base * $multiplier;
    if ($vip) $price *= 0.9;
    foreach ($coupons as $c) {
        if (isset($c['type'], $c['value'])) {
            $price = $c['type'] === 'percent' ? $price * (1 - $c['value']) : $price - $c['value'];
        }
    }
    return max(0, round($price, 2));
}

// 定義測試參數空間
$testParams = [
    'base'    => [100, 200, 500],
    'grade'   => ['A', 'B', 'C', 'D'],
    'vip'     => [true, false],
    'coupons' => [
        [],
        [['type' => 'percent', 'value' => 0.1]],
        [['type' => 'fixed', 'value' => 20]],
        [['type' => 'percent', 'value' => 0.05], ['type' => 'fixed', 'value' => 10]],
    ],
];

// 執行測試
$results = sysTestFunction('calculatePrice', $testParams);

// 導出 CSV
$fp = fopen('test_results.csv', 'w');
fputcsv($fp, ['Index', 'Args', 'Return', 'Success', 'Time_ms']);
foreach ($results as $r) {
    fputcsv($fp, [
        $r['index'],
        json_encode($r['args'], JSON_UNESCAPED_UNICODE),
        $r['success'] ? var_export($r['return'], true) : '',
        $r['success'] ? 'PASS' : 'FAIL',
        $r['time_ms'],
    ]);
}
fclose($fp);
echo "\n結果已導出 test_results.csv\n";

進階擴展方向

功能實現思路
成對測試引入 Orthogonal Array / All-pairs 算法,大幅減少組合數
屬性測試類似 QuickCheck,隨機生成滿足約束的輸入驗證不變量
並行執行使用 pcntl_fork 或進程池並行運行獨立測試用例
代碼覆蓋率整合 Xdebug/Xhprof 收集測試覆蓋的代碼路徑
數據驅動測試從 CSV/JSON/數據庫讀取測試用例,支援參數化
斷言庫引入 PHPUnit 斷言或自實現 assertEquals/assertTrue 等

最佳實踐建議

  1. 純函數優先:測試無副作用、確定性輸出的函數,避免依賴全局狀態
  2. 參數範圍控制:邊界值、等價類、異常值各 1-2 個,避免組合爆炸
  3. 異常即失敗:捕獲 Throwable,記錄堆疊跟蹤便於定位
  4. 結果持久化:CSV/JSON/數據庫存儲,支援歷史趨勢對比
  5. CI/CD 集成:作為構建流程一步,失敗即阻斷部署

總結

自動化參數組合測試能快速發現邊界條件下的邏輯錯誤。本框架雖簡陋,但展示了「參數空間定義 → 笛卡爾積生成 → 執行驗證 → 結果收集」的完整鏈路。生產環境建議遷移至 PHPUnit + 數據提供者 或 Pest 框架,獲得更豐富的斷言、報告、並行、覆蓋率等企業級能力。

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