PHP 單元測試

本文首發於 https://jaychen.cc/article/34php

做者 Jaychenhtml

朋友,你據說過安。。。不是,寫過單元測試嗎。json

單元測試是開發過程當中必不可少的一環,一個項目有良好的單元測試代碼,重構的勇氣都大不少。此次寫一篇小文來介紹一下 PHP 的單元測試工具 PHPUnit 的使用。bash

PHPUnit 的使用並不難,這篇文章主要仍是充當一個引子,介紹基本概念和使用,有了這篇文章的基礎以後,去看官網的文檔就會更加順風順水。composer

安裝

安裝 PHPUnit 的方式很簡單,使用 composer 能夠一行代碼就能夠安裝。phpstorm

composer require --dev phpunit/phpunit
複製代碼

安裝以後,在 vendor/bin 目錄下有一個 phpunit 的可執行文件,這個就是 phpunit 本體了。假設咱們項目的目錄結構以下:函數

➜  phpunit tree .

├── controller
├── model
├── service
├── test
└── vendor
├── composer.json
複製代碼

其中咱們的單元測試代碼都放在 test 目錄下。使用 composer 來爲咱們解決 autoload 的問題。工具

{
  "autoload": {
    "psr-4": {
      "Controller\\": "controller/",
      "Model\\": "model/",
      "Service\\": "service/",
      "Test\\": "test/",
    }
  },
}
複製代碼

若是你還不懂 composer 自動加載的使用,能夠參考這篇文章。最後執行 composer dumpautoload -o 讓自動加載生效。單元測試

到這裏咱們的安裝就算結束了。若是你使用 phpstorm 進行開發,那麼你須要進行以下的配置:測試

這裏指明瞭從哪裏加載 PHPUnit,因爲咱們使用 composer 安裝,因此,這裏的文件選擇 composer 生成的 autoload.php 文件便可。

使用

好了,假設咱們如今進行開發,在 service 目錄中添加了一個 CalculateService 的文件,而且編寫了一個 abs 的函數。

namespace Service;

class CalculateService {
    public function abs($num) {
        return abs($num);
    }
}
複製代碼

如今咱們對 abs 函數進行單元測試,PHPUnit 規定了一個測試類必須遵照以下的規定:

  • 單元測試類名必須以 Test 結尾,必須繼承 \PHPUnit\Framework\TestCase 基類。
  • 每一個測試函數必須以 test 開頭。

上面的規定是必須遵照的,若是代碼沒有遵照規定 PHPUnit 不會把他當作單元測試代碼。除了以上的兩條,還有一些良好的編碼習慣能夠參考:

  • 單元測試代碼都放在 test 目錄下。
  • 每一個單元測試類以被測試的類名開頭。例如被測試類爲 CalculateService,那麼單元測試類應該爲 CalculateServiceTest
  • 每一個單元測試函數應該爲被測試函數名結尾。例如被測試函數爲 abs,那麼單元測試函數應該爲 testAbs

根據上面的規範,編寫單元測試代碼

class UserServiceTest extends \PHPUnit\Framework\TestCase {
    public function testAbs() {
        $userService = new \Service\CalculateService();
        $this->assertEquals(4, $userService->abs(4));
    }
}
複製代碼

在上面的測試代碼中,調用了咱們要測試的函數 abs,而後斷言 $userService->abs(4) 的結果爲 4。在 phpstorm 中直接在 testAbs 函數處右鍵選擇 run UserServiceTest 執行:

發如今控制檯會輸出以下內容

Time: 17 ms, Memory: 4.00MB

OK (1 test, 1 assertion)
複製代碼

代表 abs 經過了 $userService->abs(4) == 4 的測試用例。這裏注意一點,這裏並不代表 abs 函數已經經過測試,一個良好的測試應該包含多個測試用例來覆蓋儘量多的可能性。

