當談到自動化測試或單元測試時,在任何程式語言中,都存在兩種相反的觀點:
因此,透過本文,我們將嘗試說服前者,特別是透過演示在 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);
}
}
現在讓我們看看如果測試斷言沒有回傳預期結果會發生什麼。
讓我們將範例測試更改為:
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,解釋如下,箭頭指向失敗的測試的確切行。 錯誤以這種方式指示。
假設我們有一個表單,我們需要測試各種情況:我們檢查它是否因無效資料而失敗,我們檢查它是否因正確輸入而成功,等等。
官方入門套件 通過拉拉維爾微風 包括我 測試其中的功能。 讓我們來看一些例子:
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()
e $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_CONNECTION
e DB_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 生態系統之外,還有多種類型的自動化測試。 您可以找到以下術語:
這聽起來很複雜,而且這些類型的測試之間的實際差異有時是模糊的。 這就是為什麼 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,我有 一個單獨的視頻 這證明了這一點。
對於所謂的「測試覆蓋率」應該有多大,存在不同的意見:在每個頁面上嘗試每一種可能的操作和案例,或將工作限制在最重要的部分。
事實上,我同意那些指責自動化測試花費更多時間而不提供實際好處的人的觀點。 如果您為每個細節編寫測試,就會發生這種情況。 也就是說,您的專案可能需要:主要問題是「潛在錯誤的代價是多少」。
換句話說,您需要透過詢問「如果此程式碼失敗會發生什麼?」的問題來確定測試工作的優先順序。 如果您的支付系統發生錯誤,將直接影響業務。 因此,如果您的角色/權限的功能被破壞,這將是一個巨大的安全問題。
我喜歡馬特·斯托弗在一次會議上的說法:“你必須先測試那些事情,如果失敗,你就會被解僱。” 當然,這有點誇張,但你明白了:先嘗試重要的事情。 如果你有時間的話,還有其他功能。
以上所有範例均基於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 的目標是消除以下開銷:
要在 Laravel 中產生 PEST 測試,您需要指定一個附加標誌:
php artisan make:test HomepageTest --pest
截至撰寫本文時,PEST 在 Laravel 開發人員中相當流行,但是否使用這個附加工具並學習其語法以及 PHPUnit 註釋取決於您個人的喜好。
BlogInnovazione.it