使用 Codeception 快速啟動您的 PHP 測試
已發表: 2022-03-11在繼續討論 Codeception 和 PHP 之前,我們應該先了解基礎知識,並首先解釋為什麼我們需要在應用程序中進行測試。 也許我們可以在不浪費時間進行測試的情況下完成一個項目,至少這次是這樣?
當然,您不需要對所有內容進行測試。 例如,當您想構建另一個主頁時。 當您的項目包含由一個路由器鏈接的靜態頁面時,您可能不需要測試。
但是,您肯定需要在以下情況下進行測試:
- 您的團隊使用 BDD/TDD。
- 您的 Git 存儲庫包含多個提交。
- 你是一個合格的專業人士,正在從事一個嚴肅的項目。
你可以說你已經有一個專門的測試部門,一組進行測試並在需要時編寫新測試的人來為自己辯解。 但是,您能想像在您為項目添加新功能後需要多長時間修復錯誤嗎?
測試能解決什麼問題?
首先,讓我們決定通過測試可以解決什麼樣的問題。 您無法通過測試擺脫所有錯誤,但您可以描述測試用例中的預期行為。 錯誤可能在您的測試用例中。 即使您使用測試用例,意大利麵條代碼仍然是意大利麵條代碼。
但是,您可以確定您的代碼會在之後更改(通過修復錯誤或添加新功能),因此您的代碼仍然不會出現測試中描述的錯誤。 此外,有時甚至可以在文檔中使用編寫良好的測試,因為在那裡您可以看到典型場景如何展開並檢查預期行為。 可以說,測試是對未來的一項小而重要的投資。
那麼我們可以採用什麼樣的測試呢?
- 單元測試:檢查一小部分代碼的低級測試——你的類的方法與其他代碼隔離。
- 集成測試:集成測試檢查應用程序的一部分,它們可能包含多個類或方法,但應僅限於一個功能。 該測試還應檢查不同類的交互方式。
- 功能測試:測試對您的應用程序的特定請求:瀏覽器響應、數據庫更改等。
- 驗收測試:在大多數情況下,驗收測試意味著檢查應用程序是否滿足所有客戶要求。
為了澄清,假設我們用有形的東西來說明這個過程,比如建築物。 建築物由形成牆壁的小塊組成。 每塊磚都必須符合規定的要求; 它必須承受所需的負載,具有特定的體積和形狀,等等。 這些是單元測試。 集成測試的想法是檢查磚塊之間的緊密度和準確性,以及它們如何集成到建築物的某個元素中。 功能測試可以比作在建築物的一面牆上進行測試,檢查內部是否受到保護,以及是否可以通過窗戶看到陽光。 驗收測試涉及將整個建築作為一個完整的產品進行測試:打開門,進去,關上門,開燈,爬到二樓,看看樓外的花園。
認識 Codeception
然而,這種劃分是有條件的,有時很難抵制混合不同類型測試的誘惑。
許多開發人員使用單元測試並聲稱這就足夠了。 我曾經是這樣的開發者之一; 我發現對不同類型的測試使用不同的系統既困難又費時。 前段時間,我決定找一些比 PHPUnit 更有用的東西; 我想更好地測試我的代碼,但我不想閱讀和學習大量文檔並尋找陷阱。 這就是我發現 Codeception 的方式。 起初,我很懷疑,就像我們經常遇到新事物一樣(這個項目已有五年曆史,所以從技術上講,它不能被認為是“新的”),但在玩了幾天之後,我總結 Codeception 是一個非常有用和強大的系統。
那麼如何安裝 Codeception? 它很簡單:
$ composer require "codeception/codeception" $ php vendor/bin/codecept bootstrap
安裝完成後,你會在你的項目中找到一個名為tests的新文件夾,其中會有一些名為accept 、functional和unit的子文件夾。 看起來我們可以開始編寫測試了。 很酷,但下一步是什麼?
現在,嘗試添加一個標準驗收Hello World 測試。
$ php vendor/bin/codecept generate:cept acceptance HelloWorld
現在,我們得到一個驗收測試文件tests/acceptance/HelloWorldCept.php,內容如下:
<?php $I = new AcceptanceTester($scenario); $I->wantTo('perform actions and see result');
名為$I
的默認變量不僅僅是一個字母; 它是一個字符。 什麼進行測試? 測試員,很明顯。 該測試人員打開您網站的頁面或課程,對其進行處理,並向您展示其操作的最終結果。 你會看到什麼有效,什麼出了問題。 這就是為什麼這個對像被命名為$I
以及為什麼它包含名為wantTo()
、 see()
或amOnPage()
的方法。
因此,讓我們像測試人員一樣思考檢查頁面可操作性的方法。 第一種方法是打開頁面並蒐索短語。 它證明該頁面可供訪問者使用。
這應該很容易:
<?php $I->amOnPage('/'); $I->see('Welcome');
我們可以使用這個命令來運行 Codeception 的測試:
$ php vendor/bin/codecept run
我們立即看到有什麼地方不對勁。 乍一看,信息似乎太長且不清楚,但當我們仔細觀察時,一切都變得顯而易見。
我們有一個測試Acceptance ,它檢測到一個錯誤:
Acceptance Tests (1) Perform actions and see result (HelloWorldCept) Error ---------- 1) Failed to perform actions and see result in HelloWorldCept (tests/acceptance/HelloWorldCept .php) [GuzzleHttp\Exception\ConnectException] cURL error 6: Could not resolve host: localhost (see http://curl.haxx.se/libcurl/c/libcurl-errors.html)
這是罪魁禍首: localhost不可用。
以下是我們測試的場景步驟:
1. $I->amOnPage("/")
好的,讓我們打開 tests/acceptance.suite.yml 並將url: http://localhost/
更改為實際可用的內容。 就我而言,它是我的本地測試主機, url: https://local.codeception-article.com/
再次運行測試,這就是你應該得到的結果:
Acceptance Tests (1) --------------------------------------------------------------------------------------- Perform actions and result (HelloWorldCept) Ok
萬歲! 我們的第一次成功測試!
當然, amOnPage()
不是唯一可用的測試方法,我們只是將其單獨用於我們的示例。 所有 Codeception 測試方法都可以分為以下幾組:
- 與頁面交互:
fillField()
、selectOption()
、submitForm()
、click()
- 斷言。
see()
,dontSee()
,seeElement()
,seeInCurrentUrl()
,seeCheckboxIsChecked()
,seeInField()
,seeLink()
。 對於所有這些方法,您可以添加一個後綴,並在您需要一種在找不到某些東西時不會中斷測試場景的方法時使用它。 - Cookie 方法:
setCookie()
、grabCookie()
、seeCookie()
- 測試場景的評論和描述:
amGoingTo()
,wantTo()
,expect()
。 使用這些方法來獲得很好的評論和描述的測試,這將幫助您記住測試的目標。
因此,如果我們要測試密碼重置電子郵件頁面,我們可以這樣做:
<?php $I = new AcceptanceTester($scenario); $I->wantTo('Test forgotten password functionality'); $I->amOnPage('/forgotten') $I->see('Enter email'); $I->fillField('email', '[email protected]'); $I->click('Continue'); $I->expect('Reset password link not sent for incorrect email'); $I->see('Email is incorrect, try again'); $I->amGoingTo('Fill correct email and get link'); $I->see('Enter email'); $I->fillField('email', '[email protected]'); $I->click('Continue'); $I->expect('Reset password link sent for correct email'); $I->see('Please check your email for next instructions');
看起來應該這樣做,但是如果頁面上有一些 Ajax 加載的部分怎麼辦? 我們可以測試這樣的頁面嗎? 答案是 Codeception 默認使用基於 Symfony BrowserKit 和 Guzzle 的 PhpBrowser。 它簡單、快速,您只需要 curl 即可使用它。

您還可以使用 Selenium 並在真實瀏覽器中測試頁面。 是的,它會更慢,但您也可以測試 JavaScript。
首先,您需要安裝 Selenium 驅動程序,更改 Acceptance.suite.yml 並重建 AcceptanceTester 類。 在此之後,您可以使用方法wait()
和waitForElement()
。 而且,更有趣的是,您將能夠通過使用方法saveSessionSnapshot()
和loadSessionSnapshot()
來節省時間和資源。 此方法允許您存儲會話狀態並在較早的會話中開始新的測試。 這在某些情況下很有用,例如在測試授權過程中。
因此,我們最終獲得了一種簡單而強大的測試許多功能的能力。
功能測試
好的,是時候進行功能測試了。
$ php vendor/bin/codecept generate:cept functional HelloWorld
這就是我們得到的:
<?php $I = new FunctionalTester($scenario); $I->amOnPage('/'); $I->see('Welcome');
等等,什麼?
不,這不是一個錯誤。 功能測試應該以與集成測試相同的方式編寫。 不同之處在於功能測試直接與您的應用程序交互。 這意味著您不需要網絡服務器來運行功能測試,並且您有更多的能力來測試應用程序的不同部分。
這確實意味著缺乏對所有框架的支持,但支持的框架列表非常廣泛:Symfony、Silex、Phalcon、Yii、Zend Framework、Lumen、Laravel。 對於大多數情況和大多數開發人員來說,這應該足夠了。 請查閱 Codeception 的模塊文檔以獲取可用功能的列表,然後在functional.suite.yml
中將其打開。
在我們進行單元測試之前,請允許我做一點題外話。 您可能已經註意到,我們使用關鍵 cept 創建了測試:
$ php vendor/bin/codecept generate: cept acceptance HelloWorld
這不是創建測試的唯一方法。 還有cest 測試。 不同之處在於您可以在一個類中構建多個相關場景:
$ php vendor/bin/codecept generate:cest acceptance HelloWorld <?php class HelloWorldCest { public function _before(AcceptanceTester $I) { $I->amOnPage('/forgotten') } public function _after(AcceptanceTester $I) { } // tests public function testEmailField(AcceptanceTester $I) { $I->see('Enter email'); } public function testIncorrectEmail(AcceptanceTester $I) { $I->fillField('email', '[email protected]'); $I->click('Continue'); $I->see('Email is incorrect, try again'); } public function testCorrectEmail(AcceptanceTester $I) { $I->fillField('email', '[email protected]'); $I->click('Continue'); $I->see('Please check your email for next instructions'); } }
在此示例中,方法_before()
和_after()
在每次測試之前和之後運行。 AcceptanceTester
類的實例被傳遞給每個測試,因此您可以像在 cest 測試中一樣使用它。 這種類型的測試在某些情況下可能很有用,因此值得牢記。
單元測試
是時候進行一些單元測試了。
Codeception 基於 PHPUnit,因此您可以使用為 PHPUnit 編寫的測試。 要添加新的 PHPUnit 測試,請使用以下方法:
$ php vendor/bin/codecept generate:phpunit unit HelloWorld
或者只是繼承您在\PHPUnit_Framework_TestCase
上的測試。
但如果你想要更多,你應該試試 Codeception 的單元測試:
$ php vendor/bin/codecept generate:test unit HelloWorld <?php class HelloWorldTest extends \Codeception\TestCase\Test { /** * @var \UnitTester */ protected $tester; protected function _before() { } protected function _after() { } // tests public function testUserSave() { $user = User::find(1); $user->setEmail('[email protected]'); $user->save(); $user = User::find(1); $this->assertEquals('[email protected]', $user->getEmail()); } }
目前沒有什麼不尋常的。 _before()
和_after()
方法是setUp()
和tearDown()
的類似方法,將在每次測試之前和之後運行。
該測試的主要優勢在於它能夠通過包含可以在unit.suite.yml
中打開的模塊來擴展您的測試過程:
- 訪問 memcache 和數據庫以跟踪更改(支持 MySQL、SQLite、PostgreSQL、MongoDB)
- 測試 REST/SOAP 應用程序
- 隊列
每個模塊都有自己的功能,因此最好在進行實際測試之前檢查文檔並收集每個模塊的必要信息。
另外,您可以使用 Codeception/Specify 包(需要將其添加到composer.json
)並編寫如下描述:
<?php class HelloWorldTest extends \Codeception\TestCase\Test { use \Codeception\Specify; private $user; protected function _before() { $this->user = User::find(1); } public function testUserEmailSave() { $this->specify("email can be stored", function() { $this->user->setEmail('[email protected]'); $this->user->save(); $user = User::find(1); $this->assertEquals('[email protected]', $user->getEmail()); }); } }
這些閉包函數內部的 PHP 代碼是隔離的,因此內部的更改不會影響您的其餘代碼。 描述將幫助您使測試更具可讀性,並更容易識別失敗的測試。
作為可選的附加功能,您可以使用包Codeception\Verify
來實現類似 BDD 的語法:
<?php public function testUserEmailSave() { verify($map->getEmail())->equals('[email protected]'); }
當然,您可以使用存根:
<?php public function testUserEmailSave() { $user = Stub::make('User', ['getEmail' => '[email protected]']); $this->assertEquals('[email protected]', $user->getEmail()); }
結論:Codeception 節省時間和精力
那麼你應該對 Codeception 有什麼期待呢? 它是給誰的? 有什麼注意事項嗎?
在我看來,這個測試框架適合各種不同的團隊:大大小小的、初學者和久經沙場的 PHP 專業人士、使用流行框架的人以及不使用任何框架的人。
無論如何,這一切都歸結為: Codeception 已準備好迎接黃金時段。
它是一個成熟且文檔齊全的框架,可以很容易地被許多模塊擴展。 Codeception 是現代的,但基於經過時間考驗的 PHPUnit,這應該可以讓不想進行過多實驗的開發人員放心。
它表現良好,這意味著它速度很快,不需要太多時間和精力。 更好的是,它相對容易掌握,豐富的文檔應該有助於輕鬆的學習過程。
Codeception 也易於安裝和配置,但它擁有許多高級選項。 雖然大多數用戶不需要全部(或者實際上是大多數),但這完全取決於您打算用它做什麼。 您可以從基礎開始,額外的功能遲早會派上用場。