使用 PHPUnit 進行單元測試並生成代碼覆蓋率報告

安裝PHPUnit

使用 Composer 安裝 PHPUnitphp

#查看composer的全局bin目錄 將其加入系統 path 路徑 方便後續直接運行安裝的命令
composer global config bin-dir --absolute
#全局安裝 phpunit
composer global require --dev phpunit/phpunit
#查看版本
phpunit --version

使用Composer構建你的項目

咱們將新建一個unit項目用於演示單元測試的基本工做流html

建立項目結構

mkdir unit && cd unit && mkdir app tests reports
#結構以下
./
├── app #存放業務代碼
├── reports #存放覆蓋率報告
└── tests #存放單元測試

使用Composer構建工程

#一路回車便可
composer init

#註冊命名空間
vi composer.json
...
    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "Tests\\": "tests/"
        }
    }
...
#更新命名空間
composer dump-autoload

#安裝 phpunit 組件庫
composer require --dev phpunit/phpunit

到此咱們就完成項目框架的構建,下面開始寫業務和測試用例。數據庫

編寫測試用例

建立文件app/Example.php 這裏我爲節省排版就不寫註釋了json

<?php
namespace App;

class Example
{
    private $msg = "hello world";

    public function getTrue()
    {
        return true;
    }

    public function getFalse()
    {
        return false;
    }

    public function setMsg($value)
    {
        $this->msg = $value;
    }

    public function getMsg()
    {
        return $this->msg;
    }
}

建立相應的測試文件tests/ExampleTest.phpbootstrap

<?php
namespace Tests;

use PHPUnit\Framework\TestCase as BaseTestCase;
use App\Example;

class ExampleTest extends BaseTestCase
{
    public function testGetTrue()
    {
        $example = new Example();
        $result = $example->getTrue();
        $this->assertTrue($result);
    }
    
    public function testGetFalse()
    {
        $example = new Example();
        $result = $example->getFalse();
        $this->assertFalse($result);
    }
    
    public function testGetMsg()
    {
        $example = new Example();
        $result = $example->getTrue();
        // $result is world not big_cat
        $this->assertEquals($result, "hello big_cat");
    }
}

執行單元測試

[root@localhost unit]# phpunit --bootstrap=vendor/autoload.php \
tests/

PHPUnit 6.5.14 by Sebastian Bergmann and contributors.

..F                                                                 3 / 3 (100%)

Time: 61 ms, Memory: 4.00MB

There was 1 failure:

1) Tests\ExampleTest::testGetMsg
Failed asserting that 'hello big_cat' matches expected true.

/opt/unit/tests/ExampleTest.php:27
/root/.config/composer/vendor/phpunit/phpunit/src/TextUI/Command.php:195
/root/.config/composer/vendor/phpunit/phpunit/src/TextUI/Command.php:148

FAILURES!
Tests: 3, Assertions: 3, Failures: 1.

這是一個很是簡單的測試用例類,能夠看到,執行了共3個測試用例,共3個斷言,共1個失敗,能夠參照PHPUnit手冊學習更多高級用法。bash

代碼覆蓋率

代碼覆蓋率反應的是測試用例測試對象行,函數/方法,類/特質的訪問率是多少(PHP_CodeCoverage 尚不支持 Opcode覆蓋率、分支覆蓋率 及 路徑覆蓋率),雖然有不少人認爲過度看重覆蓋率是不對的,但咱們初入測試仍是俗氣的追求一下吧。app

測試覆蓋率的檢測對象是咱們的業務代碼,PHPUnit經過檢測咱們編寫的測試用例調用了哪些函數,哪些類,哪些方法,每個控制流程是否都執行了一遍來計算覆蓋率。composer

PHPUnit 的覆蓋率依賴 Xdebug,能夠生成多種格式:框架

--coverage-clover <file>    Generate code coverage report in Clover XML format.
--coverage-crap4j <file>    Generate code coverage report in Crap4J XML format.
--coverage-html <dir>       Generate code coverage report in HTML format.
--coverage-php <file>       Export PHP_CodeCoverage object to file.
--coverage-text=<file>      Generate code coverage report in text format.
--coverage-xml <dir>        Generate code coverage report in PHPUnit XML format.

同時須要使用 --whitelist dir參數來設定咱們須要檢測覆蓋率的業務代碼路徑,下面演示一下具體操做:函數

phpunit \
--bootstrap vendor/autoload.php \
--coverage-html=reports/ \
--whitelist app/ \
tests/
#查看覆蓋率報告
cd reports/ && php -S 0.0.0.0:8899

