用品

了解如何使用 PHPUnit 和 PEST 透過簡單範例在 Laravel 中進行測試

當談到自動化測試或單元測試時,在任何程式語言中,都存在兩種相反的觀點:

  • 失去的時間
  • 你不能沒有它

因此,透過本文,我們將嘗試說服前者,特別是透過演示在 Laravel 中開始自動化測試是多麼容易。

首先讓我們談談“為什麼”,然後讓我們看一些如何做到這一點的例子。

為什麼我們需要自動化測試

自動化測試運行部分程式碼並報告任何錯誤。 這是描述它們的最簡單的方式。 想像一下,在應用程式中啟動一項新功能,然後個人機器人助理會手動測試新功能,同時也測試新程式碼是否不會破壞任何舊功能。

這是主要優點:自動重新測試所有功能。 這似乎是額外的工作,但如果你不告訴「機器人」去做,我們應該手動去做,對嗎? 

或者可以在不測試新功能是否有效的情況下發布新功能,希望用戶能回報錯誤。

自動化測試能夠為我們帶來幾個優勢:

  • 節省人工測試時間;
  • 它們使您可以透過避免回歸來節省實施新功能和合併功能的時間;
  • 將此優勢乘以所有新功能和所有已實現的功能;
  • 前三點適用於每個新版本;
  • ...

試著想像一兩年後您的應用程序,團隊中的新開發人員不知道前幾年編寫的程式碼,甚至不知道如何測試它。 

我們的第一個自動化測試

執行第一個 Laravel 中的自動化測試,您不需要編寫任何程式碼。 是的,你沒有看錯。 一切都已在預安裝中配置和準備defiLaravel 的一晚,包括第一個基本範例。

您可以嘗試安裝 Laravel 專案並立即執行第一個測試:

laravel new project
cd project
php artisan test

這應該是控制台中的結果:

如果我們看一下預defiLaravel 之夜 /tests,我們有兩個文件:

測試/功能/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() 當您查看測試結果時,只需將下劃線符號替換為空格即可變為可讀文字。

測試/Unit/ExampleTest.php:

class ExampleTest extends TestCase
{
    public function test_that_true_is_true()
    {
        $this->assertTrue(true);
    }
}

似乎有點無意義,看看這是不是真的? 

稍後我們將具體討論單元測試。 現在,您需要了解每個測試中通常會發生什麼。

  • 資料夾中的每個測試文件 /tests 是一個 PHP 類,擴充了 TestCase PHP單元
  • 在每個類別中,您可以建立多個方法,通常一個方法用於測試一種情況
  • 每種方法都包含三個行動:準備情況,然後採取行動,然後驗證(確認)結果是否符合預期

從結構上講,這就是您需要知道的全部內容,其他一切都取決於您想要測試的具體內容。

要產生空測試類,只需執行以下命令:

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);
    }
}

現在讓我們看看如果 Laravel 中的測試程式碼失敗會發生什麼

現在讓我們看看如果測試斷言沒有回傳預期結果會發生什麼。

讓我們將範例測試更改為:

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 中測試註冊表單程式碼

假設我們有一個表單,我們需要測試各種情況:我們檢查它是否因無效資料而失敗,我們檢查它是否因正確輸入而成功,等等。

官方入門套件 通過拉拉維爾微風 包括我 測試其中的功能。 讓我們來看一些例子:

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()。 您可以檢查官方文件中可用的所有斷言 PHP單元 e Laravel 回應 。 請注意,關於該主題的一些一般性斷言 $this,而其他人則檢查具體 $response從路由調用。

另一個重要的事情是 use RefreshDatabase;語句,用筆畫插入類別上方。 當測試操作可能影響資料庫時,這是必要的,如本例所示,日誌記錄在 users資料庫表。 為此,您應該建立一個單獨的測試資料庫,該資料庫將更新為 php artisan migrate:fresh每次運行測試時。

您有兩個選擇:物理建立一個單獨的資料庫或使用記憶體 SQLite 資料庫。 兩者都在檔案中配置 phpunit.xml預設提供defi妮塔與 Laravel。 具體來說,您需要這部分:

<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_CONNECTIONDB_DATABASE哪些被評論了? 如果您的伺服器上有 SQLite,最簡單的操作就是取消註解這些行,您的測試將針對該記憶體資料庫執行。

在這個測試中,我們說用戶已成功通過身份驗證並重定向到正確的主頁,但我們也可以測試資料庫中的實際資料。

除了這段程式碼之外:

$this->assertAuthenticated();
$response->assertRedirect(RouteServiceProvider::HOME);

我們也可以使用 資料庫測試斷言 並做這樣的事情:

$this->assertDatabaseCount('users', 1);
 
// Or...
$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();
    }
}

這是關於登入表單的。 邏輯和註冊類似吧? 但是是三種方法而不是兩種,因此這是測試好場景和壞場景的範例。 因此,常見的邏輯是您應該測試兩種情況:當事情進展順利時和事情失敗時。

創新通訊
不要錯過有關創新的最重要新聞。 註冊以通過電子郵件接收它們。

另外,您在此測試中看到的是使用 資料庫工廠 :Laravel 創建假用戶( 再次,在您更新的測試資料庫上 ),然後嘗試使用正確或不正確的憑證登入。

Laravel 再次生成工廠預置defi妮塔(nita)的虛假數據 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 自己準備了多少東西,那我們開始測試是不是很容易呢?

所以如果我們執行 php artisan test安裝 Laravel Breeze 後,我們應該會看到以下內容:

 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 e tests/Unit ?. 

它們之間有什麼區別? 

在全球範圍內,在 Laravel/PHP 生態系統之外,還有多種類型的自動化測試。 您可以找到以下術語:

  • 單元測試
  • 功能測試
  • 整合測試
  • 功能測試
  • 端對端測試
  • 驗收測試
  • 煙霧測試
  • ETC。

