phpunit基礎用法

原文:phpunit基礎用法 - 9ong

安裝phpunit

phar

$ wget https://phar.phpunit.de/phpunit-latest.phar
$ mv phpunit-latest.phar phpunit
$ chmod +x phpunit
$ ./phpunit --version
PHPUnit x.y.z by Sebastian Bergmann and contributors.

composer

composer require --dev phpunit/phpunit ^latest

執行

PS I:\src\php-shiyanchang> .\vendor\bin\phpunit --version
PHPUnit 7.0.0 by Sebastian Bergmann and contributors.

編寫phpunit測試代碼

依賴關系

生產者(producer),是能生成被測單元并將其作為返回值的測試方法。

消費者(consumer),是依賴于一個或多個生產者及其返回值的測試方法。

用 @depends 標注來表示依賴關系

declare(strict_types=1);
use \PHPUnit\Framework\TestCase;

final class StackTest extends TestCase
{
    public function testEmpty(): array
    {
        $stack = [];
        $this->assertEmpty($stack);

        return $stack;
    }

    /**
     * @depends testEmpty
     */
    public function testPush(array $stack): array
    {
        array_push($stack, 'foo');
        $this->assertSame('foo', $stack[count($stack)-1]);
        $this->assertNotEmpty($stack);

        return $stack;
    }
    
    /**
     * @return string
     */
    public function testGetName() :string
    {        
        $string = "tsingchan";
        $this->assertStringStartsWith("tsing", $string);
        return $string;
    }

    /**
     * @depends testPush
     * @depends testGetName
     */
    public function testPop(array $stack,string $name): void
    {
        $stack[]=$name;
        $this->assertSame('tsingchan', array_pop($stack));
        $this->assertSame('foo', array_pop($stack));
        $this->assertEmpty($stack);
//        $this->assertNotEmpty($stack);
    }
}

數據供給器

用 @dataProvider 標注來指定要使用的數據供給器方法。

數據供給器方法必須聲明為 public,其返回值要么是一個數組,其每個元素也是數組;要么是一個實現了 Iterator 接口的對象,在對它進行迭代時每步產生一個數組。每個數組都是測試數據集的一部分,將以它的內容作為參數來調用測試方法。

當使用到大量數據集時,最好逐個用字符串鍵名對其命名,避免用默認的數字鍵名。這樣輸出信息會更加詳細些,其中將包含打斷測試的數據集所對應的名稱。

declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class DataTest extends TestCase
{
    /**
     * @dataProvider additionProvider1
     * @dataProvider additionProvider2
     */
    public function testAdd(int $a, int $b, int $expected): void
    {
        $this->assertSame($expected, $a + $b);
    }

    public function additionProvider1(): array
    {
        return [
            'adding zeros'  => [0, 0, 0],
            'zero plus one' => [0, 1, 1],
            'one plus zero' => [1, 0, 1],
            'one plus one'  => [1, 1, 3]
        ];
    }

    public function additionProvider2(): array
    {
        return [
            '0+0'  => [1, 2, 3],
            '0+1' => [2, 2, 4],
            '2+3' => [2, 3, 5],
            '3+2'  => [3, 2, 5]
        ];
    }
}

注意:所有數據供給器方法的執行都是在對 setUpBeforeClass() 靜態方法的調用和第一次對 setUp() 方法的調用之前完成的。因此,無法在數據供給器中使用創建于這兩個方法內的變量。這是必須的,這樣 PHPUnit 才能計算測試的總數量。

異常判斷

用 @expectException 標注來測試被測代碼中是否拋出了異常。也可以使用方法:$this->expectException(TypeError::class);

declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class ExceptionTest extends TestCase
{
    /**
     * @expectedException TypeError
     * @return void
     */
    public function testException(): void
    {
        // $this->expectException(TypeError::class); //注解 @exceptionException和這行的效果是一樣的
        
        $this->add("a");//這里可以調用業務邏輯類的代碼,這里add只是為了范例方便
    }
    
    public function add(int $num):int{
        return $num;
    }
}

除了 expectException() 方法外,還有 expectExceptionCode()、expectExceptionMessage() 和 expectExceptionMessageMatches() 方法可以用于為被測代碼所拋出的異常建立預期。

