phpunitphpunit入門


備註

本節概述了phpunit是什麼,以及開發人員為什麼要使用它。

它還應該提到phpunit中的任何大型主題,並鏈接到相關主題。由於phpunit的文檔是新的,您可能需要創建這些相關主題的初始版本。

版本

支持結束這些PHP版本支持發布日期
5.4 2016年8月5日 PHP 5.6,PHP 7。 2016年6月3日
4.8 2017年2月3日 PHP 5.3,PHP 5.4,PHP 5.5和PHP 5.6。 2015年8月7日

為我們的類創建第一個PHPUnit測試

想像一下,我們有一個Math.php 類, Math.php 具有計算fiobanacci和階乘數的邏輯。像這樣的東西:

<?php    
class Math {
    public function fibonacci($n) {
        if (is_int($n) && $n > 0) {
            $elements = array();
            $elements[1] = 1;
            $elements[2] = 1;
            for ($i = 3; $i <= $n; $i++) {
                $elements[$i] = bcadd($elements[$i-1], $elements[$i-2]);
            }
            return $elements[$n];
        } else {
            throw new 
                InvalidArgumentException('You should pass integer greater than 0');
        }
    }

    public function factorial($n) {
        if (is_int($n) && $n >= 0) {
            $factorial = 1;
            for ($i = 2; $i <= $n; $i++) {
                $factorial *= $i;
            }
            return $factorial;
        } else {
            throw new 
                InvalidArgumentException('You should pass non-negative integer');
        }
    }
}
 

最簡單的測試

我們想測試fibonaccifactorial 方法的邏輯。讓我們用Math.php 將文件MathTest.php 創建到同一目錄中。在我們的代碼中,我們可以使用不同的斷言 。最簡單的代碼將是這樣的(我們只使用assertEqualsassertTrue ):

<?php
require 'Math.php';

use PHPUNIT_Framework_TestCase as TestCase;
// sometimes it can be
// use PHPUnit\Framework\TestCase as TestCase;

class MathTest extends TestCase{
    public function testFibonacci() {
        $math = new Math();
        $this->assertEquals(34, $math->fibonacci(9));
    }

    public function testFactorial() {
        $math = new Math();
        $this->assertEquals(120, $math->factorial(5));
    }

    public function testFactorialGreaterThanFibonacci() {
        $math = new Math();
        $this->assertTrue($math->factorial(6) > $math->fibonacci(6));
    }
}
 

我們可以使用命令phpunit MathTest 從控制台運行此測試,輸出將是:

    PHPUnit 5.3.2 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 88 ms, Memory: 10.50Mb

OK (3 tests, 3 assertions)
 

使用dataProviders

測試方法可以接受任意參數。這些參數由數據提供者方法提供 。要使用的數據提供程序方法是使用@dataProvider 批註指定的。 :

<?php
require 'Math.php';

use PHPUNIT_Framework_TestCase as TestCase;
// sometimes it can be
// use PHPUnit\Framework\TestCase as TestCase;

class MathTest extends TestCase {
    /**
     * test with data from dataProvider
     * @dataProvider providerFibonacci
     */
    public function testFibonacciWithDataProvider($n, $result) {
        $math = new Math();
        $this->assertEquals($result, $math->fibonacci($n));
    }

    public function providerFibonacci() {
        return array(
            array(1, 1),
            array(2, 1),
            array(3, 2),
            array(4, 3),
            array(5, 5),
            array(6, 8),
        );
    }
}
 

我們可以使用命令phpunit MathTest 從控制台運行此測試,輸出將是:

    PHPUnit 5.3.2 by Sebastian Bergmann and contributors.

......                                                              6 / 6 (100%)

Time: 97 ms, Memory: 10.50Mb

OK (6 tests, 6 assertions)


<?php
require 'Math.php';
use PHPUNIT_Framework_TestCase as TestCase;
// sometimes it can be
// use PHPUnit\Framework\TestCase as TestCase;
 

測試異常

我們可以使用expectException() 方法測試被測代碼是否拋出異常。同樣在這個例子中,我們添加了一個失敗的測試來顯示失敗測試的控制台輸出

<?php
require 'Math.php';
use PHPUNIT_Framework_TestCase as TestCase;
// sometimes it can be
// use PHPUnit\Framework\TestCase as TestCase;

class MathTest extends TestCase {
    public function testExceptionsForNegativeNumbers() {
        $this->expectException(InvalidArgumentException::class);
        $math = new Math();
            $math->fibonacci(-1);
    }

    public function testFailedForZero() {
        $this->expectException(InvalidArgumentException::class);
        $math = new Math();
        $math->factorial(0);
    }
}
 

我們可以使用命令phpunit MathTest 從控制台運行此測試,輸出將是:

        PHPUnit 5.3.2 by Sebastian Bergmann and contributors.

.F                                                                  2 / 2 (100%)

Time: 114 ms, Memory: 10.50Mb

