單元測試是什麼呢?顧名思義,就是針對單元的測試。那什麼又是單元呢?若我們看維基百科的單元測試定義,可以發現在物件導向程式中,單元通常就是指類別裡面的方法。
一個單元就是單個程式、函數、過程等;對於物件導向程式設計,最小單元就是方法,包括基礎類別(超類)、抽象類、或者衍生類別(子類)中的方法。 - 維基百科
所以回到最初的問題,單元測試就是針對一個類別裡面的方法的測試。
那在開始寫測試之前,得先決定好我們要測試目標是什麼,假設我們已經寫好一個費柏納西數數列的類別,其包含一個 find 方法,輸入數字 n 取得數列第 n 個數字的位置。此時,Fibonacci 就是我們想測試的類別,稱為待測程式,也稱為 SUT ( System Under Test )。
class Fibonacci {
int find(int index) {
if (index == 0 || index == 1) {
return 1;
}
return find(index - 2) + find(index - 1);
}
}
一個類別的測試通常會放在另一個獨立檔案中,在開始寫單元測試之前,我們必須先新增一個檔案,檔案名稱必須以 test 結尾 Flutter 才能辨識這個檔案是測試檔案,當我們跑全測試時,這些測試檔案才會被跑到。在專案目錄底下有一個 test 的目錄,就是讓我們放置這些測試檔案的地方,新增一個 fibonacci_test.dart,然後我們就可以開始寫測試了。
值得注意的是,一個類別可以擁有不只一個測試檔案,當我們測試數量較多時,我們也可以依照不同行為來拆分測試檔案,避免一個測試檔案有過多個測試,造成閱讀測試困難。
與大多數程式語言相同,Dart 的程式進入點是 main 方法,在測試中也不例外,每個測試檔案中也都會有一個 main 方法,而我們會把測試寫在這個 main 方法中。
import 'package:flutter_test/flutter_test.dart';
main() {
test("index 0 should be 1", () async {
});
}
SUT 包含可能有很多行為,但一個測試就只會挑一個行為來測試,在這邊我們的第一個測試案例就測試費柏納西數列的第一個數字等於 1 吧。
寫單元測試十分簡單,就跟我們手動測試新功能一樣,我們先想好要測試某個功能,我們會先進到某個頁面,然後操作想測試的功能,最後看看畫面變化是不是跟預期中一樣。
首先,我們會建立 Fibonacci 物件
var fibonacci = Fibonacci();
呼叫 find 方法並帶入參數 0,取得回傳值
var actual = fibonacci.find(0);
最後檢查回傳值是不是等於 1
expect(actual, 1);
最終測試完成之後也就像下面這段一樣
test("index 0 should be 1", () async {
var fibonacci = Fibonacci();
var actual = fibonacci.find(0);
expect(actual, 1);
});
讓我們執行測試若我們執行 Dartpad 上的例子[連結],測試通過,就完成了第一個單元測試。
00:00 +0: index 0 should be 1
00:00 +1: All tests passed!
觀眾朋友也可以從 Dartpad 的 Console 中看到結果。
從準備、執行、到驗證,這三個步驟是單元測試基本的架構,也稱為 3A 原則:Arrange、Act、Assert。測試程式碼可能或長或短,但不變的是肯定會包含這三個步驟,有時候我們可能會重構測試,讓測試變得簡短,使得這三個步驟就沒有像上面例子那樣明顯分成三段,但是本質上還是三個步驟。
test("index 0 should be 1", () async {
expect(Fibonacci().find(0), 1);
});
開始寫測試時,我們也可以先寫註解,簡單標示一下,依照 3A 原則的話,我們每一步應該要做什麼,當想測試的行為比較複雜時,有助我們更有條理的一步一步完成測試。
test("index 4 should be 5", () async {
// Arrang: 建立 Fibonacci
// Act: 輸入呼叫 get 方法輸入 4
// Assert: 確認結果為 5
});
今天介紹了我們如何從無到有的新增一個 Dart 單元測試,也談到單元測試應該有的基本架構,跟隨文章中的步驟,透過一步一步完成,最後就能完成一個簡單的單元測試。還沒寫過單元測試的觀眾朋友們,也可以嘗試在自己的 Side Project 或工作專案中嘗試加上第一個單元測試看看吧。