1. บทนำ
Flutter คือชุดเครื่องมือ UI ของ Google สำหรับการสร้างแอปพลิเคชันที่สวยงามซึ่งรวบรวมไว้ภายในอุปกรณ์เคลื่อนที่ เว็บ และเดสก์ท็อปจากฐานของโค้ดเดียว
ใน Codelab นี้ คุณจะได้สร้างและทดสอบแอป Flutter แบบง่ายๆ แอปจะใช้แพ็กเกจผู้ให้บริการสำหรับการจัดการสถานะ
สิ่งที่คุณจะได้เรียนรู้
- วิธีสร้างการทดสอบวิดเจ็ตโดยใช้เฟรมเวิร์กการทดสอบวิดเจ็ต
- วิธีสร้างการทดสอบการผสานรวมเพื่อทดสอบ UI และประสิทธิภาพของแอปโดยใช้ไลบรารี
integration_test
- วิธีทดสอบคลาสข้อมูล (ผู้ให้บริการ) โดยใช้การทดสอบ 1 หน่วย
สิ่งที่คุณจะสร้าง
ใน Codelab นี้ คุณจะเริ่มต้นด้วยการสร้างแอปพลิเคชันง่ายๆ ที่มีรายการรายการต่างๆ เราให้ซอร์สโค้ดแก่คุณเพื่อให้คุณเข้าสู่การทดสอบได้ทันที แอปสนับสนุนการทำงานต่อไปนี้
- การเพิ่มรายการลงในรายการโปรด
- การดูรายการโปรด
- การนำรายการออกจากรายการโปรด
เมื่อแอปเสร็จสมบูรณ์แล้ว คุณจะเขียนการทดสอบต่อไปนี้
| GIF ของแอปที่ทำงานบน Android |
คุณต้องการเรียนรู้อะไรจาก Codelab นี้
2. ตั้งค่าสภาพแวดล้อมในการพัฒนาซอฟต์แวร์ Flutter
ห้องทดลองนี้ต้องมีซอฟต์แวร์ 2 ประเภท ได้แก่ Flutter SDK และเครื่องมือแก้ไข
คุณเรียกใช้ Codelab ได้โดยใช้อุปกรณ์ต่อไปนี้
- อุปกรณ์ Android หรือ iOS ที่เชื่อมต่อกับคอมพิวเตอร์และตั้งค่าเป็นโหมดนักพัฒนาซอฟต์แวร์
- เครื่องมือจำลอง iOS (ต้องติดตั้งเครื่องมือ Xcode)
- โปรแกรมจำลอง Android (ต้องตั้งค่าใน Android Studio)
- เบราว์เซอร์ (การแก้ไขข้อบกพร่องต้องใช้ Chrome)
- เป็นแอปพลิเคชัน Windows, Linux หรือ macOS บนเดสก์ท็อป คุณต้องพัฒนาบนแพลตฟอร์มที่คุณวางแผนจะทำให้ใช้งานได้ ดังนั้นหากต้องการพัฒนาแอป Windows บนเดสก์ท็อป คุณต้องพัฒนาบน Windows เพื่อเข้าถึงเชนบิลด์ที่เหมาะสม มีข้อกำหนดเฉพาะระบบปฏิบัติการที่ครอบคลุมรายละเอียดใน docs.flutter.dev/desktop
3. เริ่มต้นใช้งาน
สร้างแอป Flutter ใหม่และ อัปเดตการอ้างอิง
Codelab นี้มุ่งเน้นไปที่การทดสอบแอปบนอุปกรณ์เคลื่อนที่ Flutter คุณจะสร้างแอปที่จะทดสอบได้อย่างรวดเร็วโดยใช้ไฟล์ต้นฉบับที่คุณคัดลอกและวาง จากนั้น Codelab ที่เหลือจะมุ่งเน้นที่การเรียนรู้การทดสอบประเภทต่างๆ
สร้างแอป Flutter ที่ใช้เทมเพลตง่ายๆ ตามวิธีการในการเริ่มต้นใช้งานแอป Flutter แรกของคุณ หรือบรรทัดคำสั่งต่อไปนี้
$ flutter create --empty testing_app Creating project testing_app... Resolving dependencies in `testing_app`... Downloading packages... Got dependencies in `testing_app`. Wrote 128 files. All done! You can find general documentation for Flutter at: https://docs.flutter.dev/ Detailed API documentation is available at: https://api.flutter.dev/ If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev In order to run your empty application, type: $ cd testing_app $ flutter run Your empty application code is in testing_app/lib/main.dart.
เพิ่มทรัพยากร Dependency ของ pub ในบรรทัดคำสั่ง
provider
เพื่อการจัดการสถานะที่ง่ายดายintegration_test
สำหรับการทดสอบโค้ด Flutter ด้วยตนเองในอุปกรณ์และโปรแกรมจำลองflutter_driver
สำหรับ API ขั้นสูงเพื่อทดสอบแอปพลิเคชัน Flutter ที่ทำงานในอุปกรณ์และโปรแกรมจำลองจริงtest
สำหรับเครื่องมือทดสอบทั่วไปgo_router
สำหรับการจัดการการไปยังส่วนต่างๆ ของแอป
$ cd testing_app $ flutter pub add provider go_router dev:test 'dev:flutter_driver:{"sdk":"flutter"}' 'dev:integration_test:{"sdk":"flutter"}' Resolving dependencies... Downloading packages... + _fe_analyzer_shared 67.0.0 (68.0.0 available) + analyzer 6.4.1 (6.5.0 available) + args 2.5.0 + convert 3.1.1 + coverage 1.7.2 + crypto 3.0.3 + file 7.0.0 + flutter_driver 0.0.0 from sdk flutter + flutter_web_plugins 0.0.0 from sdk flutter + frontend_server_client 4.0.0 + fuchsia_remote_debug_protocol 0.0.0 from sdk flutter + glob 2.1.2 + go_router 14.0.2 + http_multi_server 3.2.1 + http_parser 4.0.2 + integration_test 0.0.0 from sdk flutter + io 1.0.4 + js 0.7.1 leak_tracker 10.0.4 (10.0.5 available) leak_tracker_flutter_testing 3.0.3 (3.0.5 available) + logging 1.2.0 material_color_utilities 0.8.0 (0.11.1 available) meta 1.12.0 (1.14.0 available) + mime 1.0.5 + nested 1.0.0 + node_preamble 2.0.2 + package_config 2.1.0 + platform 3.1.4 + pool 1.5.1 + process 5.0.2 + provider 6.1.2 + pub_semver 2.1.4 + shelf 1.4.1 + shelf_packages_handler 3.0.2 + shelf_static 1.1.2 + shelf_web_socket 1.0.4 + source_map_stack_trace 2.1.1 + source_maps 0.10.12 + sync_http 0.3.1 + test 1.25.2 (1.25.4 available) test_api 0.7.0 (0.7.1 available) + test_core 0.6.0 (0.6.2 available) + typed_data 1.3.2 + watcher 1.1.0 + web 0.5.1 + web_socket_channel 2.4.5 + webdriver 3.0.3 + webkit_inspection_protocol 1.2.1 + yaml 3.1.2 Changed 44 dependencies! 9 packages have newer versions incompatible with dependency constraints. Try `flutter pub outdated` for more information.
ควรเพิ่มทรัพยากร Dependency ต่อไปนี้ใน pubspec.yaml แล้ว
pubspec.yaml
name: testing_app description: "A new Flutter project." publish_to: 'none' version: 0.1.0 environment: sdk: '>=3.4.0-0 <4.0.0' dependencies: flutter: sdk: flutter go_router: ^14.0.2 provider: ^6.1.2 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.0 test: ^1.25.2 flutter_driver: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true
เปิดโปรเจ็กต์ในตัวแก้ไขโค้ดที่ต้องการ แล้วเรียกใช้แอป หรือเรียกใช้ในบรรทัดคำสั่งดังต่อไปนี้
$ flutter run
4. สร้างแอป
ถัดไป คุณจะต้องสร้างแอปเพื่อทำการทดสอบ แอปประกอบด้วยไฟล์ต่อไปนี้
lib/models/favorites.dart
- สร้างคลาสโมเดลสำหรับรายการโปรดlib/screens/favorites.dart
- สร้างเลย์เอาต์สำหรับรายการโปรดlib/screens/home.dart
- สร้างรายการข้อมูลlib/main.dart
- ไฟล์หลักที่แอปเริ่มทำงาน
ขั้นแรก ให้สร้างโมเดล Favorites
ใน lib/models/favorites.dart
สร้างไดเรกทอรีใหม่ชื่อ models
ในไดเรกทอรี lib
แล้วสร้างไฟล์ใหม่ชื่อ favorites.dart
ในไฟล์ดังกล่าว ให้เพิ่มโค้ดต่อไปนี้
lib/models/favorites.dart
import 'package:flutter/material.dart';
/// The [Favorites] class holds a list of favorite items saved by the user.
class Favorites extends ChangeNotifier {
final List<int> _favoriteItems = [];
List<int> get items => _favoriteItems;
void add(int itemNo) {
_favoriteItems.add(itemNo);
notifyListeners();
}
void remove(int itemNo) {
_favoriteItems.remove(itemNo);
notifyListeners();
}
}
เพิ่มหน้ารายการโปรดใน lib/screens/favorites.dart
สร้างไดเรกทอรีใหม่ชื่อ screens
ในไดเรกทอรี lib
และสร้างไฟล์ใหม่ชื่อ favorites.dart
ในไดเรกทอรีดังกล่าว ในไฟล์ดังกล่าว ให้เพิ่มโค้ดต่อไปนี้
lib/screens/favorites.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/favorites.dart';
class FavoritesPage extends StatelessWidget {
const FavoritesPage({super.key});
static String routeName = 'favorites_page';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Favorites'),
),
body: Consumer<Favorites>(
builder: (context, value, child) => ListView.builder(
itemCount: value.items.length,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (context, index) => FavoriteItemTile(value.items[index]),
),
),
);
}
}
class FavoriteItemTile extends StatelessWidget {
const FavoriteItemTile(this.itemNo, {super.key});
final int itemNo;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
),
title: Text(
'Item $itemNo',
key: Key('favorites_text_$itemNo'),
),
trailing: IconButton(
key: Key('remove_icon_$itemNo'),
icon: const Icon(Icons.close),
onPressed: () {
Provider.of<Favorites>(context, listen: false).remove(itemNo);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Removed from favorites.'),
duration: Duration(seconds: 1),
),
);
},
),
),
);
}
}
เพิ่มหน้าแรกใน lib/screens/home.dart
สร้างไฟล์ใหม่ชื่อ home.dart
ในไดเรกทอรี lib/screens
ใน lib/screens/home.dart
ให้เพิ่มโค้ดต่อไปนี้
lib/screens/home.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../models/favorites.dart';
import 'favorites.dart';
class HomePage extends StatelessWidget {
static String routeName = '/';
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Testing Sample'),
actions: <Widget>[
TextButton.icon(
onPressed: () {
context.go('/${FavoritesPage.routeName}');
},
icon: const Icon(Icons.favorite_border),
label: const Text('Favorites'),
),
],
),
body: ListView.builder(
itemCount: 100,
cacheExtent: 20.0,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (context, index) => ItemTile(index),
),
);
}
}
class ItemTile extends StatelessWidget {
final int itemNo;
const ItemTile(this.itemNo, {super.key});
@override
Widget build(BuildContext context) {
var favoritesList = Provider.of<Favorites>(context);
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
),
title: Text(
'Item $itemNo',
key: Key('text_$itemNo'),
),
trailing: IconButton(
key: Key('icon_$itemNo'),
icon: favoritesList.items.contains(itemNo)
? const Icon(Icons.favorite)
: const Icon(Icons.favorite_border),
onPressed: () {
!favoritesList.items.contains(itemNo)
? favoritesList.add(itemNo)
: favoritesList.remove(itemNo);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(favoritesList.items.contains(itemNo)
? 'Added to favorites.'
: 'Removed from favorites.'),
duration: const Duration(seconds: 1),
),
);
},
),
),
);
}
}
แทนที่เนื้อหาของ lib/main.dart
แทนที่เนื้อหาของ lib/main.dart
ด้วยรหัสต่อไปนี้
lib/main.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'models/favorites.dart';
import 'screens/favorites.dart';
import 'screens/home.dart';
void main() {
runApp(const TestingApp());
}
final _router = GoRouter(
routes: [
GoRoute(
path: HomePage.routeName,
builder: (context, state) {
return const HomePage();
},
routes: [
GoRoute(
path: FavoritesPage.routeName,
builder: (context, state) {
return const FavoritesPage();
},
),
],
),
],
);
class TestingApp extends StatelessWidget {
const TestingApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Favorites>(
create: (context) => Favorites(),
child: MaterialApp.router(
title: 'Testing Sample',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
),
useMaterial3: true,
),
routerConfig: _router,
),
);
}
}
แอปเสร็จสมบูรณ์แล้วแต่ยังไม่ผ่านการทดสอบ
เรียกใช้แอป ซึ่งควรมีหน้าตาเหมือนกับภาพหน้าจอต่อไปนี้
แอปจะแสดงรายการต่างๆ แตะไอคอนรูปหัวใจในแถวใดก็ได้เพื่อใส่หัวใจและเพิ่มไอเทมลงในรายการโปรด ปุ่มรายการโปรดใน AppBar
จะนำคุณไปยังหน้าจอที่ 2 ที่มีรายการโปรด
ตอนนี้แอปพร้อมสำหรับการทดสอบแล้ว คุณจะเริ่มทดสอบแอปในขั้นตอนถัดไป
5. การทดสอบหน่วยวัดของผู้ให้บริการ
คุณจะเริ่มต้นโดยการทดสอบ 1 หน่วยของโมเดล favorites
การทดสอบ 1 หน่วยคืออะไร การทดสอบ 1 หน่วยจะตรวจสอบว่าซอฟต์แวร์แต่ละหน่วยนั้นเป็นฟังก์ชัน ออบเจ็กต์ หรือวิดเจ็ต ทำงานตามที่ต้องการได้อย่างถูกต้อง
ไฟล์ทดสอบทั้งหมดในแอป Flutter จะอยู่ในไดเรกทอรี test
ยกเว้นการทดสอบการผสานรวม
นำ test/widget_test.dart
ออก
ก่อนที่จะเริ่มทดสอบ ให้ลบไฟล์ widget_test.dart
คุณจะเพิ่มไฟล์ทดสอบของคุณเอง
สร้างไฟล์ทดสอบใหม่
ก่อนอื่น คุณจะต้องทดสอบเมธอด add()
ในโมเดล Favorites
เพื่อยืนยันว่ามีการเพิ่มรายการใหม่ลงในรายการ และรายการแสดงถึงการเปลี่ยนแปลง ตามปกติแล้ว โครงสร้างไดเรกทอรีในไดเรกทอรี test
จะเลียนแบบว่าในไดเรกทอรี lib
และไฟล์ Dart มีชื่อเดียวกันโดยมี _test
ต่อท้าย
สร้างไดเรกทอรี models
ในไดเรกทอรี test
ในไดเรกทอรีใหม่นี้ ให้สร้างไฟล์ favorites_test.dart
ที่มีเนื้อหาต่อไปนี้
test/models/favorites_test.dart
import 'package:test/test.dart';
import 'package:testing_app/models/favorites.dart';
void main() {
group('Testing App Provider', () {
var favorites = Favorites();
test('A new item should be added', () {
var number = 35;
favorites.add(number);
expect(favorites.items.contains(number), true);
});
});
}
เฟรมเวิร์กการทดสอบ Flutter ช่วยให้คุณเชื่อมโยงการทดสอบที่คล้ายกันซึ่งเกี่ยวข้องกันในกลุ่มได้ ไฟล์ทดสอบไฟล์เดียวอาจมีได้หลายกลุ่มเพื่อทดสอบส่วนต่างๆ ของไฟล์ที่เกี่ยวข้องในไดเรกทอรี lib
เมธอด test()
ใช้พารามิเตอร์ตำแหน่ง 2 ตัว ได้แก่ description
ของการทดสอบและ callback
ที่คุณใช้เขียนการทดสอบจริงๆ
ทดสอบการนำรายการออกจากรายการ แทรกการทดสอบต่อไปนี้ในกลุ่ม Testing App Provider
เดียวกัน:
test/models/favorites_test.dart
test('An item should be removed', () {
var number = 45;
favorites.add(number);
expect(favorites.items.contains(number), true);
favorites.remove(number);
expect(favorites.items.contains(number), false);
});
ทำการทดสอบ
ไปที่ไดเรกทอรีรากของโปรเจ็กต์ในบรรทัดคำสั่ง แล้วป้อนคำสั่งต่อไปนี้
$ flutter test test/models/favorites_test.dart
หากทุกอย่างทำงานเป็นปกติ คุณควรเห็นข้อความที่คล้ายกับข้อความต่อไปนี้
00:06 +2: All tests passed!
ไฟล์ทดสอบที่สมบูรณ์: test/models/favorites_test.dart
ดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบ 1 หน่วยได้ที่ข้อมูลเบื้องต้นเกี่ยวกับการทดสอบ 1 หน่วย
6. การทดสอบวิดเจ็ต
ในขั้นตอนนี้ คุณจะต้องเพิ่มโค้ดเพื่อทดสอบวิดเจ็ต การทดสอบวิดเจ็ตมีเอกลักษณ์เฉพาะสำหรับ Flutter ซึ่งคุณจะทดสอบวิดเจ็ตแต่ละรายการได้แบบแยกส่วน ขั้นตอนนี้จะทดสอบ HomePage
และ FavoritesPage
หน้าจอแยกกัน
การทดสอบวิดเจ็ตใช้ฟังก์ชัน testWidget()
แทนฟังก์ชัน test()
เช่นเดียวกับฟังก์ชัน test()
ฟังก์ชัน testWidget()
จะใช้พารามิเตอร์ 2 รายการ ได้แก่ description,
และ callback
แต่ Callback จะใช้ WidgetTester
เป็นอาร์กิวเมนต์
การทดสอบวิดเจ็ตใช้ TestFlutterWidgetsBinding
ซึ่งเป็นคลาสที่ให้ทรัพยากรเดียวกันกับวิดเจ็ตที่จะมีในแอปที่ทำงานอยู่ เช่น ข้อมูลเกี่ยวกับขนาดหน้าจอ ความสามารถในการตั้งเวลาภาพเคลื่อนไหว โดยไม่ต้องทำงานภายในแอป ระบบจะใช้สภาพแวดล้อมเสมือนในการสร้างวิดเจ็ต แล้วทำการทดสอบผลลัพธ์แทน ในส่วนนี้ pumpWidget
ได้เริ่มต้นกระบวนการด้วยการบอกให้เฟรมเวิร์กต่อเชื่อมและวัดวิดเจ็ตหนึ่งๆ เช่นเดียวกับที่ทำในแอปพลิเคชัน
เฟรมเวิร์กการทดสอบวิดเจ็ตช่วยให้เครื่องมือค้นหาค้นหาวิดเจ็ตได้ เช่น text()
, byType()
และ byIcon().
นอกจากนี้ เฟรมเวิร์กยังมีเครื่องมือจับคู่เพื่อตรวจสอบผลลัพธ์ด้วย
เริ่มต้นด้วยการทดสอบวิดเจ็ต HomePage
สร้างไฟล์ทดสอบใหม่
การทดสอบแรกจะตรวจสอบว่าการเลื่อน HomePage
ทำงานถูกต้องหรือไม่
สร้างไฟล์ใหม่ในไดเรกทอรี test
และตั้งชื่อว่า home_test.dart
เพิ่มโค้ดต่อไปนี้ในไฟล์ที่สร้างขึ้นใหม่
test/home_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:testing_app/models/favorites.dart';
import 'package:testing_app/screens/home.dart';
Widget createHomeScreen() => ChangeNotifierProvider<Favorites>(
create: (context) => Favorites(),
child: const MaterialApp(
home: HomePage(),
),
);
void main() {
group('Home Page Widget Tests', () {
testWidgets('Testing Scrolling', (tester) async {
await tester.pumpWidget(createHomeScreen());
expect(find.text('Item 0'), findsOneWidget);
await tester.fling(
find.byType(ListView),
const Offset(0, -200),
3000,
);
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsNothing);
});
});
}
ใช้ฟังก์ชัน createHomeScreen()
เพื่อสร้างแอปที่โหลดวิดเจ็ตที่จะทดสอบใน MaterialApp โดยรวมไว้ใน ChangeNotifierProvider วิดเจ็ตของหน้าแรกจำเป็นต้องมีทั้ง 2 วิดเจ็ตนี้ไว้ด้านบนในแผนผังวิดเจ็ต เพื่อให้วิดเจ็ตนั้นรับค่าจากวิดเจ็ตและเข้าถึงข้อมูลที่มีได้ ระบบจะส่งฟังก์ชันนี้เป็นพารามิเตอร์ไปยังฟังก์ชัน pumpWidget()
ถัดไป ให้ทดสอบว่าเฟรมเวิร์กนี้สามารถค้นหา ListView
ที่แสดงผลบนหน้าจอได้หรือไม่
เพิ่มข้อมูลโค้ดต่อไปนี้ใน home_test.dart
:
test/home_test.dart
group('Home Page Widget Tests', () {
// BEGINNING OF NEW CONTENT
testWidgets('Testing if ListView shows up', (tester) async {
await tester.pumpWidget(createHomeScreen());
expect(find.byType(ListView), findsOneWidget);
});
// END OF NEW CONTENT
testWidgets('Testing Scrolling', (tester) async {
await tester.pumpWidget(createHomeScreen());
expect(find.text('Item 0'), findsOneWidget);
await tester.fling(
find.byType(ListView),
const Offset(0, -200),
3000,
);
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsNothing);
});
});
ทำการทดสอบ
ขั้นแรก ให้ทำการทดสอบด้วยวิธีเดียวกับที่คุณทำการทดสอบ 1 หน่วย โดยใช้คำสั่ง:
$ flutter test test/home_test.dart
การทดสอบควรทำงานอย่างรวดเร็ว และคุณจะเห็นข้อความต่อไปนี้:
00:02 +2: All tests passed!
นอกจากนี้ คุณยังเรียกใช้การทดสอบวิดเจ็ตโดยใช้อุปกรณ์หรือโปรแกรมจำลองได้ ซึ่งช่วยให้ดูการทดสอบที่ทำงานอยู่ได้ นอกจากนี้ยังสามารถใช้ Hot รีสตาร์ทได้ด้วย
เสียบปลั๊กอุปกรณ์หรือเริ่มโปรแกรมจำลอง นอกจากนี้คุณยังทำการทดสอบเป็นแอปพลิเคชันบนเดสก์ท็อปได้อีกด้วย
จากบรรทัดคำสั่ง ให้ไปที่ไดเรกทอรีรากของโปรเจ็กต์และป้อนคำสั่งต่อไปนี้
$ flutter run test/home_test.dart
คุณอาจต้องเลือกอุปกรณ์ที่จะทำการทดสอบ ในกรณีนี้ให้ทำตามวิธีการและเลือกอุปกรณ์
Multiple devices found:
Linux (desktop) • linux • linux-x64 • Ubuntu 22.04.1 LTS 5.15.0-58-generic
Chrome (web) • chrome • web-javascript • Google Chrome 109.0.5414.119
[1]: Linux (linux)
[2]: Chrome (chrome)
Please choose one (To quit, press "q/Q"):
หากทุกอย่างทำงานได้ดี คุณควรเห็นผลลัพธ์ที่คล้ายกับข้อความต่อไปนี้
Launching test/home_test.dart on Linux in debug mode...
Building Linux application...
flutter: 00:00 +0: Home Page Widget Tests Testing if ListView shows up
Syncing files to device Linux... 62ms
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
💪 Running with sound null safety 💪
An Observatory debugger and profiler on Linux is available at: http://127.0.0.1:35583/GCpdLBqf2UI=/
flutter: 00:00 +1: Home Page Widget Tests Testing Scrolling
The Flutter DevTools debugger and profiler on Linux is available at:
http://127.0.0.1:9100?uri=http://127.0.0.1:35583/GCpdLBqf2UI=/
flutter: 00:02 +2: All tests passed!
ถัดไป คุณจะต้องแก้ไขไฟล์ทดสอบ แล้วกด Shift + R
เพื่อรีสตาร์ทแอปและเรียกใช้การทดสอบทั้งหมดอีกครั้ง อย่าหยุดแอปพลิเคชัน
เพิ่มการทดสอบไปยังกลุ่มที่ทดสอบวิดเจ็ตในหน้าแรก คัดลอกการทดสอบต่อไปนี้ไปยังไฟล์
test/home_test.dart
testWidgets('Testing IconButtons', (tester) async {
await tester.pumpWidget(createHomeScreen());
expect(find.byIcon(Icons.favorite), findsNothing);
await tester.tap(find.byIcon(Icons.favorite_border).first);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Added to favorites.'), findsOneWidget);
expect(find.byIcon(Icons.favorite), findsWidgets);
await tester.tap(find.byIcon(Icons.favorite).first);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Removed from favorites.'), findsOneWidget);
expect(find.byIcon(Icons.favorite), findsNothing);
});
การทดสอบนี้ยืนยันว่าการแตะIconButton
เปลี่ยนจาก Icons.favorite_border
(หัวใจเปิด) เป็น Icons.favorite
(หัวใจเต็มอิ่ม) จากนั้นกลับไปเป็น Icons.favorite_border
เมื่อแตะอีกครั้ง
ป้อน Shift + R
Hot นี้จะรีสตาร์ทแอปและเรียกใช้การทดสอบทั้งหมดอีกครั้ง
ไฟล์ทดสอบที่สมบูรณ์: test/home_test.dart
.
ใช้กระบวนการเดียวกันเพื่อทดสอบ FavoritesPage
ด้วยโค้ดต่อไปนี้ ทำตามขั้นตอนเดิมและเรียกใช้
test/favorites_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:testing_app/models/favorites.dart';
import 'package:testing_app/screens/favorites.dart';
late Favorites favoritesList;
Widget createFavoritesScreen() => ChangeNotifierProvider<Favorites>(
create: (context) {
favoritesList = Favorites();
return favoritesList;
},
child: const MaterialApp(
home: FavoritesPage(),
),
);
void addItems() {
for (var i = 0; i < 10; i += 2) {
favoritesList.add(i);
}
}
void main() {
group('Favorites Page Widget Tests', () {
testWidgets('Test if ListView shows up', (tester) async {
await tester.pumpWidget(createFavoritesScreen());
addItems();
await tester.pumpAndSettle();
expect(find.byType(ListView), findsOneWidget);
});
testWidgets('Testing Remove Button', (tester) async {
await tester.pumpWidget(createFavoritesScreen());
addItems();
await tester.pumpAndSettle();
var totalItems = tester.widgetList(find.byIcon(Icons.close)).length;
await tester.tap(find.byIcon(Icons.close).first);
await tester.pumpAndSettle();
expect(tester.widgetList(find.byIcon(Icons.close)).length,
lessThan(totalItems));
expect(find.text('Removed from favorites.'), findsOneWidget);
});
});
}
การทดสอบนี้จะยืนยันว่ารายการหายไปหรือไม่เมื่อกดปุ่มปิด (นำออก)
ดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบวิดเจ็ตได้ที่
7. การทดสอบ UI ของแอปด้วยการทดสอบการผสานรวม
การทดสอบการผสานรวมใช้เพื่อทดสอบว่าแอปแต่ละรายการทำงานร่วมกันอย่างไร ไลบรารี integration_test
ใช้เพื่อทำการทดสอบการผสานรวมใน Flutter นี่คือเวอร์ชัน Selenium WebDriver, Protractor, Espresso หรือ Earl Gray ของ Flutter แพ็กเกจจะใช้ flutter_driver
เป็นการภายในเพื่อทำการทดสอบในอุปกรณ์
การทดสอบการเขียนการผสานรวมใน Flutter จะคล้ายกับการทดสอบวิดเจ็ตการเขียน ยกเว้นว่าการทดสอบการผสานรวมจะทํางานบนอุปกรณ์เคลื่อนที่ เบราว์เซอร์ หรือแอปพลิเคชันเดสก์ท็อปที่เรียกว่าอุปกรณ์เป้าหมาย
เขียนแบบทดสอบ
สร้างไดเรกทอรีชื่อ integration_test
ในไดเรกทอรีรากของโครงการ แล้วสร้างไฟล์ใหม่ชื่อ app_test.dart
ในไดเรกทอรีดังกล่าว
integration_test/app_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:testing_app/main.dart';
void main() {
group('Testing App', () {
testWidgets('Favorites operations test', (tester) async {
await tester.pumpWidget(const TestingApp());
final iconKeys = [
'icon_0',
'icon_1',
'icon_2',
];
for (var icon in iconKeys) {
await tester.tap(find.byKey(ValueKey(icon)));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Added to favorites.'), findsOneWidget);
}
await tester.tap(find.text('Favorites'));
await tester.pumpAndSettle();
final removeIconKeys = [
'remove_icon_0',
'remove_icon_1',
'remove_icon_2',
];
for (final iconKey in removeIconKeys) {
await tester.tap(find.byKey(ValueKey(iconKey)));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Removed from favorites.'), findsOneWidget);
}
});
});
}
ทำการทดสอบ
เสียบปลั๊กอุปกรณ์หรือเริ่มโปรแกรมจำลอง นอกจากนี้คุณยังทำการทดสอบเป็นแอปพลิเคชันบนเดสก์ท็อปได้อีกด้วย
ไปที่ไดเรกทอรีรากของโปรเจ็กต์ในบรรทัดคำสั่ง แล้วป้อนคำสั่งต่อไปนี้
$ flutter test integration_test/app_test.dart
หากทุกอย่างทำงานได้ดี คุณควรเห็นผลลัพธ์ที่คล้ายกับข้อความต่อไปนี้
Multiple devices found:
Linux (desktop) • linux • linux-x64 • Ubuntu 22.04.1 LTS 5.15.0-58-generic
Chrome (web) • chrome • web-javascript • Google Chrome 109.0.5414.119
[1]: Linux (linux)
[2]: Chrome (chrome)
Please choose one (To quit, press "q/Q"): 1
00:00 +0: loading /home/miquel/tmp/testing_app/integration_test/app_test.dart B00:08 +0: loading /home/miquel/tmp/testing_app/integration_test/app_test.dart
00:26 +1: All tests passed!
8. การทดสอบประสิทธิภาพของแอปด้วย Flutter Driver
เขียนการทดสอบประสิทธิภาพ
สร้างไฟล์ทดสอบใหม่ชื่อ perf_test.dart ในโฟลเดอร์ Integration_test ที่มีเนื้อหาต่อไปนี้
integration_test/perf_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:testing_app/main.dart';
void main() {
group('Testing App Performance', () {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
testWidgets('Scrolling test', (tester) async {
await tester.pumpWidget(const TestingApp());
final listFinder = find.byType(ListView);
await binding.traceAction(() async {
await tester.fling(listFinder, const Offset(0, -500), 10000);
await tester.pumpAndSettle();
await tester.fling(listFinder, const Offset(0, 500), 10000);
await tester.pumpAndSettle();
}, reportKey: 'scrolling_summary');
});
});
}
ฟังก์ชัน ensureInitialized()
จะตรวจสอบว่าไดรเวอร์เพื่อการทดสอบการผสานรวมเริ่มทำงานแล้วหรือยัง และเริ่มต้นการทำงานใหม่หากจำเป็น การตั้งค่า framePolicy
เป็น fullyLive
เหมาะสำหรับการทดสอบโค้ดภาพเคลื่อนไหว
การทดสอบนี้จะเลื่อนผ่านรายการต่างๆ อย่างรวดเร็ว แล้วเลื่อนขึ้นไปจนสุด ฟังก์ชัน traceAction()
จะบันทึกการดำเนินการและสร้างสรุปไทม์ไลน์
บันทึกผลลัพธ์ด้านประสิทธิภาพ
หากต้องการเก็บผลลัพธ์ ให้สร้างโฟลเดอร์ชื่อ test_driver
โดยใช้ไฟล์ชื่อ perf_driver.dart
และเพิ่มโค้ดต่อไปนี้
test_driver/perf_driver.dart
import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() {
return integrationDriver(
responseDataCallback: (data) async {
if (data != null) {
final timeline = driver.Timeline.fromJson(
data['scrolling_summary'] as Map<String, dynamic>);
final summary = driver.TimelineSummary.summarize(timeline);
await summary.writeTimelineToFile(
'scrolling_summary',
pretty: true,
includeSummary: true,
);
}
},
);
}
ทำการทดสอบ
เสียบปลั๊กอุปกรณ์หรือเริ่มโปรแกรมจำลอง
ไปที่ไดเรกทอรีรากของโปรเจ็กต์ในบรรทัดคำสั่ง แล้วป้อนคำสั่งต่อไปนี้
$ flutter drive \
--driver=test_driver/perf_driver.dart \
--target=integration_test/perf_test.dart \
--profile \
--no-dds
หากทุกอย่างทำงานได้ดี คุณควรเห็นผลลัพธ์ที่คล้ายกับข้อความต่อไปนี้
Running "flutter pub get" in testing_app...
Resolving dependencies...
archive 3.3.2 (3.3.6 available)
collection 1.17.0 (1.17.1 available)
js 0.6.5 (0.6.7 available)
matcher 0.12.13 (0.12.14 available)
meta 1.8.0 (1.9.0 available)
path 1.8.2 (1.8.3 available)
test 1.22.0 (1.23.0 available)
test_api 0.4.16 (0.4.18 available)
test_core 0.4.20 (0.4.23 available)
vm_service 9.4.0 (11.0.1 available)
webdriver 3.0.1 (3.0.2 available)
Got dependencies!
Running Gradle task 'assembleProfile'... 1,379ms
✓ Built build/app/outputs/flutter-apk/app-profile.apk (14.9MB).
Installing build/app/outputs/flutter-apk/app-profile.apk... 222ms
I/flutter ( 6125): 00:04 +1: Testing App Performance (tearDownAll)
I/flutter ( 6125): 00:04 +2: All tests passed!
All tests passed.
หลังจากการทดสอบเสร็จสมบูรณ์แล้ว ไดเรกทอรีบิลด์ที่รูทของโปรเจ็กต์จะมีไฟล์ 2 ไฟล์ ดังนี้
scrolling_summary.timeline_summary.json
มีข้อมูลสรุป เปิดไฟล์ด้วยเครื่องมือแก้ไขข้อความใดก็ได้เพื่อดูข้อมูลที่มีอยู่scrolling_summary.timeline.json
มีข้อมูลไทม์ไลน์ที่สมบูรณ์
ดูรายละเอียดเพิ่มเติมเกี่ยวกับการทดสอบการผสานรวมได้ที่
9. ยินดีด้วย
คุณศึกษา Codelab จนจบและเรียนรู้วิธีต่างๆ ในการทดสอบแอป Flutter แล้ว
สิ่งที่คุณได้เรียนรู้
- วิธีทดสอบผู้ให้บริการโดยใช้การทดสอบ 1 หน่วย
- วิธีทดสอบวิดเจ็ตโดยใช้เฟรมเวิร์กการทดสอบวิดเจ็ต
- วิธีทดสอบ UI ของแอปโดยใช้การทดสอบการผสานรวม
- วิธีทดสอบประสิทธิภาพของแอปโดยใช้การทดสอบการผสานรวม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบใน Flutter ได้ที่