我在 2019 年 WordCamp 卡塔尼亞研討會的儲存庫
可選,但您可能需要安裝 docker:
sudo apt-get install docker-ce docker-ce-cli containerd.io
我假設你已經安裝了 Composer。我們先安裝 PHPUnit:
composer require --dev phpunit/phpunit ^8.3
請同時檢查要求!
PHPUnit 8.3 至少需要 PHP 7.2!順便一提 - PHP 7.1 的安全支援將於 2019 年 12 月 1 日結束。
提示:您沒有安裝Composer ?試試這個!
docker run --rm -it -v $PWD:/app -u $(id -u):$(id -g) composer install
當您計劃測試 WordPress 擴充功能時,至少有兩個有效的框架可以派上用場:
讓我們試試看大腦猴子:
composer require --dev brain/monkey:2.*`
這也會自動安裝 Mockery 和 Patchwork。只要執行composer install
就可以了。
建立一個目錄,為名為WcctaTest.php的小型測試類別提供目錄:
mkdir -p tests/wccta
出色的!現在讓我們在根目錄中建立一個phpunit.xml設定檔。
您也可以決定使用命令列中的設定參數來執行測試。請參閱下一部分(提示:“腳本”)!
偉大的!將一些部分添加到composer.json文件中:
composer test
讓我們建立一個目錄來存放我們的原始碼。這是您將很快測試的第一類的地方。
mkdir -p src/wccta && touch src/wccta/Plugin.php
rm -f tests/wccta/WcctaTest.php && touch tests/wccta/PluginTest.php
touch wordpress-plugins-phpunit.php
我們想要測試Plugin
類別的一些方法。想像一個名為is_loaded
的方法,成功時回傳true
。準備好後,執行:
composer test
提示:您的系統或 PHP 版本不是最新的?您可以跳過這一步,但讓我們嘗試一些[不那麼]新的東西!
docker run -it --rm -v $PWD:/app -w /app php:7.3-alpine php ./vendor/bin/phpunit
您可能會想像某些插件將有很多類,並且您很容易忘記測試所有需要測試的功能。
那麼,我們來談談覆蓋範圍吧!
只需將自訂命令新增至composer.json中的腳本部分:
"coverage": "./vendor/bin/phpunit --coverage-html ./reports/php/coverage"
和phpunit.xml的過濾器:
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory>./src</directory>
</whitelist>
</filter>
現在只需執行composer coverage
!這將建立一個目錄./reports/php/coverage
以及一些 html 檔案。嗯,不是在所有計算機上。有些仍然會收到錯誤訊息,例如:
Error: No code coverage driver is available
讓我們在 docker-image 中修復這個問題。我準備了一個Dockerfile,這樣你就可以執行:
docker build -t coverage .
建置過程完成後:
docker run -it --rm -v $PWD:/app -w /app coverage:latest php ./vendor/bin/phpunit --coverage-html ./reports/php/coverage
現在你知道功夫了!請在瀏覽器中開啟檔案./reports/php/coverage/index.html !
讓我們將Plugin
類別連接到插件。在我們真正開始測試之前,我將向您展示如何將部分程式碼聲明為不進行測試。
@codeCoverageIgnore
這是可用的重要註釋之一。我們稍後再討論這個問題,但首先:
再次使用覆蓋率報告執行單元測試!
您可能確實注意到了覆蓋率報告中的CRAP
欄。 CRAP是變更風險反模式的縮寫。它表明更改類別或方法中的程式碼可能會有多大風險。您可以透過不太複雜的程式碼和全面的測試覆蓋來降低風險(從而降低指數)。
讓我們開始測試一些東西。但什麼?仍然沒有編寫需要測試的更多功能。
TDD(測試驅動開發)登場了。
即使您決定不使用此技術,您至少應該知道我們在說什麼。
我們先建立一個 Test CarTest
來測試get_price
方法是否回傳字串'€ 14.500'
。然後建立一個Class Car
,寫一個滿足測試的方法get_price
。不要從實施開始。
這裡我還要介紹TDD中廣泛接受的測試模式AAA (Arrange Act Assert)。它描述瞭如何安排測試,與 BDD(行為驅動開發)中的GWT (Given When then)非常相似。
您可以測試您的類別是否在某些條件下拋出異常。現在讓我們實作get_price
方法。
只需建立一個類別Registry
,將混合值設定為內部數組中的命名項目。為此,請使用方法set()
或魔術方法__set()
。首先假設我們可以將 JSON 物件傳遞給我們的Car
類別。這會為我們的班級帶來更多的價值。
另一個方法get
或__get()
應該檢查給定的項目是否存在並在成功時返回。如果沒有這樣的項目,則拋出OutOfBoundsException
。現在編寫一個建構函數來處理 JSON 輸入並將物件儲存在 member-var data
中。 get_price
方法應該從data
變數中取得價格並處理格式化輸出。
如果您在編寫程式碼時遇到困難,請檢查分支步驟 10 !變數price
應該是整數。現在這可能不是問題,因為您可以使用 PHP 函數number_format()
來建立正確的輸出。但在WordPress安裝中,您需要設定區域設置,例如it_IT
(義大利語)。
在WordPress中格式化數字的正確方法是使用函數number_format_i18n()
。
那麼讓我們改變一下,看看會發生什麼:
Error: Call to undefined function wcctanumber_format_i18n()
我們稍後會解決這個問題,但讓我們先做一些準備。 Brain Monkey使用PHPUnit提供的setUp()
和tearDown()
。您可以重寫這些方法。讓我們建立一個自訂TestCase
- 將其命名為WcctaCase
- 我們可以擴展它,因為我們可能會在每個測試類別中執行此操作。
現在讓我們在 autoload-dev 部分中包含測試的命名空間:
"autoload-dev": {
"psr-4": {
"tests\wccta\": "tests/wccta"
}
},
最後,讓我們更改測試類別的父類別。
class CarTest extends WcctaTestCase { // ... }
我們準備好模擬我們的第一個WordPress函數了
Functionsexpect( $name_of_function )->andReturn( $value );
僅針對一種期望編寫測試似乎需要花費太多精力。如果您想測試不同的值怎麼辦?
數據提供商來救援。我已經在第 5 步中談到了註釋。
@dataprovider method_that_returns_data
看看我的例子。 getData
傳回一個陣列的陣列。每個數組都包含 3 個值。我們的test_getPrice
方法不僅可以接受帶有註解的資料提供者,還可以將輸入變數定義為參數。
您可以測試您的類別是否在某些條件下拋出異常。
只需建立一個類別Registry
,將混合值設定為內部數組中的命名項目。為此,請使用方法set()
或魔術方法__set()
。
另一個方法get
或__get()
應該檢查具有給定鍵的項目是否存在並在成功時返回它。如果沒有這樣的項目,則拋出OutOfBoundsException
。
如果您在編寫程式碼時遇到困難,請檢查分支步驟 10 !
最後一步將我們帶到了工廠。什麼是工廠?有時,您建立的函數或方法只是隱藏建立特定物件的複雜過程。有時您必須決定要建立哪種類型的物件。
在 WordPress 外掛中,我更喜歡將工廠中的鉤子添加到物件中。有一些插件可以在類別建構函數中添加掛鉤。這不是一件好事(特別是當您仍在測試經典方法時 - 建立一個啟動並運行 WordPress 的完整環境)。
讓我們建立一個帶有名為create
的靜態函數的類別Factory
。此方法應傳回一個Car
物件。但是讓我們重構Car
的建構函數,使其需要一個物件而不是 JSON 字串。我們將在Factory
類別的 create 方法中執行此操作。
現在使用composer test
測試您的插件,您會看到一些錯誤:
TypeError: Argument 1 passed to wcctaCar::__construct() must be an object, string given, called in ...
我們也應該修正我們的測試......
出色的!讓我們為我們的工廠創建一個測試。我們暫時讓方法不包含任何內容。再次運行測試!
There was 1 risky test:
1) testswcctaFactoryTest::test_create
This test did not perform any assertions
測試通過了,但您收到訊息表示測試有風險。順便一提:將函數命名為test_create
just create
並使用註解@test
。我相信該註釋的使用取決於您的個人品味!
我們現在將更深入地探討這一點。
建立一個介面FooterInterface
,定義一個不期望任何傳回值的公共方法info
。在Car
中實作接口, info
可以 - 例如 - 輸出一個有趣的訊息。
為Factory
的create
方法定義傳回類型FooterInterface
,並將Car
的info
方法加入 WordPress-Action wp_footer
。
現在讓我們在FactoryTest
中測試一下。至少有兩種方法可以正確測試這一點。使用 has_action 或ActionsexpectAdded()
。過濾器的測試是類似的,並且在連結頁面上有很好的描述。
檢查composer test
是否仍然通過所有測試。
目前覆蓋情況如何?執行composer coverage
並檢查產生的輸出。
我們的Car
類別的info
方法沒有被任何測試覆蓋。但是我們可以測試方法的輸出嗎?
事實證明,使用expectOutputString 非常容易。
讓我們慶祝我們學到的東西!
建立一個Locale
類,該類別具有傳回get_locale()
的公共方法get
。從覆蓋範圍中排除該方法!
現在在我們的Plugin
類別中建立一個接受Locale
實例的建構函數,並將其儲存在 member-var $this->locale
中。然後建立一個get_region_code
方法,該方法傳回$this->locale->get()
的值。啊,刪除is_loaded
方法。 ;)
在我們的測試中,我們可以建立一個Locale
類型的對象,模擬 WordPress 函數get_locale
並將其傳遞給Plugin
建構子!但我想在這裡使用 Mocker:
public function test_get_region_code() {
$code = 'it_IT';
$locale = Mockery::mock( Locale::class );
$locale->shouldReceive( 'get' )->andReturn( $code );
$sut = new Plugin( $locale );
$this->assertEquals( $code, $sut->get_region_code() );
}
現在您可以讓您的 WordPress 外掛防彈了!
玩得開心!