Laravel如何進行自動化測試?以下這篇文章透過使用 PHPUnit 和 PEST 的簡單範例來了解如何在 Laravel 中開始測試,希望對大家有幫助。
在我們談論任何程式語言的自動化測試或單元測試時,一般有兩類人:
所以,通過這篇文章,我將試圖說服前者,看看它的好處,看看在Laravel 開始自動化測試是多麼容易。 【相關推薦:laravel影片教學】
首先,讓我們談談「為什麼」,然後我將展示一些非常基本的「如何做測試」的範例。
自動化測試並不複雜:它們只是為你執行部分程式碼並報告任何錯誤。這是描述它們的最簡單的方式。想像一下,你正在應用程式中啟動一項新功能,然後一個機器人助理會為你手動測試新功能,同時測試新程式碼是否不會破壞舊功能的任何內容。
這樣的好處是:自動重新測試所有功能。這似乎是額外的工作,但如果你不告訴那個「機器人」去做,那麼你應該自己手動做,對吧?還是你在沒有詳細測試的情況下推出新功能,希望用戶回報錯誤?我諷刺地稱這種方法為「手指交叉驅動的開發」。
隨著應用程式的每項新功能,自動化測試的回報越來越高。
你應該明白了。想像一下你的應用程式在一兩年內,團隊中的新開發人員甚至不知道「功能 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
你已经看到了 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 在一次会议上的措辞:「你需要先测试这些东西,如果它们失败了,你就会被解雇」。当然,这有点夸张,但你明白了:首先测试重要的事情。然后是其他功能,如果你有时间的话。
以上所有示例均基于默认的 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 的目标是解决以下问题:
要在 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
更多编程相关知识,请访问:编程教学!!
以上是Laravel如何進行自動化測試? PHPUnit和PEST的範例分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!