對輸出進行測試

有時候,想要斷言(比如說)某方法的運行過程中生成了預期的輸出(例如,通過 echo 或 print)。PHPUnit\Framework\TestCase 類使用 PHP 的輸出緩沖特性來為此提供必要的功能支持。

何用 expectOutputString() 方法來設定所預期的輸出。如果沒有產生預期的輸出,測試將計為失敗。

declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class OutputTest extends TestCase
{
    public function testExpectFooActualFoo(): void
    {
        $this->expectOutputString('foo');

        print 'foo';
    }

    public function testExpectBarActualBaz(): void
    {
        $this->expectOutputString('bar');

        print 'baz';
    }
}

執行phpunit命令行

命令行選項

$ phpunit --help
PHPUnit latest.0 by Sebastian Bergmann and contributors.

Usage:
  phpunit [options] UnitTest.php
  phpunit [options] <directory>

Code Coverage Options:
  --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 [default: standard output]
  --coverage-xml <dir>        Generate code coverage report in PHPUnit XML format
  --coverage-cache <dir>      Cache static analysis results
  --warm-coverage-cache       Warm static analysis cache
  --coverage-filter <dir>     Include <dir> in code coverage analysis
  --path-coverage             Perform path coverage analysis
  --disable-coverage-ignore   Disable annotations for ignoring code coverage
  --no-coverage               Ignore code coverage configuration

Logging Options:
  --log-junit <file>          Log test execution in JUnit XML format to file
  --log-teamcity <file>       Log test execution in TeamCity format to file
  --testdox-html <file>       Write agile documentation in HTML format to file
  --testdox-text <file>       Write agile documentation in Text format to file
  --testdox-xml <file>        Write agile documentation in XML format to file
  --reverse-list              Print defects in reverse order
  --no-logging                Ignore logging configuration

Test Selection Options:
  --filter <pattern>          Filter which tests to run
  --testsuite <name>          Filter which testsuite to run
  --group <name>              Only runs tests from the specified group(s)
  --exclude-group <name>      Exclude tests from the specified group(s)
  --list-groups               List available test groups
  --list-suites               List available test suites
  --list-tests                List available tests
  --list-tests-xml <file>     List available tests in XML format
  --test-suffix <suffixes>    Only search for test in files with specified suffix(es). Default: Test.php,.phpt

Test Execution Options:
  --dont-report-useless-tests Do not report tests that do not test anything
  --strict-coverage           Be strict about @covers annotation usage
  --strict-global-state       Be strict about changes to global state
  --disallow-test-output      Be strict about output during tests
  --disallow-resource-usage   Be strict about resource usage during small tests
  --enforce-time-limit        Enforce time limit based on test size
  --default-time-limit <sec>  Timeout in seconds for tests without @small, @medium or @large
  --disallow-todo-tests       Disallow @todo-annotated tests

  --process-isolation         Run each test in a separate PHP process
  --globals-backup            Backup and restore $GLOBALS for each test
  --static-backup             Backup and restore static attributes for each test

  --colors <flag>             Use colors in output ("never", "auto" or "always")
  --columns <n>               Number of columns to use for progress output
  --columns max               Use maximum number of columns for progress output
  --stderr                    Write to STDERR instead of STDOUT
  --stop-on-defect            Stop execution upon first not-passed test
  --stop-on-error             Stop execution upon first error
  --stop-on-failure           Stop execution upon first error or failure
  --stop-on-warning           Stop execution upon first warning
  --stop-on-risky             Stop execution upon first risky test
  --stop-on-skipped           Stop execution upon first skipped test
  --stop-on-incomplete        Stop execution upon first incomplete test
  --fail-on-incomplete        Treat incomplete tests as failures
  --fail-on-risky             Treat risky tests as failures
  --fail-on-skipped           Treat skipped tests as failures
  --fail-on-warning           Treat tests with warnings as failures
  -v|--verbose                Output more verbose information
  --debug                     Display debugging information

  --repeat <times>            Runs the test(s) repeatedly
  --teamcity                  Report test execution progress in TeamCity format
  --testdox                   Report test execution progress in TestDox format
  --testdox-group             Only include tests from the specified group(s)
  --testdox-exclude-group     Exclude tests from the specified group(s)
  --no-interaction            Disable TestDox progress animation
  --printer <printer>         TestListener implementation to use

  --order-by <order>          Run tests in order: default|defects|duration|no-depends|random|reverse|size
  --random-order-seed <N>     Use a specific random seed <N> for random order
  --cache-result              Write test results to cache file
  --do-not-cache-result       Do not write test results to cache file

