這是「科技與技術系列」的第一篇文章,將介紹一些在實務中廣泛使用,但較難歸類的技術。回顧上週的《Laravel 系列 - 6: 自動化測試相關套件與使用》,我們已經探討過一些在 Laravel 框架下應用的自動化測試套件。今天,這篇文章將把焦點放在自動化測試這個概念本身,帶你了解它的核心與實際應用。
簡介
自動化測試的目的是模擬實際運行環境,確保系統在各種情境下能穩定運作。除了測試程式碼本身,還會涵蓋與其他系統互動的架構,如資料庫、外部 API 等,並考慮各種可能的使用情境。透過這些測試,我們可以確保系統在不同情況下的行為符合預期,進而提高穩定性與可靠性。
自動化測試的主要優點包括:- 確保程式碼在多種情境下正常運作。
- 測試與其他系統互動時的錯誤處理,像是資料庫連線失敗時的錯誤拋出與處理。
- 降低其他開發人員在修改程式碼過程中的失誤風險。
- 確保方法傳遞的型別正確性,這不僅能減少開發過程中的錯誤,還能協助在版本升級時檢查潛在問題。
需要注意的是,以上優點很大程度上取決於開發者如何設計和實作測試[ 註1 ]。因此,僅僅撰寫測試並不代表一定會達到預期的效果。
步驟
曾經讀過一篇文章提到:「要先確定情境,接著寫測試,最後才寫功能。」這是因為透過情境的設計,我們可以界定功能的範圍,並確保測試能隨著功能逐步完成而逐漸成功。這樣的流程,當然是建立在一開始就決定要進行測試的前提下。
然而,在實際開發過程中,撰寫測試的時間往往並不會被納入開發時程中。因此,自動化測試通常會在功能開發完成後才補寫,或者是軟體工程師們在所剩不多的時間內匆忙完成。雖然這樣的系統在短期內可能不會顯現問題,但一旦需要重構、升級或新增功能,系統的不穩定性往往隨之而來,甚至可能導致重大問題。
模擬實例與方法 Mock
在測試過程中,我們常會遇到需要越過重重判斷條件來達成特定情境的情況,但卻難以找到符合條件的資料;或是需要某些方法回傳特定的值,但又無法輕易調整程式碼來達到這些效果。
此時,Mock 就成為一個非常有效的工具。我們可以通過 Mock 整個類別或方法,並指定它們應該回傳的值。這樣,我們就能將焦點集中在測試功能本身,而不必過多關注其他依賴的功能或資料處理。
不過,使用 Mock 時需要特別注意它的實例設定。如果出現類似「參數 must not be accessed before initialization
」的錯誤訊息,通常表示某個 Mock 方法模擬的實例有誤,導致流程中需要用到的實例或方法並沒有被模擬出來,導致出現這則沒有初始化的提示訊息。
判斷回傳 ——斷言方法 Assertion
另外,每個程式語言使用斷言方法的方式有些許不同,需要稍微注意一下。
在每個測試情境的最後,我們需要使用斷言方法來判斷測試結果是否符合預期。可以進行判斷的項目非常多樣,包括回傳的狀態碼、輸出的型別,甚至是輸出的具體值等。當然,判斷的細節越多,系統發生錯誤的機會就越低,開發與維護的風險也會隨之降低。
另外,需要注意的是,不同程式語言在使用斷言方法時會有些微差異,因此在實作時要留意特定的語法與使用方法。
範例參考
由於我比較熟悉 PHP 的關係,我以 PHP 與 Laravel 框架的方式來稍微舉例。
private array $header;
protected function setUp(): void
{
//先在初始方法裡面設定這個測試類別中設置需要初始化的內容
//由於是整個類別一起初始化,所以如果像是資料庫每個情境測試完需要清空,就不能在這裡統一清空,需要在每個情境本身開始時清空
parent::setUp();
$this->header = ['CLient-ID' => 'test'];
$this->apiPath = route('api.test');
$this->mockUser();
$this->withoutMiddleware();
}
/**
* @testdox 測試XXX
*/
public function testXXX(): void
{
$data = ['message' => 'test'];
$this->mockHandle();
$this->post($this->apiPath, $data, $this->header)
->assertStatus(204);
}
private function mockUser(): void
{
$user = Mockery::mock(NormalUser::class)->makePartial();
$user->login_id = self::USER_ID;
$this->instance(NormalUser::class, $user);
}
加速自動化測試
當專案越來越大以後,對應的單元測試數量自然也會越來越多,如此一來,每次合併分支前的自動化測試時間就會越來越長。
有個簡單的方法,讓共用的非測試項目使用 Redis 和 Cache,這樣便能大幅減少重新創建新資料的時間。例如,當我們想測試一位用戶創建、更新、刪除訂單時的情境時,用戶的資料本身就是非測試項目(因為要測試的是訂單行為),那麼我們就可以創建一個 Mockery Table 並加入一筆用戶資料後,讓這三個測試共用這筆資料,而不是每個測試單獨創建、單獨使用。
[ 註1 ] 筆者曾遇過一位前輩編寫的測試,無論輸入參數為何,測試結果總是回傳 true
,實在非常神奇。