PHPUnit 自動化測試大作戰【CH07】

更新於 2024/12/17閱讀時間約 19 分鐘

在前幾篇文章中,我們介紹了測試3A原則,也介紹了許多 Assertion 函數,今天就讓我們實際演練吧!

過去的經驗中,最常用自動化測試來測式的對象,大概就是API了,而前後端分離也是目前 Web 開發界常用的模式,因此我們就以 API 測試來演練吧!

驗證HTTP Status Code

HTTP Status Code 的驗證,應該是最常遇到的 API 測試情境了,其實前面的 Assertion 函數範例中也有展示部分使用方式了,這次讓我們更完整地演練吧!

提醒一點,由於以下的測試會牽涉到資料庫,因此大家要再檢查一下 phpunit.xml 中的資料庫連線設定是否已填上,詳情可參考前Day 6文章內容。

另值得一提的是,為了方便取用路由路徑,在設置路由時,通常都會將路由命名。

案例1:200 — 索取使用者資料

  • routes/api.php
<?php

use App\Models\User;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\Route;

Route::get('/users', function (Request $request) {

$users = User::all();

return response()->json([

'users' => $users,

]);})->name('get.user.list');
  • tests/Feature/HttpStatusCodeTest.php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;

use Tests\TestCase;

class HttpStatusCodeTest extends TestCase{

use RefreshDatabase;

public function testCanGetUserListWhenEmpty() {

$response = $this->get(route('get.user.list'));

$response->assertOk();

// 也可以寫成以下串聯寫法

$this->get(route('get.user.list'))

->assertOk();

}}

在以上程式碼中, users 資料表尚無任何User資料,但取得User清單的端點也應該要能正常回應,透過以上測試程式碼,可以驗證此行為是否正常運作。

案例2:404 — 索取不存在之使用者資料

  • routes/api.php
<?php

use App\Models\User;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\Route;

Route::get('/users/{id}', function (Request $request, $id) {

$user = User::find($id);

if (empty($user)) {

return response()->json([

'error' => 'Not Found',

], 404);

} return response()->json([

'user' => $user,

]);})->where('id', '[0-9]?')->name('get.user');
  • tests/Feature/HttpStatusCodeTest.php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;

use Tests\TestCase;

class HttpStatusCodeTest extends TestCase{

use RefreshDatabase;

public function testCanGetNotFoundWhenNoGivenUser() {

$response = $this->get(route('get.user', ['id' => 1,]));

$response->assertNotFound();

// 也可以寫成以下串聯寫法

$this->get(route('get.user', ['id' => 1,]))

->assertNotFound();

}}

在以上程式碼中, users 資料表尚無任何User資料,因此當嘗試取得 id=1 之 User 資源時,應會找不到,也就是404,透過以上測試程式碼,可以驗證此行為是否正常運作。

案例3:422 — 請求驗證未通過

  • routes/api.php
<?php

use App\Models\User;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\Route;

Route::post('/users', function (Request $request) {

$request->validate([

'name' => 'required|string',

'email' => 'required|email',

'password' => 'required|string',

]); $user = User::create($request->all());

return response()->json([

'user' => $user,

]);})->name('store.user');
  • tests/Feature/HttpStatusCodeTest.php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;

use Tests\TestCase;

class HttpStatusCodeTest extends TestCase{

use RefreshDatabase;

public function testCanResponseUnprocessableWhenInvalidRequest() {

$response = $this->post(

route('store.user'),

['name' => ''],

['Accept' => 'application/json']

); $response->assertUnprocessable();

// 也可以寫成以下串聯寫法

$this->post(

route('store.user'),

['name' => ''],

['Accept' => 'application/json']

) ->assertUnprocessable();

}}

在以上程式碼中, 我們建立了一個可以建立 User 資料的路由端點,與實作建立資料流程,並在這當中建立了驗證請求資料的邏輯,而在下方的測試案例中,我們測試了「當請求內容有缺失時,應回應 422 Unprocessable Entity」這個行為是否正常運作。

驗證Response JSON

除了前面介紹的 HTTP Status Code 驗證,在測試 API 時,另一個常常需要做驗證的部分,應該就是回應JSON的內容本身了,以下就讓我們來看看實例吧!