Configuration Options:
  --prepend <file>            A PHP script that is included as early as possible
  --bootstrap <file>          A PHP script that is included before the tests run
  -c|--configuration <file>   Read configuration from XML file
  --no-configuration          Ignore default configuration file (phpunit.xml)
  --extensions <extensions>   A comma separated list of PHPUnit extensions to load
  --no-extensions             Do not load PHPUnit extensions
  --include-path <path(s)>    Prepend PHP's include_path with given path(s)
  -d <key[=value]>            Sets a php.ini value
  --cache-result-file <file>  Specify result cache path and filename
  --generate-configuration    Generate configuration file with suggested settings
  --migrate-configuration     Migrate configuration file to current format

Miscellaneous Options:
  -h|--help                   Prints this usage information
  --version                   Prints the version and exits
  --atleast-version <min>     Checks that version is greater than min and exits
  --check-version             Check whether PHPUnit is the latest version

TestDox

組織測試方式

支持文件或XML編排測試套件

5. 組織測試 — PHPUnit latest 手冊

如果有IDE集成了測試套件,IDE上就會有個按鈕是執行所有測試類文件。

跳過測試

用 @requires 來跳過測試

類型 可能值 示例 其他示例
PHP 任意 PHP 版本號以及可選的運算符 @requires PHP 7.1.20 @requires PHP >= 7.2
PHPUnit 任意 PHPUnit 版本號以及可選的運算符 @requires PHPUnit 7.3.1 @requires PHPUnit < 8
OS 與 PHP_OS 匹配的正則表達式 @requires OS Linux @requires OS WIN32 WINNT
OSFAMILY 任意 OS family @requires OSFAMILY Solaris @requires OSFAMILY Windows
function 任意 function_exists 的有效參數 @requires function imap_open @requires function ReflectionMethod::setAccessible
extension 任意擴展名以及可選的版本號和可選的運算符 @requires extension mysqli @requires extension redis >= 2.2.0

HP、PHPUnit 和擴展的版本約束支持以下運算符:<、<=、>、>=、=、==、!=、<>。

版本是用 PHP 的 version_compare 函數進行比較的。除了其他事情之外,這意味著 = 和 == 運算符只能用于完整的 X.Y.Z 版本號,只用 X.Y 是不行的。

use PHPUnit\Framework\TestCase;

/**
 * @requires extension mysqli
 */
final class DatabaseTest extends TestCase
{
    /**
     * @requires PHP >= 7.0
     */
    public function testConnection(): void
    {
        // 測試需要 mysqli 擴展,并且要求 PHP >= 7.0
    }

    // ... 其他需要 mysqli 擴展的測試
}

測試替身

有時候對被測系統進行測試是很困難的,因為它依賴于其他無法在測試環境中使用的組件。這有可能是因為這些組件不可用,它們不會返回測試所需要的結果,或者執行它們會有不良副作用。

如果在編寫測試時無法使用(或選擇不使用)實際的依賴組件,可以用測試替身來代替。測試替身不需要和真正的依賴組件有完全一樣的的行為方式;它只需要提供和真正的組件同樣的 API 即可,這樣被測系統就會以為它是真正的組件!

PHPUnit 提供的 createStub(type)、createMock(type) 和 getMockBuilder($type) 方法可以在測試中用來自動生成對象,此對象可以充當任意指定原版類型(接口或類名)的測試替身。在任何預期或要求使用原版類的實例對象的上下文中都可以使用這個測試替身對象來代替。

createStub(type) 和 createMock(type) 方法直接返回指定類型(接口或類)的測試替身對象實例。

打樁Stubs

將對象替換為(可選地)返回配置好的返回值的測試替身的實踐方法稱為打樁(stubbing)。可以用樁件(Stub)來“替換掉被測系統所依賴的實際組件,這樣測試就有了對被測系統的間接輸入的控制點。這使得測試能強制安排被測系統的執行路徑,否則被測系統可能無法執行”