clipboard.png

clipboard.png

這樣咱們就對業務代碼App\Example作單元測試,而且得到咱們單元測試的代碼覆蓋率,如今天然是百分之百,由於個人測試用例已經訪問了App\Example的全部方法,沒有遺漏的,開發中則能體現出你的測試時用力對業務代碼測試度的完善性。

基境共享測試數據

可能你會發現咱們在每一個測試方法中都建立了App\Example對象,在一些場景下是重複勞動,爲何不能只建立一次而後供其餘測試方法訪問呢?這須要理解 PHPUnit 執行測試用例的工做流程。

咱們沒有辦法在不一樣的測試方法中經過某成員屬性來傳遞數據,由於每一個測試方法的執行都是新建一個測試類對象,而後調用相應的測試方法

即測試的執行模式並非

testObj = new ExampleTest();
testObj->testMethod1();
testObj->testMethod2();

而是

testObj1 = new ExampleTest();
testObj1->testMethod1();

testObj2 = new ExampleTest();
testObj2->testMethod2();

因此testMethod1()修改的屬性狀態沒法傳遞給 testMethod2()使用。

PHPUnit則爲咱們提供了全面的hook接口:

public static function setUpBeforeClass()/tearDownAfterClass()//測試類構建/解構時調用
protected function setUp()/tearDown()//測試方法執行前/後調用
protected function assertPreConditions()/assertPostConditions()//斷言前/後調用

當運行測試時,每一個測試類大體就是以下的執行步驟

#測試類基境構建
setUpBeforeClass

#new一個測試類對象
#第一個測試用例
setUp
assertPreConditions
assertPostConditions
tearDown

#new一個測試類對象
#第二個測試用例
setUp
assertPreConditions
assertPostConditions
tearDown
...

#測試類基境解構
tearDownAfterClass

因此咱們能夠在測試類構建時使用setUpBeforeClass建立一個 App\Example 對象做爲測試類的靜態成員變量(tearDownAfterClass主要用於一些資源清理,好比關閉文件,數據庫鏈接),而後讓每個測試方法用例使用它:

<?php
namespace Tests;

use App\Example;
use PHPUnit\Framework\TestCase as BaseTestCase;

class ExampleTest extends BaseTestCase
{
    // 類靜態屬性
    private static $example;

    public static function setUpBeforeClass()
    {
        self::$example = new Example();
    }

    public function testGetTrue()
    {
        // 類的靜態屬性更新
        self::$example->setMsg("hello big_cat");
        $result = self::$example->getTrue();
        $this->assertTrue($result);
    }

    public function testGetFalse()
    {
        $result = self::$example->getFalse();
        $this->assertFalse($result);
    }

    /**
     * 依賴 testGetTrue 執行完畢
     * @depends testGetTrue
     * @return [type] [description]
     */
    public function testGetMsg()
    {
        $result = self::$example->getMsg();
        $this->assertEquals($result, "hello big_cat");
    }
}

或者使用@depends註解來聲明兩者的執行順序,並使用傳遞參數的方式來知足需求。

public function testMethod1()
{
    $this->assertTrue(true);
    return "hello";
}

/**
 * @depends testMethod1
 */
public function testMethod2($str)
{
    $this->assertEquals("hello", $str);
}
#執行模式大概以下
testObj1 = new Test;
$str = testObj1->testMethod1();

testObj2 = new Test;
testObj2->testMethod2($str);

理解測試執行的模式仍是頗有幫助的,其餘高級特性請瀏覽官方文檔

使用phpunit.xml編排測試套件

使用測試套件來管理測試,vi phpunit.xml

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="./vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <!--能夠定義多個 suffix 用於指定待執行的測試類文件後綴-->
        <testsuite name="Tests">
            <directory suffix="Test.php">./test</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <!--能夠定義多個 對./app下的業務代碼作覆蓋率統計-->
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <logging>
        <!--覆蓋率報告生成類型和輸出目錄 lowUpperBound低覆蓋率閾值 highLowerBound高覆蓋率閾值-->
        <log type="coverage-html" target="./reports" lowUpperBound="35" highLowerBound="70"/>
    </logging>
</phpunit>

而後直接運phpunit行便可:

[root@localhost unit]# phpunit 
PHPUnit 6.5.14 by Sebastian Bergmann and contributors.

Time: 81 ms, Memory: 4.00MB

No tests executed!

Generating code coverage report in HTML format ... done
相關文章
相關標籤/搜索