案例1:驗證回應JSON內容

  • routes/api.php
<?php

use Illuminate\Http\Request;

use Illuminate\Support\Facades\Route;

Route::get('/users', function (Request $request) {

// 這邊先用假的 User 資料

$users = [

[ 'id' => 1,

'name' => 'name1',

'email' => 'user1@email.com',

], [ 'id' => 1,

'name' => 'name1',

'email' => 'user1@email.com',

], ]; return response()->json([

'users' => $users,

]);})->name('get.user.list');
  • tests/Feature/HttpStatusCodeTest.php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;

use Tests\TestCase;

class HttpStatusCodeTest extends TestCase{

use RefreshDatabase;

public function testCanGetUserJson() {

$response = $this->get(route('get.user.list'));

$response->assertJson([

'users' => [

[ 'id' => 1,

'name' => 'name1',

'email' => 'user1@email.com',

], [ 'id' => 1,

'name' => 'name1',

'email' => 'user1@email.com',

], ] ]); // 也可以寫成以下串聯寫法

$this->get(route('get.user.list'))

->assertJson([

'users' => [

[ 'id' => 1,

'name' => 'name1',

'email' => 'user1@email.com',

], [ 'id' => 1,

'name' => 'name1',

'email' => 'user1@email.com',

], ] ]); }}

在以上程式碼中, 我們建立了一個可以取得 User 清單的端點,並且驗證在呼叫此 API 後,其回應的JSON是否符合預期。

案例2:驗證回應JSON結構

  • routes/api.php
<?php

use Illuminate\Http\Request;

use Illuminate\Support\Facades\Route;

Route::get('/users', function (Request $request) {

// 這邊先用假的 User 資料

$users = [

[ 'id' => 1,

'name' => 'name1',

'email' => 'user1@email.com',

], [ 'id' => 1,

'name' => 'name1',

'email' => 'user1@email.com',

], ]; return response()->json([

'users' => $users,

]);})->name('get.user.list');
  • tests/Feature/HttpStatusCodeTest.php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;

use Tests\TestCase;

class HttpStatusCodeTest extends TestCase{

use RefreshDatabase;

public function testCanGetUserJsonStructure() {

$response = $this->get(route('get.user.list'));

// '*' 代表是每陣列元素

// 此寫法相當於驗證 'users' 是一個陣列

// 每個陣列元素是一個物件

// 每個物件有 id, name, email 這幾個欄位

$response->assertJsonStructure([

'users' => [

'*' => [

'id',

'name',

'email'

], ], ]); // 也可以寫成以下串聯寫法

$this->get(route('get.user.list'))

->assertJsonStructure([

'users' => [

'*' => [

'id',

'name',

'email'

], ], ]); }}

另一種常見的情況,是只驗證 JSON 結構,而略過驗證 JSON 的鍵與值。

以上就是今天的介紹,大家可以多加練習看看!

下一篇讓我們來練習資料庫測試吧!

如果您喜歡這篇文章,歡迎加入追蹤以接收新文章通知 😄

本系列文章目錄

