การ Testing แอป Flutter ด้วย Widget Tests และ Integration Tests
ในบทความก่อนหน้า เราได้เรียนรู้เกี่ยวกับการ Debugging แอป Flutter ด้วย Flutter DevTools ซึ่งเป็นเครื่องมือสำคัญในการค้นหาและแก้ไขข้อผิดพลาดในโค้ดของเรา ในบทความนี้ เราจะมาพูดถึงอีกหนึ่งเครื่องมือสำคัญที่จะช่วยให้แอปพลิเคชันของเรามีคุณภาพ นั่นคือ การ Testing แอป Flutter ด้วย Widget Tests และ Integration Tests
การ Testing เป็นกระบวนการตรวจสอบว่าโค้ดของเราทำงานได้อย่างถูกต้องตามที่คาดหวังไว้ การทำ Test ช่วยให้เรามั่นใจได้ว่าเมื่อเราทำการเปลี่ยนแปลงโค้ด จะไม่ส่งผลกระทบต่อส่วนอื่น ๆ ของแอปพลิเคชัน และยังช่วยลดโอกาสในการเกิดข้อผิดพลาดใน Production อีกด้วย
ทำไมต้อง Testing ใน Flutter?
- ป้องกัน Regression: การ Testing ช่วยให้เราตรวจจับข้อผิดพลาดที่เกิดจากการเปลี่ยนแปลงโค้ดได้อย่างรวดเร็ว
- ปรับปรุงคุณภาพโค้ด: การเขียน Test บังคับให้เราคิดถึงการออกแบบโค้ด ทำให้โค้ดของเรามีความชัดเจนและง่ายต่อการบำรุงรักษา
- เพิ่มความมั่นใจ: การมี Test ที่ครอบคลุมช่วยให้เรามั่นใจในการ Deploy แอปพลิเคชัน
ประเภทของการ Testing ใน Flutter
ใน Flutter มีการ Testing หลายประเภท แต่ในบทความนี้เราจะเน้นที่ Widget Tests และ Integration Tests ซึ่งเป็นประเภทที่สำคัญและใช้กันบ่อย
- Widget Tests: ทดสอบ Widget แต่ละตัวว่าทำงานถูกต้องหรือไม่ เช่น การแสดงผลข้อมูล การตอบสนองต่อการ Interact ของผู้ใช้
- Integration Tests: ทดสอบการทำงานร่วมกันของ Widget หลายตัว หรือการทำงานร่วมกันของแอปพลิเคชันกับระบบภายนอก เช่น API หรือ Database
การเริ่มต้นใช้งาน Testing ใน Flutter
Flutter มี package ที่ชื่อว่า flutter_test ที่ช่วยให้เราเขียน Test ได้อย่างง่ายดาย package นี้ถูกติดตั้งมาพร้อมกับ Flutter SDK อยู่แล้ว ดังนั้นเราจึงสามารถเริ่มใช้งานได้ทันที
โครงสร้างไฟล์ Test
โดยทั่วไปแล้ว ไฟล์ Test จะถูกเก็บไว้ใน directory test โดยมีโครงสร้างไฟล์ดังนี้:
my_app/
lib/
main.dart
test/
widget_test.dart
ไฟล์ widget_test.dart จะเป็นไฟล์ที่เราเขียน Widget Tests
Widget Tests
Widget Tests ใช้สำหรับทดสอบ Widget แต่ละตัวว่าทำงานถูกต้องหรือไม่ โดยเราจะใช้ class WidgetTester ในการ Interact กับ Widget และตรวจสอบผลลัพธ์
ตัวอย่าง Widget Test
สมมติว่าเรามี Widget ที่แสดงข้อความ "Hello, World!" เราสามารถเขียน Widget Test เพื่อตรวจสอบว่า Widget นั้นแสดงข้อความถูกต้องหรือไม่ ดังนี้:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart'; // สมมติว่า main.dart คือไฟล์ที่ Widget ของเราอยู่
void main() {
testWidgets('MyWidget displays "Hello, World!"', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp()); // MyApp() คือ Widget ที่เราต้องการทดสอบ
// Verify that our counter starts at 0.
expect(find.text('Hello, World!'), findsOneWidget);
});
}
คำอธิบายโค้ด:
testWidgetsคือ function ที่ใช้สำหรับกำหนด Widget TestWidgetTesterคือ class ที่ใช้สำหรับ Interact กับ Widgettester.pumpWidgetใช้สำหรับสร้าง Widget ที่เราต้องการทดสอบfind.text('Hello, World!')ใช้สำหรับค้นหา Widget ที่มีข้อความ "Hello, World!"findsOneWidgetใช้สำหรับตรวจสอบว่ามี Widget ที่ตรงกับเงื่อนไขการค้นหาเพียง 1 ตัวexpectใช้สำหรับตรวจสอบว่าผลลัพธ์เป็นไปตามที่คาดหวัง
กรณีใช้งานจริงของ Widget Tests
1. ทดสอบการแสดงผลข้อมูล: ตรวจสอบว่า Widget แสดงข้อมูลถูกต้องหรือไม่ เช่น การแสดงชื่อผู้ใช้ รูปภาพ หรือรายละเอียดสินค้า
testWidgets('User name displays correctly', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: UserProfile(name: 'John Doe'), // UserProfile คือ Widget ที่แสดงข้อมูลผู้ใช้
),
);
expect(find.text('John Doe'), findsOneWidget);
});
2. ทดสอบการ Interact ของผู้ใช้: ตรวจสอบว่า Widget ตอบสนองต่อการ Interact ของผู้ใช้ถูกต้องหรือไม่ เช่น การกดปุ่ม การป้อนข้อความ หรือการเลือกรายการ
testWidgets('Button tap increments counter', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: CounterApp())); // CounterApp คือ Widget ที่มีปุ่มและตัวนับ
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('1'), findsOneWidget);
});
ข้อผิดพลาดที่พบบ่อยในการเขียน Widget Tests
1. ลืมเรียก tester.pumpWidget: หากเราลืมเรียก tester.pumpWidget Widget ของเราจะไม่ถูกสร้างขึ้น ทำให้ Test ล้มเหลว
2. ใช้ find ไม่ถูกต้อง: หากเราใช้ find ไม่ถูกต้อง เช่น ค้นหา Widget ที่ไม่มีอยู่จริง Test ของเราก็จะล้มเหลว
3. ไม่รอ Animation เสร็จสิ้น: หาก Widget ของเรามีการ Animation เราจะต้องรอให้ Animation เสร็จสิ้นก่อนที่จะตรวจสอบผลลัพธ์
Integration Tests
Integration Tests ใช้สำหรับทดสอบการทำงานร่วมกันของ Widget หลายตัว หรือการทำงานร่วมกันของแอปพลิเคชันกับระบบภายนอก เช่น API หรือ Database
ในการเขียน Integration Tests เราจะต้องใช้ package integration_test ซึ่งเป็น package ที่แยกออกมาจาก flutter_test
การติดตั้ง integration_test
เพิ่ม dependency integration_test ลงในไฟล์ pubspec.yaml:
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
จากนั้นทำการ flutter pub get เพื่อติดตั้ง package
การตั้งค่า Integration Tests
สร้าง directory integration_test ใน root directory ของโปรเจกต์
my_app/
lib/
main.dart
test/
widget_test.dart
integration_test/
app_test.dart
ในไฟล์ integration_test/app_test.dart เราจะเขียน Integration Tests ของเรา
ตัวอย่าง Integration Test
สมมติว่าเรามีแอปพลิเคชันที่ดึงข้อมูลจาก API และแสดงผลบนหน้าจอ เราสามารถเขียน Integration Test เพื่อตรวจสอบว่าแอปพลิเคชันดึงข้อมูลและแสดงผลได้อย่างถูกต้องหรือไม่ ดังนี้:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart'; // สมมติว่า main.dart คือไฟล์ที่แอปพลิเคชันของเราอยู่
import 'package:http/http.dart' as http;
import 'dart:convert';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('App fetches data from API and displays it', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Wait for the data to load.
await tester.pumpAndSettle();
// Verify that the data is displayed.
expect(find.text('Data from API'), findsOneWidget); // สมมติว่า API return ข้อความนี้
});
}
คำอธิบายโค้ด:
IntegrationTestWidgetsFlutterBinding.ensureInitialized()จำเป็นต้องเรียกก่อนที่จะเริ่ม Integration Teststester.pumpAndSettle()ใช้สำหรับรอให้แอปพลิเคชันโหลดข้อมูลและแสดงผล
กรณีใช้งานจริงของ Integration Tests
1. ทดสอบการทำงานกับ API: ตรวจสอบว่าแอปพลิเคชันสามารถดึงข้อมูลจาก API ได้อย่างถูกต้อง และแสดงผลข้อมูลได้อย่างถูกต้อง
2. ทดสอบการทำงานกับ Database: ตรวจสอบว่าแอปพลิเคชันสามารถอ่าน เขียน และอัปเดตข้อมูลใน Database ได้อย่างถูกต้อง
3. ทดสอบการ Navigation ระหว่างหน้าจอ: ตรวจสอบว่าแอปพลิเคชันสามารถ Navigate ระหว่างหน้าจอได้อย่างถูกต้อง
ข้อผิดพลาดที่พบบ่อยในการเขียน Integration Tests
1. ลืมเรียก IntegrationTestWidgetsFlutterBinding.ensureInitialized(): หากเราลืมเรียก IntegrationTestWidgetsFlutterBinding.ensureInitialized() Test ของเราจะไม่ทำงาน
2. ไม่รอให้แอปพลิเคชันโหลดข้อมูลเสร็จ: หากเราไม่รอให้แอปพลิเคชันโหลดข้อมูลเสร็จ Test ของเราอาจจะล้มเหลว
สรุป
การ Testing เป็นส่วนสำคัญในการพัฒนาแอปพลิเคชัน Flutter ที่มีคุณภาพ การเขียน Widget Tests และ Integration Tests ช่วยให้เรามั่นใจได้ว่าแอปพลิเคชันของเราทำงานได้อย่างถูกต้อง และช่วยลดโอกาสในการเกิดข้อผิดพลาดใน Production
ในบทความถัดไป เราจะมาเรียนรู้เกี่ยวกับการ Deploy แอป Flutter ไปยัง Android และ iOS