核心要點
本文由 Younes Rafie 審核。感謝所有 SitePoint 的同行評審人員,使 SitePoint 內容達到最佳狀態!
我一開始並沒有為我的代碼編寫測試。像許多人一樣,我的“測試”就是編寫代碼並刷新頁面。 “看起來對嗎?”,我會問自己。如果我認為是對的,我就繼續進行。
事實上,我做過的大部分工作都是在不太關心其他形式測試的公司。經過多年時間,以及像 Chris Hartjes 這樣的人的明智建議,我才看到了測試的價值。而且我仍在學習好的測試是什麼樣的。
我最近開始從事一些包含捆綁測試觀察者的 JavaScript 項目。
這是一個關於測試驅動 NodeJS 開發的精彩高級視頻教程!
在 JavaScript 的世界裡,預處理源代碼並不少見。在 JavaScript 的世界裡,開發人員使用不被廣泛支持的語法編寫代碼,然後將代碼轉換為被廣泛支持的語法,通常使用名為 Babel 的工具。
為了減少調用轉換腳本的負擔,樣板項目已開始包含自動監視文件更改的腳本;然後調用這些腳本。
我參與的這些項目採用了類似的方法來重新運行單元測試。當我更改 JavaScript 文件時,這些文件會被轉換,並且單元測試會被重新運行。這樣,我可以立即看到是否破壞了任何東西。
本教程的代碼可以在 Github 上找到。我已經用 PHP 7.1 測試過它。
自從開始從事這些項目以來,我開始為 PHPUnit 設置類似的東西。事實上,我設置 PHPUnit 觀察者腳本的第一個項目是一個也預處理文件的 PHP 項目。
在我向我的項目添加預處理腳本後,這一切就開始了:
composer require pre/short-closures
這些特定的預處理腳本允許我重命名 PSR-4 自動加載的類(從 path/to/file.php ⇒ path/to/file.pre),以選擇加入它們提供的功能。所以我向我的 composer.json 文件添加了以下內容:
"autoload": { "psr-4": { "App\": "src" } }, "autoload-dev": { "psr-4": { "App\Tests\": "tests" } }
這來自 composer.json
然後我添加了一個類來生成包含當前用戶會話詳細信息的函數:
namespace App; use Closure; class Session { private $user; public function __construct(array $user) { $this->user = $user; } public function closureWithUser(Closure $closure) { return () => { $closure($this->user); }; } }
這來自 src/Session.pre
為了檢查這是否有效,我設置了一個小的示例腳本:
require_once __DIR__ . "/vendor/autoload.php"; $session = new App\Session(["id" => 1]); $closure = ($user) => { print "user: " . $user["id"] . PHP_EOL; }; $closureWithUser = $session->closureWithUser($closure); $closureWithUser();
這來自 example.pre
……而且因為我想在非 PSR-4 類中使用短閉包,我還需要設置一個加載器:
require_once __DIR__ . "/vendor/autoload.php"; Pre\Plugin\process(__DIR__ . "/example.pre");
這來自 loader.php
這段代碼很多,是為了說明一個小點。 Session 類有一個 closureWithUser 方法,它接受一個閉包並返回另一個。調用時,這個新的閉包將調用原始閉包,提供用戶會話數組作為參數。
要運行所有這些,請在終端中鍵入:
php loader.php
作為旁注,這些預處理器生成的有效 PHP 語法非常漂亮。它看起來像這樣:
$closure = function ($user) { print "user: " . $user["id"] . PHP_EOL; };
……和
public function closureWithUser(Closure $closure) { return [$closure = $closure ?? null, "fn" => function () use (&$closure) { $closure($this->user); }]["fn"]; }
你可能不想將 php 和 pre 文件都提交到倉庫。為此,我已經將 app/**/*.php 和 examples.php 添加到 .gitignore 中。
那麼我們如何測試這個呢?讓我們從安裝 PHPUnit 開始:
composer require --dev phpunit/phpunit
然後,我們應該創建一個配置文件:
<?xml version="1.0" encoding="UTF-8"?> <phpunit backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="false" processIsolation="false" stopOnFailure="false" syntaxCheck="false" > <testsuites> <testsuite> <directory suffix="Test.php">tests</directory> </testsuite> </testsuites> <filter> <whitelist addUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter> </phpunit>
這來自 phpunit.xml
如果我們運行 vendor/bin/phpunit,它將有效。但是我們還沒有任何測試。讓我們做一個:
namespace App\Tests; use App\Session; use PHPUnit\Framework\TestCase; class SessionTest extends TestCase { public function testClosureIsDecorated() { $user = ["id" => 1]; $session = new Session($user); $expected = null; $closure = function($user) use (&$expected) { $expected = "user: " . $user["id"]; }; $closureWithUser = $session ->closureWithUser($closure); $closureWithUser(); $this->assertEquals("user: 1", $expected); } }
這來自 tests/SessionTest.php
當我們運行 vendor/bin/phpunit 時,單個測試通過了。耶!
到目前為止,一切順利。我們編寫了一小段代碼,以及這段代碼的測試。我們甚至不必擔心預處理是如何工作的(比 JavaScript 項目更上一層樓)。
當我們嘗試檢查代碼覆蓋率時,問題就開始了:
vendor/bin/phpunit --coverage-html coverage
由於我們對 Session 進行了測試,因此將報告覆蓋率。它是一個簡單的類,因此我們已經對它實現了 100% 的覆蓋率。但是如果我們添加另一個類:
namespace App; class BlackBox { public function get($key) { return $GLOBALS[$key]; } }
這來自 src/BlackBox.pre
當我們檢查覆蓋率時會發生什麼?仍然是 100%。
發生這種情況是因為我們沒有任何加載 BlackBox.pre 的測試,這意味著它從未被編譯。因此,當 PHPUnit 查找已覆蓋的 PHP 文件時,它看不到這個可預處理的文件。
讓我們創建一個新腳本,在嘗試運行測試之前構建所有 Pre 文件:
composer require pre/short-closures
這來自 tests/bootstrap.php
在這裡,我們創建了 3 個函數;一個用於獲取遞歸文件迭代器(來自路徑),一個用於刪除此迭代器的文件,一個用於重新編譯 Pre 文件。
我們需要替換 phpunit.xml 中當前的 bootstrap 文件:
"autoload": { "psr-4": { "App\": "src" } }, "autoload-dev": { "psr-4": { "App\Tests\": "tests" } }
這來自 phpunit.xml
現在,每當我們運行測試時,此腳本將首先清理並重建所有 Pre 文件到 PHP 文件。覆蓋率被正確報告,我們可以繼續我們的快樂旅程……
我們的代碼庫很小,但它不需要很小。我們可以在真實的應用程序中嘗試這個,並立即後悔每次想要測試時都必須重建文件。
在這個我提到的項目中,我有 101 個 Pre 文件。僅僅為了運行我的(希望很快的)單元測試套件,這就需要大量的預處理。我們需要一種方法來監視更改,並且只重建重要的部分。首先,讓我們安裝一個文件觀察者:
namespace App; use Closure; class Session { private $user; public function __construct(array $user) { $this->user = $user; } public function closureWithUser(Closure $closure) { return () => { $closure($this->user); }; } }
然後,讓我們創建一個測試腳本:
require_once __DIR__ . "/vendor/autoload.php"; $session = new App\Session(["id" => 1]); $closure = ($user) => { print "user: " . $user["id"] . PHP_EOL; }; $closureWithUser = $session->closureWithUser($closure); $closureWithUser();
這來自 scripts/watch-test
該腳本創建一個 Symfony 查找器(用於掃描我們的 src 和 tests 文件夾)。我們定義了一個臨時更改文件,但這對於我們正在做的事情來說並不是嚴格要求的。我們接下來使用一個無限循環。 ResourceWatcher 有一個我們可以用來查看是否創建、修改或刪除了任何文件的方法。
新的,讓我們找到哪些文件已更改,並重建它們:
require_once __DIR__ . "/vendor/autoload.php"; Pre\Plugin\process(__DIR__ . "/example.pre");
這來自 scripts/watch-test
這段代碼類似於我們在 bootstrap 文件中所做的操作,但它只應用於已更改的文件。當文件更改時,我們還應該重新運行測試:
php loader.php
這來自 scripts/watch-test
我們正在引入幾個環境變量。您可以根據自己的喜好管理這些變量,但我更喜歡將它們添加到 composer 腳本中:
$closure = function ($user) { print "user: " . $user["id"] . PHP_EOL; };
這來自 composer.json
APP_COVER 並不是那麼重要。它只是告訴觀察者腳本是否包含代碼覆蓋率。 APP_REBUILD 扮演著更重要的角色:它控制在加載 tests/bootstrap.php 文件時是否重建 Pre 文件。我們需要修改該文件,以便僅在請求時重建文件:
public function closureWithUser(Closure $closure) { return [$closure = $closure ?? null, "fn" => function () use (&$closure) { $closure($this->user); }]["fn"]; }
這來自 tests/bootstrap.php
我們還需要修改觀察者腳本,以便在包含 bootstrap 代碼之前設置此環境變量。整個觀察者腳本如下所示:
composer require --dev phpunit/phpunit
這來自 scripts/watch-test
現在我們應該能夠啟動它,並在每次可預處理文件更改時運行我們的測試……
需要記住幾件事(rawr)。首先,您需要 chmod x scripts/* 才能運行觀察者腳本。其次,您需要設置 config: {process-timeout: 0}(在 composer.json 中),否則觀察者將在 300 秒後死亡。
這個測試觀察者還啟用了一個很酷的副作用:能夠在我們的 PHPUnit 測試中使用預處理器/轉換。如果我們向 tests/bootstrap.php 添加一些代碼:
composer require pre/short-closures
這來自 tests/bootstrap.php
……並且我們在測試文件中啟用預處理(對於 Pre,這意味著將其重命名為 .pre)。然後我們可以開始在我們的測試文件中使用相同的預處理器:
"autoload": { "psr-4": { "App\": "src" } }, "autoload-dev": { "psr-4": { "App\Tests\": "tests" } }
這來自 tests/SessionTest.pre
我不敢相信在嘗試創建這種測試觀察者之前,我已經做了這麼多的預處理器工作。這證明了我們可以從其他語言和框架中學到什麼。如果我沒有參與那些 JavaScript 項目,我可能會繼續在每次測試運行之前重建我的文件。噁心!
這種方法對您有效嗎?它可以適應異步 HTTP 服務器或其他長期運行的進程。請在評論中告訴我們您的想法。
在 PHP 中設置 JavaScript 風格的測試觀察者涉及多個步驟。首先,您需要安裝 PHPUnit 和 PHPUnit-Watcher。 PHPUnit 是一個用於 PHP 的測試框架,它提供了一種為代碼編寫測試的方法。 PHPUnit-Watcher 是一個監視您的代碼並在保存文件時運行 PHPUnit 測試的工具。安裝這些工具後,您可以配置 PHPUnit-Watcher 來監視您的代碼並自動運行您的測試。此設置允許您立即獲得代碼更改的反饋,這可以幫助您更快地發現和修復錯誤。
在 PHP 中使用測試觀察者有很多好處。它提供代碼更改的即時反饋,這可以幫助您更快地發現和修復錯誤。它還可以節省您的時間,因為您不必在每次代碼更改後手動運行測試。此外,它鼓勵您為代碼編寫測試,這可以提高代碼質量並使其更易於維護。
是的,您可以在 JavaScript 函數內使用 PHP 代碼,但不建議這樣做。 PHP 是一種服務器端語言,而 JavaScript 是一種客戶端語言。這意味著 PHP 代碼在頁面發送到客戶端之前在服務器上執行,而 JavaScript 代碼在頁面接收後在客戶端執行。因此,如果您嘗試在 JavaScript 函數內使用 PHP 代碼,PHP 代碼將在 JavaScript 函數之前執行,這可能會導致意外結果。
Codeception 是一個用於 PHP 的測試框架,它支持單元測試、功能測試和驗收測試。要使用 Codeception 測試您的 PHP 代碼,您首先需要安裝 Codeception 並為您的項目配置它。然後,您可以使用 Codeception 的語法為您的代碼編寫測試,並使用 Codeception 的命令行工具運行您的測試。
雖然從技術上講可以在 JavaScript 中編寫 PHP 代碼,但不建議這樣做。 PHP 是一種服務器端語言,而 JavaScript 是一種客戶端語言。這意味著 PHP 代碼在頁面發送到客戶端之前在服務器上執行,而 JavaScript 代碼在頁面接收後在客戶端執行。因此,如果您嘗試在 JavaScript 中編寫 PHP 代碼,PHP 代碼將在 JavaScript 代碼之前執行,這可能會導致意外結果。相反,最好使用 AJAX 將數據從客戶端發送到服務器,反之亦然。
以上是如何在PHP中寫JavaScript風格的測試觀察者的詳細內容。更多資訊請關注PHP中文網其他相關文章!