如今 PHPUnit 基本的單元測試已經運行成功了,在 PHPUnit 的文檔中,有更多關於測試的用法。因爲 PHPUnit 的用法過多,這裏不能一一說明,這裏提一些其餘用法。

  • PHPUnit 提供了 @test 的註解,若是一個測試函數添加了 @test 註解,那麼測試函數名字就沒必要以 test 開頭。

  • \PHPUnit\Framework\TestCase 有一個 setUp 函數,若是本身編寫的測試類重寫了這個函數,那麼每次在開始執行測試函數以前,會先執行 setUp 進行測試以前的初始化。一樣,也有一個 tearDown 的函數,若是重寫,那麼在測試函數執行完畢以後調用 tearDown 函數。

  • .... 更多的內容需參考 PHPUnit 的文檔。

phpunit.xml 文件

在上面的例子中,咱們使用 phpstorm 逐個執行測試函數,可是若是咱們須要一次性執行全部的單元測試,那麼咱們能夠編寫 phpunit.xml 文件來實現。

給出一個 phpunit.xml 的編寫例子來說解 phpunit.xml 的做用

<?xml version="1.0" encoding="UTF-8"?>
<phpunit>
    <testsuites>
        <testsuite>
            <directory>test</directory>
        </testsuite>
    </testsuites>
</phpunit>
複製代碼

這裏 <directory>test</directory> 指定了測試代碼都放在 test 目錄下,在 phpstorm 下右鍵點擊 phpunit.xml 文件選擇 Run phpunit.xml,phpunit 就會到 test 目錄下查找全部單元測試並逐個執行。

除了使用 phpunit.xml 來一次性執行全部的單元測試,還能夠在 phpunit.xml 中配置單元測試結果的輸出日誌。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit>
	.....
    <logging>
        <log type="testdox-html" target="tmp/log.html"/>
    </logging>
</phpunit>
複製代碼

此時在執行 phpunit.xml 文件,就會在項目目錄下生成一個 tmp/log.html 文件,這個文件記錄了全部單元測試的結果。

固然,,,更多 phpunit.xml 配置相關的內容,仍是須要查看文檔。 :laughing:

Mock 測試

PHPUnit 還提供了 Mock 測試。這裏先介紹一下什麼是 Mock 測試。

假設 foo 函數調用了 bar 函數,那麼在對 foo 函數進行單元測試會有兩個問題:

  • foo 函數依賴於 bar 函數的結果,那麼在對 foo 進行單元測試的時候必然會引入 bar ,那麼這樣子單元測試就沒意義了,若是測試不經過,那麼沒法保證 bug 出在 foo 仍是 bar。
  • bar 函數可能在測試環境不可執行,那麼 foo 沒法獲取 bar 的執行結果,從而沒法對 foo 進行單元測試。

Mock 測試就是爲了解決上面的問題而出現的,使用 Mock 咱們能夠虛擬出一個 bar 的調用,而且假設 bar 調用返回結果。若是仍是聽不懂,上一段代碼就知道了。

class MockTest extends \PHPUnit\Framework\TestCase {
	public function testGet() {  
		$stub = $this->createMock(\App\UserService::class);     //1
		$stub->method('get')->willReturn(3); 					//2
		$this->assertEquals(3,$stub->get(1));  					//3
	} 
}
複製代碼

上面的測試函數就使用到了 Mock,一行一行代碼來分析:

  • 第一行建立了一個虛擬的 UserService 對象。
  • 第二行假設 UserService 中的 get 函數的返回值爲 3。
  • 第三行調用 $stub->get(1) 不會真的去執行 get 函數,而是根據第二行的 willReturn 函數直接返回 3。

以上就是一個簡單的 Mock 測試,固然 Mock 測試還有不少複雜的用法,這裏沒辦法一一展開,其實掌握基本的用法,更多複雜的高級用法在實踐中碰到了再去查看文檔也不遲。

好了,PHPUnit 的基本操做就這些了,單元測試自己並非一個很難的東西,阻礙單元測試的進行並非在技術上,更多的是一個項目時間安排的衡量與考慮。

相關文章
相關標籤/搜索