業務邏輯類,上樁類:SomeClass:

declare(strict_types=1);
class SomeClass
{
    public function doSomething()
    {
        // 隨便做點什么。
    }
}

測試類對某個方法的調用進行上樁,返回固定值:

declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class StubTest extends TestCase
{
    public function testStub(): void
    {
        // 為 SomeClass 類創建樁件。
        $stub = $this->createStub(SomeClass::class);

        // 配置樁件。
        $stub->method('doSomething')
             ->willReturn('foo');

        // 現在調用 $stub->doSomething() 會返回 'foo'。
        $this->assertSame('foo', $stub->doSomething());
    }

用 willReturn(value) 返回簡單值。這個簡短的語法相當于 will(this->returnValue($value))。而在這個長點的語法中,可以使用變量,從而實現更復雜的上樁行為。

用returnArgument返回方法參數:

declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class StubTest extends TestCase
{
    public function testReturnArgumentStub(): void
    {
        // 為 SomeClass 類創建樁件。
        $stub = $this->createStub(SomeClass::class);

        // 配置樁件。
        $stub->method('doSomething')
             ->will($this->returnArgument(0));

        // $stub->doSomething('foo') 返回 'foo'
        $this->assertSame('foo', $stub->doSomething('foo'));

        // $stub->doSomething('bar') 返回 'bar'
        $this->assertSame('bar', $stub->doSomething('bar'));
    }
}  

用 returnSelf()返回樁對象:

declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class StubTest extends TestCase
{
    public function testReturnSelf(): void
    {
        // 為 SomeClass 類創建樁件。
        $stub = $this->createStub(SomeClass::class);

        // 配置樁件。
        $stub->method('doSomething')
             ->will($this->returnSelf());

        // $stub->doSomething() 返回 $stub
        $this->assertSame($stub, $stub->doSomething());
    }
}

更多對方法的調用上樁的方式詳見:8. 測試替身 — PHPUnit latest 手冊

仿件對象(Mock Object)

仿件對象是為了模仿被調用對象。

業務邏輯代碼采用了觀察者模式:

declare(strict_types=1);
use PHPUnit\Framework\TestCase;

class Subject
{
    protected $observers = [];
    protected $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function attach(Observer $observer)
    {
        $this->observers[] = $observer;
    }

    public function doSomething()
    {
        // 隨便做點什么。
        // ...

        // 通知觀察者我們做了點什么。
        $this->notify('something');
    }

    public function doSomethingBad()
    {
        foreach ($this->observers as $observer) {
            $observer->reportError(42, 'Something bad happened', $this);
        }
    }

    protected function notify($argument)
    {
        foreach ($this->observers as $observer) {
            $observer->update($argument);
        }
    }

    // 其他方法。
}

class Observer
{
    public function update($argument)
    {
        // 隨便做點什么。
    }

    public function reportError($errorCode, $errorMessage, Subject $subject)
    {
        // 隨便做點什么
    }

    // 其他方法。
}

首先用 PHPUnit\Framework\TestCase 類提供的 createMock() 方法來為 Observer 建立仿件對象。

由于關注的是檢驗某個方法是否被調用,以及調用時具體所使用的參數,因此引入 expects() 與 with() 方法來指明此交互應該是什么樣的。

php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class SubjectTest extends TestCase
{
    public function testObserversAreUpdated(): void
    {
        // 為 Observer 類建立仿件
        // 只模仿 update() 方法。
        $observer = $this->createMock(Observer::class);

        // 為 update() 方法建立預期:
        // 只會以字符串 'something' 為參數調用一次。
        $observer->expects($this->once())
                 ->method('update')
                 ->with($this->equalTo('something'));

        // 建立 Subject 對象并且將模仿的 Observer 對象附加其上。
        $subject = new Subject('My subject');
        $subject->attach($observer);

        // 在 $subject 上調用 doSomething() 方法,
        // 我們預期會以字符串 'something' 調用模仿的 Observer
        // 對象的 update() 方法。
        $subject->doSomething();
    }
}

參考

PHPUnit 手冊 — PHPUnit latest 手冊

原文:phpunit基礎用法 - 9ong

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容