這聽起來很複雜,而且這些類型的測試之間的實際差異有時是模糊的。 這就是為什麼 Laravel 簡化了所有這些令人困惑的術語,並將它們分為兩部分:單元/功能。

簡而言之,功能測試嘗試執行應用程式的實際功能:取得 URL、呼叫 API、模仿填寫表單等確切行為。 功能測試通常執行與任何專案使用者在現實生活中手動執行的相同或相似的操作。

單元測試有兩個意義。 一般來說,你可能會發現任何自動化測試都稱為“單元測試”,整個過程都可以稱為“單元測試”。 但在功能與單元的背景下,這個過程是關於單獨測試特定的非公共代碼單元。 例如,您有一個 Laravel 類,其中包含一個計算某些內容的方法,例如帶參數的總訂單價格。 因此,單元測試將說明是否使用不同的參數從該方法(程式碼單元)傳回正確的結果。

要產生單元測試,您需要新增一個標誌:

php artisan make:test OrderPriceTest --unit

產生的程式碼與預單元測試相同defi拉拉維爾系統:

class OrderPriceTest extends TestCase
{
    public function test_example()
    {
        $this->assertTrue(true);
    }
}

正如你所看到的,它不存在 RefreshDatabase,這是其中之一 defi最常見的單元測試定義:它不會接觸資料庫,它作為一個「黑盒子」工作,與正在運行的應用程式隔離。

嘗試模仿我之前提到的範例,假設我們有一個服務類 OrderPrice.

app/Services/OrderPriceService.php:

class OrderPriceService
{
    public function calculatePrice($productId, $quantity, $tax = 0.0)
    {
        // Some kind of calculation logic
    }
}

然後,單元測試可能如下所示:

class OrderPriceTest extends TestCase
{
    public function test_single_product_no_taxes()
    {
        $product = Product::factory()->create(); // generate a fake product
        $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);
    }
 
    // More cases with more parameters
}

根據我個人對 Laravel 專案的經驗,絕大多數測試都是功能測試,而不是單元測試。 首先,您需要測試您的應用程式是否能夠正常運作,就像人們使用它的方式一樣。

接下來,如果您有特殊的計算或邏輯,您可以 definire 作為一個單元,帶有參數,您可以專門為此建立單元測試。

有時,編寫測試需要修改程式碼本身並重構它以使其更加“可測試”:將單元分成特殊的類別或方法。

何時/如何進行測試?

這個有什麼實際用途 php artisan test,你應該什麼時候運行它?

根據您的業務工作流程,有不同的方法,但通常您需要在將最終程式碼變更推送到儲存庫之前確保所有測試都是「綠色」(即無錯誤)。

然後,您在本地完成任務,當您認為完成時,請執行一些測試以確保沒有破壞任何內容。 請記住,您的程式碼不僅可能會導致邏輯錯誤,而且可能會無意中破壞別人很久以前編寫的程式碼中的某些其他行為。

如果我們更進一步,自動化是可能的 許多 事物。 使用各種 CI/CD 工具,您可以指定每當有人將變更推送到特定 Git 分支時或在將程式碼合併到生產分支之前執行的測試。 最簡單的工作流程是使用 Github Actions,我有 一個單獨的視頻 這證明了這一點。

你應該測試什麼?

對於所謂的「測試覆蓋率」應該有多大,存在不同的意見:在每個頁面上嘗試每一種可能的操作和案例,或將工作限制在最重要的部分。

事實上,我同意那些指責自動化測試花費更多時間而不提供實際好處的人的觀點。 如果您為每個細節編寫測試,就會發生這種情況。 也就是說,您的專案可能需要:主要問題是「潛在錯誤的代價是多少」。

換句話說,您需要透過詢問「如果此程式碼失敗會發生什麼?」的問題來確定測試工作的優先順序。 如果您的支付系統發生錯誤,將直接影響業務。 因此,如果您的角色/權限的功能被破壞,這將是一個巨大的安全問題。

我喜歡馬特·斯托弗在一次會議上的說法:“你必須先測試那些事情,如果失敗,你就會被解僱。” 當然,這有點誇張,但你明白了:先嘗試重要的事情。 如果你有時間的話,還有其他功能。

PEST:PHPUnit 的新替代品

以上所有範例均基於Laravel預測試工具defi晚上: PHP單元 。 但多年來,生態系統中出現了其他工具,其中最新流行的工具之一是 害蟲 。 由 Laravel 官方員工創建 努諾·馬杜羅 ,旨在簡化語法,使測試程式碼的編寫速度更快。

在引擎蓋下,它運行 su PHPUnit,作為一個附加層,只是試圖最小化一些預先重複的部分defiPHPUnit 程式碼的一部分。

讓我們來看一個例子。 記住預功能測試類defi在 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 註釋取決於您個人的喜好。

BlogInnovazione.it

創新通訊
不要錯過有關創新的最重要新聞。 註冊以通過電子郵件接收它們。

最近的文章

線上支付:串流服務如何讓您永遠付款

數百萬人為串流媒體服務付費,每月支付訂閱費用。人們普遍認為您...

29月2024

Veeam 為勒索軟體提供最全面的支持,從保護到回應和恢復

Veeam 的 Coveware 將繼續提供網路勒索事件回應服務。 Coveware 將提供取證和修復功能…

23月2024

綠色與數位革命:預測性維護如何改變石油和天然氣產業

預測性維護正在透過創新和主動的工廠管理方法徹底改變石油和天然氣行業。

22月2024

英國反壟斷監管機構對 GenAI 向 BigTech 發出警報

英國 CMA 對大型科技公司在人工智慧市場的行為發出了警告。那裡…

18月2024