哈囉,大家好!讓我們延續單元測試議題,在這篇文章中,我想和大家聊聊單元測試的延伸議題,重構的重要性,特別是它如何為我們的程式碼提供一層安全防線,讓我們在重構時可以無後顧之憂。
在開發過程中,難免會遇到需要重構的情況。也許一開始的設計不夠完善,或者需求發生了變化。然而,隨著專案的複雜度增加,每次修改都有可能影響到其他部分。這種不確定性常常讓人感到焦慮,甚至不敢輕易動手。
還記得有一次,我在一個專案中需要優化一個看似簡單的函式。結果,一改之下,整個系統都出現了奇怪的錯誤。當時真是頭大,花了好幾個小時才找到問題所在。那次經驗讓我深刻體會到,沒有單元測試的保護,重構真的像是在走鋼索,一不小心就可能摔得很慘。
單元測試的目的,不僅僅是為了找到錯誤,更是為我們的程式碼建立一個可靠的基礎。透過撰寫針對各個功能的測試,我們可以:
讓我們以實際例子來看看如何利用單元測試為重構保駕護航。
假設我們有一個 Transaction 模型,裡面有一個計算用戶總收入的方法:
public function calculateTotalIncome($userId)
{
return $this->where('user_id', $userId)
->where('type', 'income')
->sum('amount');
}
在進行重構前,我們先為這個方法撰寫單元測試:
/** @test */
public function it_calculates_total_income_correctly()
{
$user = User::factory()->create();
Transaction::factory()->create(['user_id' => $user->id, 'type' => 'income', 'amount' => 1000]);
Transaction::factory()->create(['user_id' => $user->id, 'type' => 'income', 'amount' => 2000]);
Transaction::factory()->create(['user_id' => $user->id, 'type' => 'expense', 'amount' => 500]);
$totalIncome = (new Transaction())->calculateTotalIncome($user->id);
$this->assertEquals(3000, $totalIncome);
}
假設我們覺得這個方法應該放到 User 模型裡,更符合邏輯。因此,我們將方法移到 User 模型:
public function calculateTotalIncome()
{
return $this->transactions()
->where('type', 'income')
->sum('amount');
}
重構後,重新執行測試:
php artisan test --filter=it_calculates_total_income_correctly
如果測試通過,表示我們的重構沒有破壞原有功能。
我們可以進一步撰寫更多測試,確保各種情況下功能都正常。例如,當沒有收入時:
/** @test */
public function it_returns_zero_when_no_income()
{
$user = User::factory()->create();
$totalIncome = $user->calculateTotalIncome();
$this->assertEquals(0, $totalIncome);
}
透過這樣的過程,我們不僅確保了程式碼的品質,也讓自己對系統的理解更加深入。有時候,撰寫測試時會發現原本設計的不合理之處,進而促使我們思考更好的解決方案。
還有一次,我在撰寫測試時,發現某個方法的依賴太多,導致測試很難寫。這讓我意識到,這個方法需要被拆分或重構。結果,不僅測試變得容易,整個系統的結構也因此優化了。
單元測試就像是我們程式碼的守護者,為我們築起一座安全的城牆。在這座城牆的保護下,我們可以更自在地進行重構,提升系統的品質和效能。
當然,撰寫測試需要時間和精力,但從長遠來看,這絕對是值得的投資。畢竟,誰也不想在深夜還在追著難纏的 Bug 跑。透過單元測試,我們可以更有信心地面對未來的挑戰。