Laravel如何進行自動化測試?下面本篇文章通過使用 PHPUnit 和 PEST 的簡單示例來了解如何在 Laravel 中開始測試,希望對大家有所幫助。
在我們談論任何編程語言的自動化測試或單元測試時,一般有兩類人:
那些不編寫自動化測試并認為他們在浪費時間的人
那些寫測試然后無法想象沒有他們的工作的人
所以,通過這篇文章,我將試圖說服前者,看看它的好處,看看在 Laravel 中開始自動化測試是多么容易。
首先,讓我們談談「為什么」,然后我將展示一些非常基本的「如何做測試」的示例。
為什么需要自動化測試
自動化測試并不復雜:它們只是為你運行部分代碼并報告任何錯誤。這是描述它們的最簡單的方式。想象一下,你正在應用程序中啟動一項新功能,然后一個機器人助理會為你手動測試新功能,同時測試新代碼是否不會破壞舊功能的任何內容。
這樣的好處是:自動重新測試所有功能。這似乎是額外的工作,但如果你不告訴那個「機器人」去做,那么你應該自己手動做,對吧?或者你在沒有詳細測試的情況下推出新功能,希望用戶報告錯誤?我諷刺地稱這種方法為「手指交叉驅動的開發」。
隨著應用程序的每一項新功能,自動化測試的回報越來越高。
功能 1:手動節省 X 分鐘的測試時間
功能 2:節省 2X 分鐘 - 再次用于功能 2 和功能 1
功能 3:節省 3X 分鐘…
等等。
你應該明白了。想象一下你的應用程序在一兩年內,團隊中的新開發人員甚至不知道「功能 1」如何運行或如何重現它以進行測試。所以,你未來的自己會非常感謝你編寫自動化測試。
當然,如果你認為你的項目是一個非常短期的項目,并且你不太關心它的未來…… 不,我相信你的好意,所以讓我告訴你開始測試是多么容易。
開始我們第一個自動化測試
要在 Laravel 中運行第一個自動化測試,你不需要編寫任何代碼。是的,你沒看錯。一切都已經在默認的 Laravel 安裝中進行了配置和準備,包括第一個真正的基本示例。
你可以嘗試安裝一個 Laravel 項目并立即運行第一個測試:
laravel new project cd project php artisan test
按照正常預期,終端將會輸出如下結果:
PASS Tests\Unit\ExampleTest ? that true is true PASS Tests\Feature\ExampleTest ? the application returns a successful response Tests: 2 passed Time: 0.10s
如果我們看一下默認的 Laravel /tests
文件夾,其中有兩個文件。
tests/Feature/ExampleTest.php:
class ExampleTest extends TestCase { public function test_the_application_returns_a_successful_response() { $response = $this->get('/'); $response->assertStatus(200); } }
你無需了解任何語法即可讀懂這段代碼的含義:加載主頁并檢查 HTTP 狀態代碼是否「200 OK」。
你需要注意:在查看測試結果時,方法名稱 test_the_application_returns_a_successful_response()
如何變為可讀文本,只需將下劃線符號替換為空格即可。
tests/Unit/ExampleTest.php:
class ExampleTest extends TestCase{ public function test_that_true_is_true() { $this->assertTrue(true); } }
這樣的代碼看上去讓人感覺毫無意義,檢查結果為 true 很必要嗎?在后面片段中,我們將具體討論單元測試。現在,你只需要了解每次測試中通常發生的情況。
tests/
文件夾中的每個測試文件都是一個 PHP 類,擴展了 PHPUnit 的 TestCase在每個類中,你可以創建多個方法,通常一種方法用于一種情況進行測試
每個方法內部都有三個動作:準備情況,然后動作,然后檢查(斷言)結果是否符合預期
從結構上講,這就是你需要知道的全部內容,其他一切都取決于你要測試的確切內容。
要生成一個空的測試類,只需運行以下命令:
php artisan make:test HomepageTest
它會生成文件 tests/Feature/HomepageTest.php
:
class HomepageTest extends TestCase{ // Replace this method with your own ones public function test_example() { $response = $this->get('/'); $response->assertStatus(200); } }
如果測試失敗怎么辦?
讓我向你展示如果測試斷言沒有返回預期結果會發生什么。
讓我們將示例測試編輯為:
class ExampleTest extends TestCase { public function test_the_application_returns_a_successful_response() { $response = $this->get('/non-existing-url'); $response->assertStatus(200); } } class ExampleTest extends TestCase { public function test_that_true_is_false() { $this->assertTrue(false); } }
現在,如果我們再次運行 php artisan test
:
FAIL Tests\Unit\ExampleTest ? that true is true FAIL Tests\Feature\ExampleTest ? the application returns a successful response --- ? Tests\Unit\ExampleTest > that true is true Failed asserting that false is true. at tests/Unit/ExampleTest.php:16 12▕ * @return void 13▕ */ 14▕ public function test_that_true_is_true() 15▕ { ? 16▕ $this->assertTrue(false); 17▕ } 18▕ } 19▕ ? Tests\Feature\ExampleTest > the application returns a successful response Expected response status code [200] but received 404. Failed asserting that 200 is identical to 404. at tests/Feature/ExampleTest.php:19 15▕ public function test_the_application_returns_a_successful_response() 16▕ { 17▕ $response = $this->get('/non-existing-url'); 18▕ ? 19▕ $response->assertStatus(200); 20▕ } 21▕ } 22▕ Tests: 2 failed Time: 0.11s
如你所見,有兩個語句標記為 FAIL,下面有解釋,箭頭指向斷言失敗的確切測試行。所以這就是錯誤的顯示方式。這非常的方便,不是嗎?
簡單示例:注冊表單
讓我們來看看一個現實生活中常見的例子。假設你有一個表單,你需要測試各種情況:檢查是否填充無效數據是否失敗,檢查是否輸入正確輸入成功等。
你不一定知道,其實官方的 Laravel Breeze 入門套件附帶了內部功能測試?現在,讓我們從那里看幾個例子:
tests/Feature/RegistrationTest.php
use App\Providers\RouteServiceProvider; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class RegistrationTest extends TestCase { use RefreshDatabase; public function test_registration_screen_can_be_rendered() { $response = $this->get('/register'); $response->assertStatus(200); } public function test_new_users_can_register() { $response = $this->post('/register', [ 'name' => 'Test User', 'email' => 'test@example.com', 'password' => 'password', 'password_confirmation' => 'password', ]); $this->assertAuthenticated(); $response->assertRedirect(RouteServiceProvider::HOME); } }
在這里,我們在一個類中有兩個測試,因為它們都與注冊表相關:一個是檢查表單是否正確加載了,另一個是檢查提交是否正常。
我們來熟悉另外兩個檢查結果的方法,另外兩個斷言: $this->assertAuthenticated()
和 $response->assertRedirect()
。 你可以查看 PHPUnit and Laravel Response 官方文檔中所有可用的斷言。請記住,一些一般的斷言發生在 $this
對象上,而另一些檢查則來自于路由調用的特定 $response
語句。
另一件重要的事情是 use RefreshDatabase;
語句,使用這個 trait,包含在這個類的上方。當你的測試操作可能會影響數據庫時,需要使用它,例如在本例中,注冊會在 users
數據庫表中添加一個新條目。為此,你需要創建一個單獨的測試數據庫,該數據庫將會在每次測試中使用 php artisan migrate:fresh
命令時被刷新。
你有兩個選擇:物理上創建一個單獨的數據庫,或者使用內存中的 SQLite 數據庫。它都在 Laravel 默認提供的文件 phpunit.xml
中配置。具體來說, 你需要這部分:
<php> <env name="APP_ENV" value="testing"/> <env name="BCRYPT_ROUNDS" value="4"/> <env name="CACHE_DRIVER" value="array"/> <!-- <env name="DB_CONNECTION" value="sqlite"/> --> <!-- <env name="DB_DATABASE" value=":memory:"/> --> <env name="MAIL_MAILER" value="array"/> <env name="QUEUE_CONNECTION" value="sync"/> <env name="SESSION_DRIVER" value="array"/> <env name="TELESCOPE_ENABLED" value="false"/> </php>
看到被注釋掉的 DB_CONNECTION
和 DB_DATABASE
了嗎?如果你的服務器上有 SQLite,最簡單的操作就是取消注釋這些行,你的測試將在該內存數據庫上運行。
在本次測試中,我們斷言用戶通過了身份驗證,并被重定向到正確的首頁,但我們也可以測試數據庫中真實的數據。
除此代碼之外:
$this->assertAuthenticated(); $response->assertRedirect(RouteServiceProvider::HOME);
我們也可以使用 Database Testing assertions 并執行以下操作:
$this->assertDatabaseCount('users', 1); // 或者... $this->assertDatabaseHas('users', [ 'email' => 'test@example.com', ]);
另外一個真實示例:登錄表單
讓我們看看另外一個來自 Laravel Breeze 的測試。
tests/Feature/AuthenticationTest.php:
class AuthenticationTest extends TestCase { use RefreshDatabase; public function test_login_screen_can_be_rendered() { $response = $this->get('/login'); $response->assertStatus(200); } public function test_users_can_authenticate_using_the_login_screen() { $user = User::factory()->create(); $response = $this->post('/login', [ 'email' => $user->email, 'password' => 'password', ]); $this->assertAuthenticated(); $response->assertRedirect(RouteServiceProvider::HOME); } public function test_users_can_not_authenticate_with_invalid_password() { $user = User::factory()->create(); $this->post('/login', [ 'email' => $user->email, 'password' => 'wrong-password', ]); $this->assertGuest(); } }
這是關于登錄表單的例子。他的邏輯和注冊差不多吧?但不一樣的是使用了三個方法而不是兩個,所以這是一個測試好的和壞的場景的例子。所以,他們共同的邏輯是你應該測試的兩種情況:什么時候順利,什么時候失敗。
此外,你在這個測試中看到的是 Database 工廠類 的使用:Laravel 創建了一個假用戶(再次, 在你的測試數據庫刷新) 上,然后嘗試使用正確或不正確的憑據登錄。
同樣,Laravel 為 User
模型生成帶有假數據的默認工廠,開箱即用。
database/factories/UserFactory.php:
class UserFactory extends Factory { public function definition() { return [ 'name' => $this->faker->name(), 'email' => $this->faker->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; } }
看,有多少東西是 Laravel 本身提供的,所以我們很容易開始測試。
因此,如果我們在安裝 Laravel Breeze 后運行 php artisan test
, 我們應該會看到如下內容:
PASS Tests\Unit\ExampleTest ? that true is true PASS Tests\Feature\Auth\AuthenticationTest ? login screen can be rendered ? users can authenticate using the login screen ? users can not authenticate with invalid password PASS Tests\Feature\Auth\EmailVerificationTest ? email verification screen can be rendered ? email can be verified ? email is not verified with invalid hash PASS Tests\Feature\Auth\PasswordConfirmationTest ? confirm password screen can be rendered ? password can be confirmed ? password is not confirmed with invalid password PASS Tests\Feature\Auth\PasswordResetTest ? reset password link screen can be rendered ? reset password link can be requested ? reset password screen can be rendered ? password can be reset with valid token PASS Tests\Feature\Auth\RegistrationTest ? registration screen can be rendered ? new users can register PASS Tests\Feature\ExampleTest ? the application returns a successful response Tests: 17 passed Time: 0.61s
功能測試 VS 單元測試 VS 其他
你已經看到了 tests/Feature
和 tests/Unit
子文件夾。兩者之間有什么區別?答案有點“哲學”。
從測試的全局視角來看,在 Laravel/PHP 生態系統之外,有不同類型的自動化測試。你可以找到以下術語:
單元測試
功能測試
集成測試
功能測試
端到端測試
驗收測試
煙霧測試
其他
這聽起來很復雜,而且這些測試類型之間的實際差異有時是模糊的。這就是為什么 Laravel 簡化了所有這些令人困惑的術語并將它們分為兩類:單元測試/功能測試。
簡而言之,功能測試嘗試運行應用程序的實際功能:獲取 URL、調用 API、模擬填寫表單等確切行為。功能測試通常執行與任何項目用戶在現實生活中手動執行的相同或相似的事情。
單元測試有兩個含義。通常,你可能會發現任何自動化測試都稱為「單元測試」,而整個過程可能稱為「單元測試」。但是在功能與單元的上下文中,這個過程是關于單獨測試代碼的特定非公共單元。例如,你有一些 Laravel 類,它有一個計算某些東西的方法,比如帶有參數的訂單的總價格。因此,你的單元測試將斷言該方法(代碼單元)是否返回了具有不同參數的正確結果。
要生成單元測試,你需要添加一個標志:
php artisan make:test OrderPriceTest --unit
生成的代碼與 Laravel 的默認單元測試相同:
class OrderPriceTest extends TestCase { public function test_example() { $this->assertTrue(true); } }
如你所見,沒有 RefreshDatabase
行為的定義,這是單元測試最常見的定義之一:它不涉及數據庫,它像一個「黑匣子」一樣工作,與正在運行的應用程序隔離。
你可以嘗試模仿我之前提到的示例,假設我們有一個服務類 OrderPrice
。
app/Services/OrderPriceService.php:
class OrderPriceService{ public function calculatePrice($productId, $quantity, $tax = 0.0) { // 某種計算邏輯 } }
然后,單元測試可能看起來像這樣:
class OrderPriceTest extends TestCase{ public function test_single_product_no_taxes() { $product = Product::factory()->create(); // 生成假的產品數據 $price = (new OrderPriceService())->calculatePrice($product->id, 1); $this->assertEquals(1, $price); } public function test_single_product_with_taxes() { $price = (new OrderPriceService())->calculatePrice($product->id, 1, 20); $this->assertEquals(1.2, $price); } // 更多的參數和案例 }
從我個人對 Laravel 項目的經驗而言,絕大多數測試是功能測試,而不是單元測試。首先,你需要測試你的應用程序是否正常工作,以及真實用戶使用它的方式。
接下來,如果你有可以定義為單元的特殊計算或邏輯,或帶有一些參數,你可以專門為此創建單元測試。
有時候,編寫測試需要更改代碼本身,并將其重構為更「可測試的」:將單元分離為特殊的類或方法。
何時/如何運行測試?
php artisan test
命令的實際用途是什么,我們應該在什么時候運行它?
什么時候運行測試,在開發過程中并沒有固定的時間節點或說法,具體取決于你公司的工作流程。通常情況下,在我們將最新的代碼更改推送到代碼倉庫之前,你需要確保所有測試都是「綠色的」(意味著沒有錯誤)。
因此,當你在本地編寫代碼,在你覺得自己已經完成了你的任務時,你需要運行測試,用來確保你沒有破壞任何東西。請記住,你的代碼可能不僅會在你自己編寫的代碼邏輯中導致錯誤,而且還會無意中破壞其他人很久以前編寫的代碼中的其他行為。
如果我們更進一步,可以自動化的完成很多事情。如使用各種 CI/CD 工具,你可以指定在有人將更改推送到特定 Git 分支時或在將代碼合并到生產分支之前執行的測試。最簡單的工作流程是使用 Github Actions,在這里,我提供了 一個單獨的視頻 演示它。
你應該測試什么?
關于所謂的「測試覆蓋率」應該覆蓋到多大的范圍的爭議,一直以來,有多種意見:你應該測試每個頁面上的每個操作和每個可能的案例,還是只將你的工作限制在最重要的部分。
事實上,這就是我同意人們指責自動化測試花費更多時間而不是帶來實際收益觀點的地方。如果你為每個細節編寫測試,這種情況就可能出現。也就是說,你的項目可能需要思考這個問題:「代碼中潛在的錯誤會給你帶來多大的成本或代價」。
換句話說,你需要通過“如果此代碼失敗會發生什么?”這個問題來確定你的測試工作的優先級。如果你的支付系統存在錯誤,這將直接影響業務。如果你的角色/權限功能被破壞,那這將是一個巨大的安全問題。
我喜歡 Matt Stauffer 在一次會議上的措辭:「你需要先測試這些東西,如果它們失敗了,你就會被解雇」。當然,這有點夸張,但你明白了:首先測試重要的事情。然后是其他功能,如果你有時間的話。
PEST:PHPUnit 的新流行替代品
以上所有示例均基于默認的 Laravel 測試工具:PHPUnit。但多年來,生態系統中出現了其他工具,最新流行的工具之一是 PEST。由 Laravel 官方員工 Nuno Maduro 創建,它的目標是簡化語法,從而更快地編寫測試代碼。
在底層實現上,它基于 PHPUnit 運行;作為一個附屬擴展,它只是試圖最小化 PHPUnit 代碼的一些默認重復部分。
讓我們來看一個例子。還記得 Laravel 中默認的功能測試類嗎?就如下面這段代碼:
namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class ExampleTest extends TestCase { public function test_the_application_returns_a_successful_response() { $response = $this->get('/'); $response->assertStatus(200); } }
讓我們使用 PEST 來實現同樣的測試,實現后的代碼如下:
test('the application returns a successful response')->get('/')->assertStatus(200);
是的,一行代碼,就是這樣。因此,PEST 的目標是解決以下問題:
為一切創建類和方法;
擴展測試用例;
將所有操作放在一行代碼上 – 在 PEST 中,你可以使用鏈式操作把不同動作串聯起來。
要在 Laravel 中生成 PEST 測試,你需要指定一個附加標志:
php artisan make:test HomepageTest --pest
在撰寫本文時,PEST 在 Laravel 開發人員中相當流行,但是除了眾所周知的 PHPUnit 之外,是否使用這個額外的工具并學習它的語法是你個人的喜好。
因此,這就是你需要了解的有關自動化測試基礎知識的全部內容。從這里開始,你可以選擇創建哪些測試以及如何在你的項目中運行它們。
原文地址:https://laravel-news.com/how-to-start-testing
譯文地址:https://learnku.com/laravel/t/67381