使用 Composer
安裝 PHPUnit
php
#查看composer的全局bin目錄 將其加入系統 path 路徑 方便後續直接運行安裝的命令 composer global config bin-dir --absolute #全局安裝 phpunit composer global require --dev phpunit/phpunit #查看版本 phpunit --version
咱們將新建一個unit
項目用於演示單元測試的基本工做流html
mkdir unit && cd unit && mkdir app tests reports #結構以下 ./ ├── app #存放業務代碼 ├── reports #存放覆蓋率報告 └── tests #存放單元測試
#一路回車便可 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.php
bootstrap
<?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
這樣咱們就對業務代碼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);
理解測試執行的模式仍是頗有幫助的,其餘高級特性請瀏覽官方文檔。
使用測試套件來管理測試,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