avatar-img
8會員
270內容數
歡迎來到 WilliamP 的沙龍天地,在這裡將與各位讀者探討各種主題,包刮高中數學題庫、PHP開發經驗、LINE聊天機器人開發經驗、書摘筆記等,歡迎交流!
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
WilliamP的沙龍 的其他內容
今天會再與大家介紹幾個資料庫 Assertion 函數,與陣列 Assertion 函數。 與前一篇一樣,以下會提到的資料庫 Assertion 函數,並非 PHPUnit 內建,而是由 Laravel 所擴充,因此需注意是否有確實引用到 use Tests\\TestCase 。最後面介紹的2個
前一天與大家分享了幾個通用型 Assertion 函數,今天來為大家介紹幾個 HTTP 相關的 Assertion 函數吧! 今天要介紹的各函數,其使用方式和前一天所介紹的略有不同。以下所列各函數,皆是基於 HTTP Response 來做驗證測試,因此大家會看到 $response = $thi
前一天我們實作了第一個測試,我們學到了第一個 Assert 函數 assertEquals。 今天讓我們來了解其他常用的 Assert 函數吧! 通用型 Assertion 函數 assertEmpty 函數簽名:assertEmpty(mixed $actual[, string $mess
今天我們來寫第一個單元測試吧! 不過在那之前,先讓我們了解單元測試的「3個A」 單元測試3A 所謂的「3個A」,是指以下三個英文單字: Arrange:初始化工作,如準備假資料 Act:執行測試對象 Assert:驗證結果 一個良好的單元測試案例,應該包含以上的結構, 依序執行 Arra
下載與設定 Laradock 首先,讓我們在Home資料夾下,將 Laradock 下載下來: cd ~ && git clone <https://github.com/Laradock/laradock.git> Laradock 將 Laradock 下載回來後,切換到 Laradock
初遇自動化測試 在數年前,我剛從第一份工作離職,轉職到第二份工作, 新工作是在一個大集團的IT部門,職位是後端工程師。 當時集團正準備導入一個由子公司開發的微服務系統, 使用的技術是PHP 8 及 Laravel 9 因為該系 統在子公司運作得不錯, 因此集團高層想將它擴展成,全集團都可使用的規模
今天會再與大家介紹幾個資料庫 Assertion 函數,與陣列 Assertion 函數。 與前一篇一樣,以下會提到的資料庫 Assertion 函數,並非 PHPUnit 內建,而是由 Laravel 所擴充,因此需注意是否有確實引用到 use Tests\\TestCase 。最後面介紹的2個
前一天與大家分享了幾個通用型 Assertion 函數,今天來為大家介紹幾個 HTTP 相關的 Assertion 函數吧! 今天要介紹的各函數,其使用方式和前一天所介紹的略有不同。以下所列各函數,皆是基於 HTTP Response 來做驗證測試,因此大家會看到 $response = $thi
前一天我們實作了第一個測試,我們學到了第一個 Assert 函數 assertEquals。 今天讓我們來了解其他常用的 Assert 函數吧! 通用型 Assertion 函數 assertEmpty 函數簽名:assertEmpty(mixed $actual[, string $mess
今天我們來寫第一個單元測試吧! 不過在那之前,先讓我們了解單元測試的「3個A」 單元測試3A 所謂的「3個A」,是指以下三個英文單字: Arrange:初始化工作,如準備假資料 Act:執行測試對象 Assert:驗證結果 一個良好的單元測試案例,應該包含以上的結構, 依序執行 Arra
下載與設定 Laradock 首先,讓我們在Home資料夾下,將 Laradock 下載下來: cd ~ && git clone <https://github.com/Laradock/laradock.git> Laradock 將 Laradock 下載回來後,切換到 Laradock
初遇自動化測試 在數年前,我剛從第一份工作離職,轉職到第二份工作, 新工作是在一個大集團的IT部門,職位是後端工程師。 當時集團正準備導入一個由子公司開發的微服務系統, 使用的技術是PHP 8 及 Laravel 9 因為該系 統在子公司運作得不錯, 因此集團高層想將它擴展成,全集團都可使用的規模
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
在前一篇文章中,我們探討了多重資料庫連線情境下,Model 及 Database Assertion 的應對方式,不過實際上筆者認為比較有難度的,其實是 Migration 應對方式。 今天就讓我們來探討這部分吧! Migration 應對方式 對於多重資料庫連線這種情境,筆者實務上做過的對應
今天讓我們探討「缺乏 Migration Files 與 Factory Files」的 Legacy 情境吧! 很多時候我們會遇到沒有 Migration Files 或 Factory Files 的 Legacy Codebase,原因大概有以下幾種: 該程式庫原本不是以 Laravel
在實務情境上,常會有在單一專案程式庫中,存取多個不同資料庫的使用情境,在這種情況下,我們通常會設置多個資料庫連線(Database Connection)設定。 在平常開發使用設很方便,但要做測試時就會發現一些問題: 在測試程式碼或 Seeder 中調用 factory() 時,都是在預設連線資
今天就讓我們依照前一天的情境題,來撰寫測試案例函數吧! 這次同樣地,先讓我們規畫擬訂測試案例: 測試案例 使用者註冊: 使用者可送出註冊資料,系統將建立使用者資料,並送出含有專屬驗證連結之驗證信,當此驗證連結被開啟後,將讓使用者轉為已驗證狀態 請求錯誤的驗證連結: 錯誤的驗證連結被開啟後
前兩天,我們探討了「網站文章」的情境題;今明兩天,就讓我們探討另一個情境題「會員註冊」吧! 這邊我們同樣假設網站是採前後端分離的設計,因此我們就專注在測試 API 的部分,不過會多一個「註冊驗證信」的部分要實作與做測試驗證。 使用案例 使用者可填寫註冊資料後送出資料。 使用者可收到註冊驗證信
Thumbnail
練習 PHPUnit 測試的撰寫,依序創建Controller、Service,並針對計算邏輯進行單元測試的練習。
Thumbnail
在 Laravel 中的測試中,PHPUnit 和 Mockery 都可以用來創建測試替身(test double),但它們有不同的方式和功能,以下簡單介紹兩種寫法方式。
前言 基本準備差不多了,也能跑自己的測試,再來就是關於測試腳本的核心:元素定位跟動作,本篇會著重介紹 XPATH 定位的部分
Thumbnail
前言 上篇我們成功執行第一個測試案例,從 Python 腳本透過 Appium 控制模擬器點選設定中的電池,下個問題就是怎麼找元件,這時候就要請出 Appium Inspector 了
前言 經過五個小單元的準備,終於可以開始跑第一個測試了,Appium 本身是個工具,可以搭配各種語言,這邊選擇 Python 作為測試腳本語言,以便之後跟 Robot Framework 串接。
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
在前一篇文章中,我們探討了多重資料庫連線情境下,Model 及 Database Assertion 的應對方式,不過實際上筆者認為比較有難度的,其實是 Migration 應對方式。 今天就讓我們來探討這部分吧! Migration 應對方式 對於多重資料庫連線這種情境,筆者實務上做過的對應
今天讓我們探討「缺乏 Migration Files 與 Factory Files」的 Legacy 情境吧! 很多時候我們會遇到沒有 Migration Files 或 Factory Files 的 Legacy Codebase,原因大概有以下幾種: 該程式庫原本不是以 Laravel
在實務情境上,常會有在單一專案程式庫中,存取多個不同資料庫的使用情境,在這種情況下,我們通常會設置多個資料庫連線(Database Connection)設定。 在平常開發使用設很方便,但要做測試時就會發現一些問題: 在測試程式碼或 Seeder 中調用 factory() 時,都是在預設連線資
今天就讓我們依照前一天的情境題,來撰寫測試案例函數吧! 這次同樣地,先讓我們規畫擬訂測試案例: 測試案例 使用者註冊: 使用者可送出註冊資料,系統將建立使用者資料,並送出含有專屬驗證連結之驗證信,當此驗證連結被開啟後,將讓使用者轉為已驗證狀態 請求錯誤的驗證連結: 錯誤的驗證連結被開啟後
前兩天,我們探討了「網站文章」的情境題;今明兩天,就讓我們探討另一個情境題「會員註冊」吧! 這邊我們同樣假設網站是採前後端分離的設計,因此我們就專注在測試 API 的部分,不過會多一個「註冊驗證信」的部分要實作與做測試驗證。 使用案例 使用者可填寫註冊資料後送出資料。 使用者可收到註冊驗證信
Thumbnail
練習 PHPUnit 測試的撰寫,依序創建Controller、Service,並針對計算邏輯進行單元測試的練習。
Thumbnail
在 Laravel 中的測試中,PHPUnit 和 Mockery 都可以用來創建測試替身(test double),但它們有不同的方式和功能,以下簡單介紹兩種寫法方式。
前言 基本準備差不多了,也能跑自己的測試,再來就是關於測試腳本的核心:元素定位跟動作,本篇會著重介紹 XPATH 定位的部分
Thumbnail
前言 上篇我們成功執行第一個測試案例,從 Python 腳本透過 Appium 控制模擬器點選設定中的電池,下個問題就是怎麼找元件,這時候就要請出 Appium Inspector 了
前言 經過五個小單元的準備,終於可以開始跑第一個測試了,Appium 本身是個工具,可以搭配各種語言,這邊選擇 Python 作為測試腳本語言,以便之後跟 Robot Framework 串接。