在開發程式的時候,無論是用 TDD 開發或是面對遺留代碼 (Legacy code),單元測試都是一個相當重要的工具。單元測試可以協助開發者確認每一個使用情境都是如預期般運行。
在現今的程式語言開發中,每一個語言都會有它們自己的單元測試,當然 Dart 也不例外。
Dart 的單元測試 package 在 https://pub.dev/packages/test ,目前的版本為 1.15.4。
開啟 pubspec.yaml
檔案,在 dev_dependencies:
新增 test: ^1.15.4
(預設應該就已經有加入 test 的 package,不過可能不是最新的)
name: unit_test_workshop
description: A starting point for Dart libraries or applications.
environment:
sdk: '>=2.8.1 <3.0.0'
dev_dependencies:
test: ^1.15.4
lib
目錄底下,有一個 battery.dart
。
test
目錄底下新增與待測試檔案相對應路徑的測試檔案,並且命名為 battery_test.dart
root directory --- lib --- src --- battery_.dart
|
|- test --- src --- battery_test.dart
|
-- pubspec.yaml
我們的 battery.dart 內容如下:
class Battery {
int powerLevel;
Battery(this.powerLevel);
bool isFullPower(){
return powerLevel == 100;
}
}
battery_test.dart 要怎麼寫呢?
每一個測試皆兩個函數,一是 test()
,另一則是 expect()
test():需要填入測試名稱與測試的內容。
expect():需要填入實際值與期望值,若實際值與期望值符合,則通過測試;反之,測試不通過。
import 'package:unit_test_workshop/src/battery.dart';
import 'package:test/test.dart';
void main(){
test('isFullPower() should return true when powerLevel is 100', (){
final battery = Battery(100);
final result = battery.isFullPower();
expect(result, true);
});
}
→ 執行測試,果然 pass。
isFullPower()
函數是否會回傳 false
。import 'package:unit_test_workshop/src/battery.dart';
import 'package:test/test.dart';
void main(){
test('isFullPower() should return true when powerLevel is 100', (){
final battery = Battery(100);
final result = battery.isFullPower();
expect(result, true);
});
test('isFullPower() should return false when powerLevel is 50', (){
final battery = Battery(50);
final result = battery.isFullPower();
expect(result, false);
});
}
→ 測試成功,代表 isFullPower()
的行為如我們預期。
由於這兩個測項都是測試 isFullPower()
函數,我們可以利用 group()
函數將這兩個測試整合在一起。測試時便可以 group() 為一個單位,進行多個測試。
void main(){
group('isFullPower()',(){
test('should return true when powerLevel is 100', (){
final battery = Battery(100);
final result = battery.isFullPower();
expect(result, true);
});
test('should return false when powerLevel is 50', (){
final battery = Battery(50);
final result = battery.isFullPower();
expect(result, false);
});
});
}
將 powerLevel 改為不在中建構子傳入,改由 setter 來修改
//battery.dart
class Battery {
int powerLevel;
Battery([this.powerLevel]);
bool isFullPower(){
return powerLevel == 100;
}
}
我們便可以將實例化 Battery 移到 setUp()
函式中執行:
void main(){
Battery battery;
group('isFullPower()',(){
setUp((){
battery = Battery();
});
test('should return true when powerLevel is 100', (){
battery.powerLevel = 100;
final result = battery.isFullPower();
expect(result, true);
});
test('should return false when powerLevel is 50', (){
battery.powerLevel = 50;
final result = battery.isFullPower();
expect(result, false);
});
});
}
假設,Battery 可以由外部輸入一個 Json 物件,裡面包含了電池的序號以及電量。
class Battery{
int powerLevel;
String serialNo;
Battery(this.serialNo,[this.powerLevel]);
bool isFullPower(){
return powerLevel == 100;
}
factory Battery.fromJson(Map<String, dynamic>jsonMap) {
return Battery(jsonMap['serialNo'], jsonMap['powerLevel']);
}
}
fromJson()
函式,這個函是用來解析傳入的Json 物件,並依據這個物件的內容建立一個 Battery 物件出來。void main(){
Battery battery;
group('fromJson', (){
final json = {'serialNo':'fake_serialno', 'powerLevel':50};
setUp((){
battery = Battery('fake_serialno', 50);
});
test('should return Battery when json is correct', (){
final result = Battery.fromJson(json);
expect(result, battery);
});
});
}
這是因為兩個物件在比較的時候, Dart 是用它們的 hashCode
來比較,但是建立兩次物件,縱使物件內容相同,其 hashCode
還是不同,所以測試的結果當然會是紅燈 (失敗)。
這時候我們可以安裝 [Equatable
package](https://pub.dev/packages/equatable) 。
將 Battery 類別繼承 Equatable 類,修改如下:
import 'package:equatable/equatable.dart';
class Battery extends Equatable{
final int powerLevel;
final String serialNo;
Battery(this.serialNo,[this.powerLevel]);
bool isFullPower(){
return powerLevel == 100;
}
factory Battery.fromJson(Map<String, dynamic>jsonMap) {
return Battery(jsonMap['serialNo'], jsonMap['powerLevel']);
}
@override
List<Object> get props =>[serialNo, powerLevel];
}
final
,因為 Equatable 有不可變屬性的註解(@immutable)。改完 Battery 之後,果然測試通過了。
單元測試在軟體開發是相當重要的一個環節,有了單元測試我們就可以確保我們的修改是如我們所預期,其他人在研究程式碼的時候,也可以依據單元測試來了解該類別的功能。
將需要被測試的內容加入 test()
函數中,再利用 expect()
來判斷運算的結果是否如預期。
若要比較兩個物件是否具有相同的屬性,可以使用 Equatable package 來協助我們用更直覺的方式判斷兩個物件。不過需要注意的是,要將屬性改為不可變的 (final),並且實作 props
函數。