There was 1 failure:

1) MathTest::testFailedForZero
Failed asserting that exception of type "InvalidArgumentException" is thrown.

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

SetUp和TearDown

此外, PHPUnit 還支持共享設置代碼。在運行測試方法之前,將調用名為setUp()的模板方法。 setUp() 是您創建要測試的對象的位置。一旦測試方法運行完畢,無論是成功還是失敗,都會調用另一個名為tearDown() 模板方法。 tearDown() 用於清理測試對象。

<?php
require 'Math.php';

use PHPUNIT_Framework_TestCase as TestCase;
// sometimes it can be
// use PHPUnit\Framework\TestCase as TestCase;

class MathTest extends TestCase {
    public $fixtures;
    protected function setUp() {
        $this->fixtures = [];
    }

    protected function tearDown() {
        $this->fixtures = NULL;
    }

    public function testEmpty() {
        $this->assertTrue($this->fixtures == []);
    }
}
 

更多信息

您可以在測試中使用PHPUnit 更多機會。有關詳細信息,請參閱官方手冊

使用Stub和Mock的APItest的PHPUNIT示例

您將為其創建單元測試用例的類。 class Authorization {

/* Observer so that mock object can work. */    
 public function attach(Curl $observer)
{
    $this->observers = $observer;
}

/* Method for which we will create test */
public  function postAuthorization($url, $method) {
    
    return $this->observers->callAPI($url, $method);
}
 

}

現在我們不希望我們的測試代碼有任何外部交互,因此我們需要為callAPI函數創建一個模擬對象,因為這個函數實際上是通過curl調用外部URL。 class AuthorizationTest extends PHPUnit_Framework_TestCase {

protected $Authorization;

/**
 * This method call every time before any method call.
 */
protected function setUp() {
    $this->Authorization = new Authorization();
}

/**
 * Test Login with invalid user credential
 */
function testFailedLogin() {

    /*creating mock object of Curl class which is having callAPI function*/
    $observer = $this->getMockBuilder('Curl')
                     ->setMethods(array('callAPI'))
                     ->getMock();

    /* setting the result to call API. Thus by default whenver call api is called via this function it will return invalid user message*/
    $observer->method('callAPI')
            ->will($this->returnValue('"Invalid user credentials"'));

    /* attach the observer/mock object so that our return value is used */
    $this->Authorization->attach($observer);

    /* finally making an assertion*/
    $this->assertEquals('"Invalid user credentials"',           $this->Authorization->postAuthorization('/authorizations', 'POST'));
}
 

}

下面是curl類的代碼(只是一個示例) class Curl{ function callAPI($url, $method){

    //sending curl req
}
 

}

最小的例子測試

給出一個簡單的PHP類:

class Car
{
    private $speed = 0;

    public getSpeed() {
        return $this->speed;
    }

    public function accelerate($howMuch) {
        $this->speed += $howMuch;
    }
}
 

您可以編寫一個PHPUnit測試,通過調用公共方法並檢查它們是否按預期運行來測試被測試類的行為:

class CarTest extends PHPUnit_Framework_TestCase
{
    public function testThatInitalSpeedIsZero() {
        $car = new Car();
        $this->assertSame(0, $car->getSpeed());
    }

    public function testThatItAccelerates() {
        $car = new Car();
        $car->accelerate(20);
        $this->assertSame(20, $car->getSpeed());
    }

    public function testThatSpeedSumsUp() {
        $car = new Car();
        $car->accelerate(30);
        $car->accelerate(50);
        $this->assertSame(80, $car->getSpeed());
    }
}
 

重要部分:

  1. 測試類需要從PHPUnit_Framework_TestCase派生。
  2. 每個測試函數名稱都應以前綴'test'開頭
  3. 使用$this->assert... 函數來檢查預期值。

模擬課程

使用驗證期望的測試double替換對象的實踐,例如斷言已調用方法,被稱為模擬。

讓我們假設我們有SomeService來測試。

class SomeService
{
    private $repository;
    public function __construct(Repository $repository)
    {
        $this->repository = $repository;
    }

    public function methodToTest()
    {
        $this->repository->save('somedata');
    }
}   
 

我們想測試methodToTest 真的調用了存儲庫的save 方法。但是我們不想實際實例化存儲庫(或者Repository 可能只是一個接口)。

在這種情況下,我們可以模擬Repository

use PHPUnit\Framework\TestCase as TestCase;

class SomeServiceTest extends TestCase
{    
    /**
     * @test
     */
    public function testItShouldCallRepositorySavemethod()
    {
        // create an actual mock
        $repositoryMock = $this->createMock(Repository::class);

        $repositoryMock->expects($this->once()) // test if method is called only once
             ->method('save')                   // and method name is 'save'
             ->with('somedata');                // and it is called with 'somedata' as a parameter

        $service = new SomeService($repositoryMock);
        $service->someMethod();
    }
}