1. مقدمة
Flutter هو مجموعة أدوات واجهة المستخدم من Google لإنشاء تطبيقات رائعة ومُجمَّعة إلى رموز أصلية للأجهزة الجوّالة والويب وأجهزة الكمبيوتر المكتبي، وذلك من خلال قاعدة رموز برمجية واحدة.
في هذا الدرس التطبيقي حول الترميز، ستتمكّن من إنشاء تطبيق بسيط من Flutter واختباره. سيستخدم التطبيق حزمة الموفّر لإدارة الحالة.
ما ستتعرَّف عليه
- كيفية إنشاء اختبارات التطبيقات المصغّرة باستخدام إطار عمل اختبار التطبيقات المصغّرة
- كيفية إنشاء اختبار دمج لاختبار واجهة مستخدم التطبيق وأدائه باستخدام مكتبة
integration_test
- كيفية اختبار فئات البيانات (الموفّرين) بمساعدة اختبارات الوحدات
ما الذي ستقوم ببنائه
في هذا الدرس التطبيقي حول الترميز، ستبدأ بإنشاء تطبيق بسيط يحتوي على قائمة بالعناصر. نحن نوفّر رمز المصدر لك حتى تتمكّن من الانتقال مباشرةً إلى الاختبار. يتيح التطبيق العمليات التالية:
- إضافة العناصر إلى المفضلة
- عرض قائمة الأجهزة المفضَّلة
- إزالة عناصر من قائمة "المفضلات"
بعد اكتمال التطبيق، ستكتب الاختبارات التالية:
| ملف GIF للتطبيق الذي يعمل على نظام Android |
ما الذي تريد تعلّمه من هذا الدرس التطبيقي حول الترميز؟
2. إعداد بيئة تطوير Flutter
لإكمال هذا التمرين، تحتاج إلى برنامجَين، وهما Flutter SDK ومحرِّر.
يمكنك تشغيل الدرس التطبيقي حول الترميز باستخدام أي من الأجهزة التالية:
- جهاز Android أو iOS فعلي متصل بجهاز الكمبيوتر وتم ضبطه على "وضع مطور البرامج".
- محاكي iOS (يتطلب تثبيت أدوات Xcode).
- محاكي Android (يتطلب عملية إعداد في "استوديو Android").
- متصفّح (يجب توفُّر متصفّح Chrome لتصحيح الأخطاء)
- كتطبيق سطح المكتب الذي يعمل بنظام التشغيل Windows أو Linux أو macOS. يجب إجراء تطوير على النظام الأساسي الذي تخطّط لنشر الإعلان عليه. لذا، إذا كنت ترغب في تطوير تطبيق سطح مكتب Windows، ينبغي لك تطويره على Windows للوصول إلى سلسلة الإصدار المناسبة. هناك متطلبات خاصة بنظام التشغيل تم تناولها بالتفصيل على docs.flutter.dev/desktop.
3- الخطوات الأولى
إنشاء تطبيق Flutter جديد تحديث التبعيات
يركّز هذا الدرس التطبيقي حول الترميز على اختبار تطبيق Flutter للأجهزة الجوّالة. ستُنشئ التطبيق سريعًا ليتم اختباره باستخدام ملفات المصدر التي تنسخها وتلصقها. ويركّز باقي الدرس التطبيقي حول الترميز على تعلُّم أنواع مختلفة من الاختبارات.
أنشِئ تطبيق 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.
أضِف التبعيات التي تخصّ الحانات إلى سطر الأوامر.
provider
لإدارة الحالات بسهولةintegration_test
لإجراء اختبار القيادة الذاتي لرمز Flutter على الأجهزة وأجهزة المحاكاة،flutter_driver
للحصول على واجهة برمجة تطبيقات متقدّمة لاختبار تطبيقات 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.
يجب إضافة التبعيات التالية إلى 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
في الدليل lib/screens
، أنشِئ ملفًا جديدًا آخر باسم home.dart
. في 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
إلى شاشة ثانية تحتوي على قائمة المفضلة.
التطبيق جاهز الآن للاختبار. ستبدأ في اختبار التطبيق في الخطوة التالية.
5- وحدة اختبار الموفِّر
ستبدأ باختبار الوحدة لنموذج favorites
. ما هو اختبار الوحدة؟ يتحقّق اختبار الوحدة من أنّ كل وحدة من البرامج، سواء كانت وظيفة أو كائنًا أو أداة، تؤدي مهمتها المقصودة بشكل صحيح.
يتم وضع جميع ملفات الاختبار في تطبيق 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()
مَعلمتَين موضعيتَين، وهما 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
.
لمزيد من المعلومات عن اختبار الوحدة، اطّلِع على المقالة مقدمة عن اختبار الوحدة.
6- اختبار التطبيقات المصغّرة
في هذه الخطوة، ستضيف رمزًا لاختبار التطبيقات المصغّرة. يقتصر اختبار التطبيقات المصغّرة على Flutter، حيث يمكنك اختبار كل تطبيق مصغّر بطريقة منعزلة. تختبر هذه الخطوة شاشتَي "HomePage
" و"FavoritesPage
" بشكلٍ فردي.
يستخدم اختبار التطبيقات المصغّرة الدالة testWidget()
بدلاً من الدالة test()
. مثل الدالة test()
، تستخدم الدالة testWidget()
معلمتين وهما: description,
و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. ويجب أن تكون أداة HomePage موجودةً أعلاه في شجرة الأدوات حتى يمكن اكتسابها منها والوصول إلى البيانات المعروضة عليها. يتمّ تمرير هذه الدالة كمَعلمة إلى الدالة 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);
});
});
إجراء الفحص
أولاً، نفِّذ الاختبار بالطريقة نفسها المُتّبعة لتشغيل اختبار الوحدة، وذلك باستخدام الأمر:
$ flutter test test/home_test.dart
من المفترض أن يتم إجراء الاختبار بسرعة، ومن المفترض أن تظهر رسالة مثل هذه:
00:02 +2: All tests passed!
يمكنك أيضًا إجراء اختبارات التطبيقات المصغّرة باستخدام جهاز أو محاكي يتيح لك مشاهدة الاختبار أثناء تشغيله. ويتيح لك أيضًا استخدام إعادة التشغيل السريع.
ابدأ بتوصيل جهازك أو تشغيل المحاكي. يمكنك أيضًا إجراء الاختبار كتطبيق متوافق مع أجهزة الكمبيوتر المكتبي.
من سطر الأوامر، انتقِل إلى الدليل الجذري للمشروع وأدخِل الأمر التالي:
$ 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
. يؤدي هذا الإجراء إلى إعادة تشغيل التطبيق وإعادة إجراء جميع الاختبارات.
الملف التجريبي الكامل: 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. اختبار واجهة المستخدم للتطبيق من خلال اختبارات الدمج
تُستخدم اختبارات التكامل لاختبار كيفية عمل الأجزاء الفردية من التطبيق معًا ككل. يتم استخدام مكتبة integration_test
لإجراء اختبارات الدمج في Flutter. هذا إصدار Flutter من إصدار Selenium WebDriver أو المنقلة أو Espresso أو Earl Gray. تستخدم الحزمة 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.
بعد اكتمال الاختبار بنجاح، يحتوي دليل الإصدار في جذر المشروع على ملفين:
- يحتوي
scrolling_summary.timeline_summary.json
على الملخّص. افتح الملف باستخدام أي محرر نصوص لمراجعة المعلومات الواردة فيه. - يتضمّن
scrolling_summary.timeline.json
بيانات المخطط الزمني الكاملة.
لمزيد من التفاصيل حول اختبار الدمج، يُرجى زيارة:
9. تهانينا!
لقد أكملت الدرس التطبيقي حول الترميز وتعلّمت طرقًا مختلفة لاختبار تطبيق Flutter.
ما تعلمته
- كيفية اختبار مقدّمي الخدمات بمساعدة اختبارات الوحدات
- كيفية اختبار التطبيقات المصغّرة باستخدام إطار عمل اختبار التطبيقات المصغّرة
- كيفية اختبار واجهة المستخدم الخاصة بالتطبيق باستخدام اختبارات الدمج
- كيفية اختبار أداء التطبيق باستخدام اختبارات الدمج
لمزيد من المعلومات حول الاختبار في Flutter، يُرجى الانتقال إلى