最近更新: 2008-04-08

PHPUnit 自定 TestSuite 輸出樣式,輸出為 HTML 網頁

PHPUnit 預置了一個純文字型式的 TextUI Runner,在大多數情形下,它足夠應付我們的需求。不過,當我想要把 TestSuite 放到專案網頁上,讓小組成員 - 特別是 PM - 可以上網查看立即的測試結果時,那個文字型式的 TextUI Runner 就顯得太簡略了。至少要把測試工作及結果輸出成 HTML 的條列式清單,最好還加上顏色區別成功或失敗。

所幸 PHPUnit 提供了良好的擴充性,要自定一個可以將測試內容輸出為 HTML 網頁的 HTML Runner 相當簡單。

自定輸出樣式的方法很多,我選擇透過擴展 PHPUnit_Framework_TestListener 來實現。See also: Implement PHPUnit_Framework_TestListener.

另一方面,我將測試案例清單放置在另一個檔案中。在本例中,測試案例清單之文件名稱為 UnitTestSuiteList.php。

<?php
/**
 * 測試案例清單。
 */
$testCases = array(
    'AbcTest.php',
    'XyzTest.php'
);
?>

HTML Runner, UnitTestSuite.php

<?php
if (!defined('PHPUnit_MAIN_METHOD')) {
    define('PHPUnit_MAIN_METHOD', '');
}

//測試案例清單
require_once 'UnitTestSuiteList.php';

require_once 'PHPUnit/Framework.php';

class HtmlTestListener implements PHPUnit_Framework_TestListener {
    protected $lastTestStatus;

    private function _name(PHPUnit_Framework_Test $test)
    {
        $name = $test->getName();
        if (preg_match_all('/[A-Z][a-z0-9]+/', $name, $words)) {
            $words = $words[0];
            $showName = array_shift($words);
            foreach ($words as $word) {
                $showName .= ' ' . strtolower($word);
            }
            return $showName;
        }
        return $name;
    }

    public function addError(PHPUnit_Framework_Test $test,
           Exception $e,
           $time)
    {
        $this->lastTestStatus = 'e';
    }

    public function addFailure(PHPUnit_Framework_Test $test,
             PHPUnit_Framework_AssertionFailedError $e,
             $time)
    {
        $this->lastTestStatus = 'f';
    }

    public function addIncompleteTest(PHPUnit_Framework_Test $test,
                    Exception $e,
                    $time)
    {
        $this->lastTestStatus = 'i';
    }

    public function addSkippedTest(PHPUnit_Framework_Test $test,
                 Exception $e,
                 $time)
    {
        $this->lastTestStatus = 's';
    }

    public function startTest(PHPUnit_Framework_Test $test)
    {
        $this->lastTestStatus = 'p'; //pass
    }

    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        echo '<li>[';

        switch ( $this->lastTestStatus ) {
            case 'e':
                echo '<span class="error">E</span>';
                break;
            case 'f':
                echo '<span class="failure">F</span>';
                break;
            case 'i':
                echo '<span class="incomplete">I</span>';
                break;
            case 's':
                echo '<span class="skipped">S</span>';
                break;
            default:
                echo '<span class="pass">P</span>';
                break;
        }
        echo ']', $this->_name($test), "</li>\n";
    }

    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        if ('' != $suite->getName())
            printf("<div class='test_suite'>%s:</div><ul>\n", $suite->getName());
    }

    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        if ('' != $suite->getName())
            printf("</ul></div>\n");
    }
}
?>

<style type="text/css">
.pass,
.error,
.failure,
.incomplete,
.skipped {
    font-family: monospace, courier;
}

.pass {
    background-color: #00cc00;
}
.error {
    background-color: #CC0000;
}
.failure {
    background-color: red;
}
.incomplete {
    background-color: #ffff99;
}
.skipped {
    background-color: #CCFFCC;
}
</style>
<div>
[<span class="pass">P</span>]: Pass;
[<span class="failure">F</span>]: Failure;
[<span class="incomplete">I</span>]: Incomplete;
[<span class="skipped">S</span>]: Skipped;
[<span class="error">E</span>]: Error.
</div>
<hr/>

<?php

$suite = new PHPUnit_Framework_TestSuite;
$result = new PHPUnit_Framework_TestResult;
$result->addListener(new HtmlTestListener);

foreach ($testCases as $testCase) {
    require_once $testCase;
    preg_match('/\/(\w+)\.php$/', $testCase, $matches);
    $testCaseClassName = $matches[1];
    $suite->addTestSuite($testCaseClassName);
}

// Run the tests.
echo '<p>Start. Timestamp:', date('Y-m-d h:i:s'), '</p>';
$suite->run($result);

echo '<div><pre>';
if ($result->wasSuccessful() &&
    $result->allCompletlyImplemented() &&
    $result->noneSkipped())
{
    printf(
        "\nOK (%d test%s)\n",

        count($result),
        (count($result) == 1) ? '' : 's'
    );
}
else if ((!$result->allCompletlyImplemented() ||
          !$result->noneSkipped())&&
         $result->wasSuccessful())
{
    printf(
        "\nOK, but incomplete or skipped tests!\n" .
        " Tests: %d \n Incomplete: %s \n Skipped: %s.\n",

        count($result),
        $result->notImplementedCount(),
        $result->skippedCount()
    );
}
else
{
    printf(
        "\nFAILURES!\n" .
        " Tests: %d \n Failures: %s \n Errors: %s \n Incomplete: %s \n Skipped: %s.\n",

        count($result),
        $result->failureCount(),
        $result->errorCount(),
        $result->notImplementedCount(),
        $result->skippedCount()
    );
}
echo '</pre></div>';
echo '<p>End. Timestamp:', date('Y-m-d h:i:s'), '</p>';
?>
使用畫面
樂多舊網址: http://blog.roodo.com/rocksaying/archives/5829661.html

樂多舊回應
HACGIS@gmail.com(tokimeki) (#comment-16180631)
Tue, 08 Apr 2008 17:52:27 +0800
我記得 SimpleTest 有一個 HtmlReporter 的類別可以用來輸出 HTML 報表,或許找一下 PHPUnit 裡面搞不好也許也有~