Skip to content

Zeroplex 生活隨筆

軟體開發、伺服器和生活瑣事

小 縮小字型大小。 中 重設字型大小。 大 放大字型大小。

標籤: PHPUnit

PHPUnit 10 改變 expected exception 的寫法

Posted on 2023 年 4 月 22 日2023 年 4 月 22 日 By 日落 在〈PHPUnit 10 改變 expected exception 的寫法〉中尚無留言

PHPUnit 9 以及之前測試是否有 throw exception 的方法,是在 test case 前加上 annotation:

/**
 * @expectedException \Exception
 */
public function testFailed() { }

在 PHPUnit 10 則直接在 test case 中呼叫 assertion 即可:

public function testFailed()
{
    $this->expectException(\Exception::class);
}

但要注意,必須在測試開始之前。若 exception 比 expectedException() 還要早出現仍會視為 test failed。

Tags:PHP, PHPUnit

Set protected property on PHPUnit mock object

Posted on 2023 年 1 月 8 日2023 年 1 月 8 日 By 日落 在〈Set protected property on PHPUnit mock object〉中尚無留言

假設有個 class:

class Woker
{
    protected Queue $queue;

    public function run()
    {
        while (!$this->queue->isEmpty()) {
            $job = $this->queue->pop();
            // do something
        }
    }
}

用 PHPUnit 建立 stub 以後,必須使用 reflection 來自訂 $queue property:

$worker = $this->getMockBuilder(Worker::class)
    ->disableOriginalConstructor()
    ->getMock();

$queue = $this->createMock(Queue::class);
$queue->expected($this->atLeast(1))
    ->method('isEmpty')
    ->willReturn(true);

// PHP Reflection on $worker
$refProperty = new \ReflectionProperty($worker, 'queue');
$refProperty->setAccessible(true);
$refProperty->setValue($crawler, $queue);

這樣寫 test case 蠻髒的,有點想要換 test framework ….

Tags:PHP, PHPUnit

整合 Laravel / Lumen 和 AspectMock 的設定

Posted on 2022 年 3 月 4 日2022 年 3 月 4 日 By 日落 在〈整合 Laravel / Lumen 和 AspectMock 的設定〉中尚無留言

Laravel 和 Lumen 都有自己整理好的一套 unit test framework,因此若要加入 AspectMock 會需要對預設的 boostrap file 做一些調整。

整合時需要注意的事情:

  • 需要改寫 /bootstrap/app.php
  • phpunit.xml 中的 bootstrap config,要改成自己新建立的 bootstrap file
  • 注意 require 順序,不要讓 Laravel App 的資料被覆蓋掉 (另外也要注意 require 和 require_once 的差異)

建立新的 bootstrap 設定檔 /tests/bootstrap.php:

<?php

// composer autoload
require_once __DIR__.'/../vendor/autoload.php';

// load Laravel bootstrap
$app = require_once __DIR__ . '/../bootstrap/app.php';


// create temporary folder for AspectMock
$tmpPath = '/tmp/aspectMock/';
if (!file_exists($tmpPath)) {
    mkdir($tmpPath, 0777);
}

$kernel = \AspectMock\Kernel::getInstance();
$kernel->init([
    'appDir' => __DIR__ . '/..',
    'debug' => true,
    'includePaths' => [
        __DIR__ . '/../app',
        __DIR__ . '/../vendor/laravel',  // 如果需要 mock Model 則加上這行
    ],
    'excludePaths' => __DIR__, // "/tests" 目錄不需要處理
    'cacheDir' => $tmpPath,
]);

return $app;  // 把 App 回傳給 createApplication()

接下來要調整原有的 phpunit.xml 設定檔,需要參考 AspectMock 的要求,來調整:

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="tests/bootstrap.php"
         backupGlobals="false">

注意最後面二行,bootstrap 要改成剛剛新建立的檔案,backupGlobals 則要關閉。

最後,去調整 BaseTest.php,在 tearDown() 加上 AspectMock 清理設定的指令:

protected function tearDown(): void
{
    test::clean();
}

另外在 createApplication() 這邊,要改用上面建立的 bootstrap file:

public function createApplication()
{
    return require __DIR__ . '/bootstrap.php';
}

以上全部改好的話,執行 vendor/bin/phpunit 應該可以正常運作。


Error: Call to a member function make() on boolean

注意 require 和 require_once 的差異,Laravel 會在每次開始 unit test 之前,重新建立 $app,所以用 require_once 的話,就的設定清不掉

-$app = require_once __DIR__ . '/../bootstrap/app.php'; // Laravel bootstrap
+$app = require __DIR__ . '/../bootstrap/app.php';
Tags:Laravel, PHP, PHPUnit

由網頁觸發、執行 PHPUnit

Posted on 2018 年 5 月 31 日2021 年 3 月 12 日 By 日落 在〈由網頁觸發、執行 PHPUnit〉中尚無留言

通常都是在 console 底下執行 PHPUnit 跑測試:

$ phpunit Test.php 
... Run by vendor/bin/phpunit ...
PHPUnit 7.1.5 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 30 ms, Memory: 4.00MB

OK (1 test, 1 assertion)

但若開發環境無法使用 console 的話,只能靠寫 PHP script 來 require PHPUnit library 來執行。在這邊發現其實 PHPUnit 有保留 interface 讓使用者可以不透過 console 來操作 PHPUnit,可以在 PHP script 裡面 require library 來使用。

先使用 composer 把 library 拉下來:

composer require 'phpunit/phpunit'

在 PHPUnut/TextUI 底下,有實作幾個 controller 來模擬 console 的操作。以下使用 Command 來執行 PHPUnit:

<?php

require __DIR__ . '/vendor/autoload.php';

use PHPUnitTextUICommand;

$cmd = new Command();

$cmd->run([
    'phpunit',  // 1st arg
    'Test.php', // 2nd arg
]);

如果執行上面這段 PHP script,會得到與在 console 執行 PHPUnit 一樣的結果。
不過要注意的是測試結果會直接輸出到 std out,若另外處理的話,記得用 output buffer 把資料拉回來,直接在瀏覽器看的話會破版。

備註:PHPUnit 不同版本的路徑不同,類別名稱也會不同。

Tags:PHP, PHPUnit

PHPUnit 中「backupGlobals」的作用

Posted on 2018 年 3 月 8 日2021 年 3 月 12 日 By 日落 在〈PHPUnit 中「backupGlobals」的作用〉中尚無留言

複習 PHPUnit 順手寫了小程式做實驗。

PHPUnit 為了讓各個不同的 test case 不會因為 super globals 變數而互相影響,設計了 「backupGlobals」這個功能。先來看範例程式:

class MyTest extends TestCase
{
    public function testOne()
    {
        $GLOBALS['test'] = 123;

        $this->assertArrayHasKey('test', $GLOBALS);
    }

    public function testTwo()
    {
        $this->assertArrayHasKey('test', $GLOBALS);
    }
}

範例中 testOne() 修改了 $GLOBALS 變數的內容,如果在不使用「backupGlobals」的情況下,在 testTwo() 中的 $GLOBALS 也會保留 testOne() 修改過的內容,使 testTow() 抓得到「test」這個 key:

$ phpunit MyTest.php 
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 40 ms, Memory: 4.00MB

OK (2 tests, 2 assertions)

也就表示 testOne() 影響到了 testTwo() 的測試結果,這在測試時是不樂見的,各個測試應該有獨立的測試環境,不應該互相影響。

使用「backupGlobals」功能,PHPUnit 會在 test case 執行之前,使用 serialize() 對 super globals 做備份,並在測試結束以後 unserialize() 復原,所以 testOne() 中做的修改就不會影響到 testTwo() 了:

$ phpunit --globals-backup MyTest.php 
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.

.F                                                                  2 / 2 (100%)

Time: 52 ms, Memory: 4.00MB

There was 1 failure:

1) MyTest::testTwo
Failed asserting that an array has the key 'test'.

/home/johnroyer/tmp/MyTest.php:16

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

重點來了,因為是使用 serialize() 和 unserialize(),所以只能針對 primitive type 進行備份和還原,如果使用了 object 或是 singleton 時,「backupGlobals」就派不上用場了。這個時候要使用殺手鐧「runTestsInSeparateProcesses」:

/**
 * @runTestsInSeparateProcesses
 */
class MyTest extends TestCase
{
    ....
}

使用這個 annotation 會讓 PHPUnit 為每個 test case 分別 fork 出新的 proccess 進行測試,因此不同 test case 就可以在完全獨立的環境下執行不受影響:

$ phpunit MyTest.php 
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.

.F                                                                  2 / 2 (100%)

Time: 303 ms, Memory: 4.00MB

There was 1 failure:

1) MyTest::testTwo
Failed asserting that an array has the key 'test'.

/home/johnroyer/tmp/MyTest.php:19

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

但使用這個殺手鐧也是要付出一點代價:時間。最前二次執行 PHPUnit 時,跑完二個 test case 只花費了 50ms 左右,而開 proccess 來進行測試則花了 300ms。

Tags:PHP, PHPUnit

文章分頁

1 2 下一頁

其他

關於我  (About me)

小額贊助

  文章 RSS Feed

  留言 RSS Feed

Apache AWS Bash C/C++ Docker FreeBSD GCP Git Google Java JavaScript Laravel Linux Microsoft MSSQL MySQL Nginx PHP PHPUnit PostgreSQL Python Qt Ubuntu Unix Vim Web Windows WordPress XD 作業系統 分享 好站推薦 專題 攝影 新奇搞笑 新聞 旅遊 生活雜記 程式設計 網路架站 網頁設計 資訊學習 資訊安全 遊戲 音樂


創用 CC 授權條款
本著作係採用創用 CC 姓名標示-相同方式分享 4.0 國際 授權條款授權.