اعتبارسنجی رابط کاربری کارآمد: بررسی تست ویجت در Flutter (تستهای UI)

Summarize this content to 400 words in Persian Lang
کمی زمینه
تست ویجت نوعی آزمایش است که گاهی دست کم گرفته می شود، اما ارزش قابل توجهی دارد. در این مقاله، تست ویجت را بررسی میکنیم، نکاتی را ارائه میکنیم که به شما کمک میکند آن را در جریان کار روزانه خود بگنجانید، و کاربردهای عملی آن را نشان میدهیم.
قبل از غواصی، ذکر این نکته ضروری است که مستندات رسمی فلاتر یک نقطه شروع عالی است. این نمونههای ساده و کاربردی را ارائه میکند که به شما درک کاملی از نحوه کار کردن چیزها میدهد. در اینجا، خلاصهای از اطلاعات کلیدی را به همراه بینشهای شخصی که زمان زیادی را صرف کار با این نوع آزمایش کرده است، خواهید یافت.
چه مشکلی را حل می کند؟
برای درک نقش و انواع تست های ویجت، این ویدیو را در کانال یوتیوب Flutter تماشا کنید. این سه نوع تست UI را توضیح می دهد:
تست های طلایی (متمرکز بر پیکسل کامل)؛
تست های یاب (Comportamento);
تست های PaitPattern (دستورالعمل های ترسیم)؛
ما روی نوع دوم تمرکز خواهیم کرد، Finder tests، که رفتار اجزای شما را تایید می کند. همانطور که در اسناد Flutter آمده است: “بسیاری از ویجت ها نه تنها اطلاعات را نمایش می دهند، بلکه به تعامل کاربر نیز پاسخ می دهند. این شامل دکمه هایی است که می توان روی آنها ضربه زد و TextFields برای وارد کردن متن.”
مفاهیم اساسی
ایجاد این نوع تست بسیار شبیه به تست واحد است. با این حال، باید SDK تست ابزارک Flutter را اضافه کنید:
dev_dependencies:
flutter_test:
sdk: flutter
پس از انجام این کار، به روش های لازم برای ساخت و اجرای تست های خود دسترسی خواهید داشت.
یک راه سریع برای ایجاد یک فایل آزمایشی در VSCode با کلیک راست و انتخاب گزینه Go to Test است. این دستور بررسی می کند که آیا یک فایل آزمایشی از قبل وجود دارد یا خیر. اگر نه، پیشنهاد ایجاد یکی را می دهد. این یک دستور ساده اما مفید است، به خصوص که تمام لایه های پوشه لازم را برای آن فایل ایجاد می کند. بسیار تمیز – آن را امتحان کنید!
در داخل فایل تست خود، باید چیزی شبیه به این را ببینید:
testWidgets(‘Test Description’, (WidgetTester tester) async {})
testWidgets روشی است که برای اجرای تست های ویجت استفاده می شود و tester ابزاری است که برای یافتن، تعامل و موارد دیگر استفاده خواهد شد.
اکنون، باید ویجت خود را برای آزمایش قرار دهید. رایج ترین رویکرد به شرح زیر است:
await tester.pumpWidget(const MyWidget());
این همچنین مکان خوبی برای تنظیم برخی از تنظیمات اولیه برای مؤلفه خود است، مانند تنظیمات تم یا حتی تزریق وابستگی. بهترین روش این است که جزء خود را در یک بپیچید MaterialApp برای اطمینان از داشتن تمام تم های لازم و MediaQuery تنظیمات
await tester.pumpWidget(MaterialApp(home: const MyWidget()));
برای مکان یابی عناصر روی صفحه، می توانید از CommonFinders singleton استفاده کنید. راههای مختلفی را برای یافتن عنصر مورد نظر ارائه میدهد، و علامت گذاری کاملاً ساده است:
final buttonFinder = find.byType(ElevatedButton)
final textFinder = find.text(‘Hello world!’)
final myWidgetByKeyFinder = find.byKey(Key(‘MyWidget-Key’))
مهم است که توجه داشته باشید که متغیرهای ایجاد شده، مانند buttonFinder و textFinder، عناصر واقعی را ذخیره نمی کنند، بلکه یک “راهی” برای یافتن آنها هستند.
expect(buttonFinder, findsOneWidget);
در تست بالا، من دقیقا به دنبال یک دکمه هستم. اگر هیچ یا بیش از یک مورد پیدا نشد، آزمون ناموفق خواهد بود.
برای تعامل با عناصر، از tester ارائه شده توسط testWidgets روش می توانید اقداماتی مانند ضربه زدن، وارد کردن متن یا کشیدن را انجام دهید. شما همچنین می توانید خود عنصر را انتخاب کنید.
await tester.enterText(find.byType(TextField), ‘hi’);
await tester.pump()
برای اطمینان از اینکه درخت ویجت پس از شبیه سازی تعامل کاربر بازسازی می شود، پمپ() یا pumpAndSettle() را فراخوانی کنید. در اینجا خلاصه ای از نحوه کار آنها آورده شده است.
بیایید تمرین کنیم!
در اینجا جزء مثال ما است:
class PrimaryButton extends StatelessWidget {
PrimaryButton({
required String title,
super.key,
this.onTap,
this.backgroundColor = Colors.blue,
this.isLoading = false,
}) : title = Text(
title,
);
final Widget title;
final VoidCallback? onTap;
final Color backgroundColor;
final bool isLoading;
@override
Widget build(BuildContext context) => ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (isLoading) {
return Colors.grey;
}
return backgroundColor;
},
),
),
onPressed: isLoading ? null : onTap,
child: isLoading ? const CircularProgressIndicator() : title,
);
}
رفتارهایی که بلافاصله به عنوان نیاز به اعتبار برجسته می شوند عبارتند از:
تغییر رنگ پس زمینه؛
وقتی isLoading درست است:
onTap نباید قابل فراخوانی باشد.
یک CircularProgressIndicator باید به جای عنوان ما نمایش داده شود.
backgroundColor باید Colors.grey باشد.
اعتبار سنجی backgroundColor تغییر دهید
بیایید مؤلفه خود را برای آزمایش آماده کنیم:
testWidgets(‘Slould be able to render and interact with PrimaryButton’,
(tester) async {
await tester.pumpWidget(MaterialApp(
home: PrimaryButton(
title: ‘title’,
backgroundColor: Colors.red,
onTap: () {},
),
));
// …
});
حالا بیایید بررسی کنیم که آیا رنگ پس زمینه سفارشی به درستی اعمال شده است یا خیر.
مهم است
دکمه رندر شده روی صفحه این نیست PrimaryButton اما یک ElevatedButton، بنابراین اعتبارسنجی ها و تعاملات باید روی این مؤلفه انجام شود، نه والد آن. به عنوان مثال، onTap روش در PrimaryButton; اگر سعی کنید به آن ضربه بزنید، هیچ اتفاقی نمی افتد. اما با آن کار خواهد کرد ElevatedButton.
همین مفهوم در مورد کلیدها نیز صدق می کند. اگر می خواهید عنصری را با استفاده از یک کلید شناسایی کنید، باید بدانید که کلید را به کدام ویجت اختصاص دهید. در مثال ما، کلید از PrimaryButton با فرزندش یکی نیست ElevatedButton. بنابراین، اگر سعی می کنید تحریک کنید onTap از طریق والدین، کار نخواهد کرد. برای استفاده از این کلیدهای کامپوننت Flutter پیشفرض، باید آنها را گسترش دهید:
class PrimaryButton extends ElevatedButton {}
به این ترتیب، مسائل ذکر شده در بالا رخ نخواهد داد. با این حال، من ترجیح دادم از این کامپوننت همانطور که هست استفاده کنم، زیرا از گسترش ویجتها رایجتر است.
برای تأیید اینکه آیا رنگی که ما ارسال کردیم واقعاً اعمال شده است، انجام می دهیم:
final buttonFinder = find.byType(ElevatedButton);
final buttonWidget = tester.widget<ElevatedButton>(buttonFinder);
expect(
buttonWidget.style?.backgroundColor?.resolve({}),
Colors.red,
);
expect(find.text(‘title’), findsOneWidget);
با ظهور Material3 و پیاده سازی MaterialStateProperty، ما استفاده می کنیم resolve روشی برای به دست آوردن ویژگی جزء برای یک حالت خاص. از آنجایی که هیچ حالتی را پاس نکردیم، از یک خالی استفاده کردم Set.
اولین اعتبارسنجی ما اکنون کامل شده است. بعد، بیایید با آن تعامل داشته باشیم onTap روش
تعامل با onTap روش
یک راه ساده برای تأیید اینکه آیا متد فراخوانی شده است یا خیر، استفاده از کتابخانه ای مانند است mockito یا mocktail.
class OnTapMock extends Mock {
void call();
}
void main() {
late OnTapMock onTapMock;
setUp(() {
onTapMock = OnTapMock();
});
}
ما این ساختگی را به عنوان یک تماس برای onTap روش و می تواند اعتبار سنجی:
verifyNever(() => onTapMock());
حال برای تعامل واقعی:
await tester.tap(buttonFinder);
verify(() => onTapMock());
ساده است، درست است؟
هندلینگ isLoading == true
بیایید با ایجاد یک تست جدید شروع کنیم:
testWidgets(‘Slould be able to validate PrimaryButton behaivor with isLoading equals true’,
(tester) async {
await tester.pumpWidget(MaterialApp(
home: PrimaryButton(
title: ‘title’,
backgroundColor: Colors.red,
onTap: () => onTapMock(),
isLoading: true,
),
));
});
و اعتبارسنجی ها به این صورت خواهد بود:
final buttonFinder = find.byType(ElevatedButton);
final buttonWidget = tester.widget<ElevatedButton>(buttonFinder);
expect(
buttonWidget.style?.backgroundColor?.resolve({}),
Colors.grey,
);
expect(find.text(‘title’), findsNothing);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
verifyNever(() => onTapMock());
await tester.tap(buttonFinder);
verifyNever(() => onTapMock());
به این ترتیب، ما اطمینان حاصل می کنیم که با isLoading == true تکیه گاه:
رنگ پس زمینه به درستی تنظیم شده است.
عنوان ارائه نشده است.
را CircularProgressIndicator نمایش داده می شود؛
ضربه زدن روی دکمه در این حالت، پاسخ تماس را آغاز نمی کند.
اکنون میتوانیم با اطمینان خاطر این مؤلفه را اصلاح کنیم، زیرا میدانیم که رفتار آن ثابت است.
نتیجه گیری
تست ویجت در Flutter یک فرآیند قدرتمند و لذت بخش است که به شما امکان می دهد رفتار اجزای خود را به طور موثر تأیید کنید. گاهی اوقات، این آزمایشها از شما میخواهند که در مورد ساختار اجزای خود تجدید نظر کنید، اما این شبیه آنچه در تستهای واحد اتفاق میافتد، است، جایی که بهبود روشی که روشهای خود را ایجاد میکنیم و مدیریت وابستگیها منجر به کد قویتر و قابل آزمایش میشود.
پیادهسازی این تستها هنگام ایجاد تغییرات در کد، اطمینان بیشتری به همراه میآورد و اطمینان میدهد که رفتار مورد نظر دست نخورده باقی میماند. بنابراین، اگر هنوز تست ویجت را در گردش کار خود ادغام نکرده اید، اکنون زمان مناسبی برای شروع است.
این همه برای امروز است! دفعه بعد می بینمت!
کمی زمینه
تست ویجت نوعی آزمایش است که گاهی دست کم گرفته می شود، اما ارزش قابل توجهی دارد. در این مقاله، تست ویجت را بررسی میکنیم، نکاتی را ارائه میکنیم که به شما کمک میکند آن را در جریان کار روزانه خود بگنجانید، و کاربردهای عملی آن را نشان میدهیم.
قبل از غواصی، ذکر این نکته ضروری است که مستندات رسمی فلاتر یک نقطه شروع عالی است. این نمونههای ساده و کاربردی را ارائه میکند که به شما درک کاملی از نحوه کار کردن چیزها میدهد. در اینجا، خلاصهای از اطلاعات کلیدی را به همراه بینشهای شخصی که زمان زیادی را صرف کار با این نوع آزمایش کرده است، خواهید یافت.
چه مشکلی را حل می کند؟
برای درک نقش و انواع تست های ویجت، این ویدیو را در کانال یوتیوب Flutter تماشا کنید. این سه نوع تست UI را توضیح می دهد:
- تست های طلایی (متمرکز بر پیکسل کامل)؛
- تست های یاب (Comportamento);
- تست های PaitPattern (دستورالعمل های ترسیم)؛
ما روی نوع دوم تمرکز خواهیم کرد، Finder tests
، که رفتار اجزای شما را تایید می کند. همانطور که در اسناد Flutter آمده است: “بسیاری از ویجت ها نه تنها اطلاعات را نمایش می دهند، بلکه به تعامل کاربر نیز پاسخ می دهند. این شامل دکمه هایی است که می توان روی آنها ضربه زد و TextFields برای وارد کردن متن.”
مفاهیم اساسی
ایجاد این نوع تست بسیار شبیه به تست واحد است. با این حال، باید SDK تست ابزارک Flutter را اضافه کنید:
dev_dependencies:
flutter_test:
sdk: flutter
پس از انجام این کار، به روش های لازم برای ساخت و اجرای تست های خود دسترسی خواهید داشت.
یک راه سریع برای ایجاد یک فایل آزمایشی در VSCode با کلیک راست و انتخاب گزینه Go to Test است. این دستور بررسی می کند که آیا یک فایل آزمایشی از قبل وجود دارد یا خیر. اگر نه، پیشنهاد ایجاد یکی را می دهد. این یک دستور ساده اما مفید است، به خصوص که تمام لایه های پوشه لازم را برای آن فایل ایجاد می کند. بسیار تمیز – آن را امتحان کنید!
در داخل فایل تست خود، باید چیزی شبیه به این را ببینید:
testWidgets('Test Description', (WidgetTester tester) async {})
testWidgets
روشی است که برای اجرای تست های ویجت استفاده می شود و tester
ابزاری است که برای یافتن، تعامل و موارد دیگر استفاده خواهد شد.
اکنون، باید ویجت خود را برای آزمایش قرار دهید. رایج ترین رویکرد به شرح زیر است:
await tester.pumpWidget(const MyWidget());
این همچنین مکان خوبی برای تنظیم برخی از تنظیمات اولیه برای مؤلفه خود است، مانند تنظیمات تم یا حتی تزریق وابستگی. بهترین روش این است که جزء خود را در یک بپیچید MaterialApp
برای اطمینان از داشتن تمام تم های لازم و MediaQuery
تنظیمات
await tester.pumpWidget(MaterialApp(home: const MyWidget()));
برای مکان یابی عناصر روی صفحه، می توانید از CommonFinders singleton استفاده کنید. راههای مختلفی را برای یافتن عنصر مورد نظر ارائه میدهد، و علامت گذاری کاملاً ساده است:
final buttonFinder = find.byType(ElevatedButton)
final textFinder = find.text('Hello world!')
final myWidgetByKeyFinder = find.byKey(Key('MyWidget-Key'))
مهم است که توجه داشته باشید که متغیرهای ایجاد شده، مانند buttonFinder و textFinder، عناصر واقعی را ذخیره نمی کنند، بلکه یک “راهی” برای یافتن آنها هستند.
expect(buttonFinder, findsOneWidget);
در تست بالا، من دقیقا به دنبال یک دکمه هستم. اگر هیچ یا بیش از یک مورد پیدا نشد، آزمون ناموفق خواهد بود.
برای تعامل با عناصر، از tester
ارائه شده توسط testWidgets
روش می توانید اقداماتی مانند ضربه زدن، وارد کردن متن یا کشیدن را انجام دهید. شما همچنین می توانید خود عنصر را انتخاب کنید.
await tester.enterText(find.byType(TextField), 'hi');
await tester.pump()
برای اطمینان از اینکه درخت ویجت پس از شبیه سازی تعامل کاربر بازسازی می شود، پمپ() یا pumpAndSettle() را فراخوانی کنید. در اینجا خلاصه ای از نحوه کار آنها آورده شده است.
بیایید تمرین کنیم!
در اینجا جزء مثال ما است:
class PrimaryButton extends StatelessWidget {
PrimaryButton({
required String title,
super.key,
this.onTap,
this.backgroundColor = Colors.blue,
this.isLoading = false,
}) : title = Text(
title,
);
final Widget title;
final VoidCallback? onTap;
final Color backgroundColor;
final bool isLoading;
@override
Widget build(BuildContext context) => ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (isLoading) {
return Colors.grey;
}
return backgroundColor;
},
),
),
onPressed: isLoading ? null : onTap,
child: isLoading ? const CircularProgressIndicator() : title,
);
}
رفتارهایی که بلافاصله به عنوان نیاز به اعتبار برجسته می شوند عبارتند از:
- تغییر رنگ پس زمینه؛
- وقتی isLoading درست است:
- onTap نباید قابل فراخوانی باشد.
- یک CircularProgressIndicator باید به جای عنوان ما نمایش داده شود.
- backgroundColor باید Colors.grey باشد.
اعتبار سنجی backgroundColor
تغییر دهید
بیایید مؤلفه خود را برای آزمایش آماده کنیم:
testWidgets('Slould be able to render and interact with PrimaryButton',
(tester) async {
await tester.pumpWidget(MaterialApp(
home: PrimaryButton(
title: 'title',
backgroundColor: Colors.red,
onTap: () {},
),
));
// ...
});
حالا بیایید بررسی کنیم که آیا رنگ پس زمینه سفارشی به درستی اعمال شده است یا خیر.
مهم است
دکمه رندر شده روی صفحه این نیست PrimaryButton
اما یک ElevatedButton
، بنابراین اعتبارسنجی ها و تعاملات باید روی این مؤلفه انجام شود، نه والد آن. به عنوان مثال، onTap
روش در PrimaryButton
; اگر سعی کنید به آن ضربه بزنید، هیچ اتفاقی نمی افتد. اما با آن کار خواهد کرد ElevatedButton
.
همین مفهوم در مورد کلیدها نیز صدق می کند. اگر می خواهید عنصری را با استفاده از یک کلید شناسایی کنید، باید بدانید که کلید را به کدام ویجت اختصاص دهید. در مثال ما، کلید از PrimaryButton
با فرزندش یکی نیست ElevatedButton
. بنابراین، اگر سعی می کنید تحریک کنید onTap
از طریق والدین، کار نخواهد کرد. برای استفاده از این کلیدهای کامپوننت Flutter پیشفرض، باید آنها را گسترش دهید:
class PrimaryButton extends ElevatedButton {}
به این ترتیب، مسائل ذکر شده در بالا رخ نخواهد داد. با این حال، من ترجیح دادم از این کامپوننت همانطور که هست استفاده کنم، زیرا از گسترش ویجتها رایجتر است.
برای تأیید اینکه آیا رنگی که ما ارسال کردیم واقعاً اعمال شده است، انجام می دهیم:
final buttonFinder = find.byType(ElevatedButton);
final buttonWidget = tester.widget<ElevatedButton>(buttonFinder);
expect(
buttonWidget.style?.backgroundColor?.resolve({}),
Colors.red,
);
expect(find.text('title'), findsOneWidget);
با ظهور Material3 و پیاده سازی MaterialStateProperty
، ما استفاده می کنیم resolve
روشی برای به دست آوردن ویژگی جزء برای یک حالت خاص. از آنجایی که هیچ حالتی را پاس نکردیم، از یک خالی استفاده کردم Set
.
اولین اعتبارسنجی ما اکنون کامل شده است. بعد، بیایید با آن تعامل داشته باشیم onTap
روش
تعامل با onTap
روش
یک راه ساده برای تأیید اینکه آیا متد فراخوانی شده است یا خیر، استفاده از کتابخانه ای مانند است mockito
یا mocktail
.
class OnTapMock extends Mock {
void call();
}
void main() {
late OnTapMock onTapMock;
setUp(() {
onTapMock = OnTapMock();
});
}
ما این ساختگی را به عنوان یک تماس برای onTap
روش و می تواند اعتبار سنجی:
verifyNever(() => onTapMock());
حال برای تعامل واقعی:
await tester.tap(buttonFinder);
verify(() => onTapMock());
ساده است، درست است؟
هندلینگ isLoading == true
بیایید با ایجاد یک تست جدید شروع کنیم:
testWidgets('Slould be able to validate PrimaryButton behaivor with isLoading equals true',
(tester) async {
await tester.pumpWidget(MaterialApp(
home: PrimaryButton(
title: 'title',
backgroundColor: Colors.red,
onTap: () => onTapMock(),
isLoading: true,
),
));
});
و اعتبارسنجی ها به این صورت خواهد بود:
final buttonFinder = find.byType(ElevatedButton);
final buttonWidget = tester.widget<ElevatedButton>(buttonFinder);
expect(
buttonWidget.style?.backgroundColor?.resolve({}),
Colors.grey,
);
expect(find.text('title'), findsNothing);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
verifyNever(() => onTapMock());
await tester.tap(buttonFinder);
verifyNever(() => onTapMock());
به این ترتیب، ما اطمینان حاصل می کنیم که با isLoading == true
تکیه گاه:
- رنگ پس زمینه به درستی تنظیم شده است.
- عنوان ارائه نشده است.
- را
CircularProgressIndicator
نمایش داده می شود؛ - ضربه زدن روی دکمه در این حالت، پاسخ تماس را آغاز نمی کند.
اکنون میتوانیم با اطمینان خاطر این مؤلفه را اصلاح کنیم، زیرا میدانیم که رفتار آن ثابت است.
نتیجه گیری
تست ویجت در Flutter یک فرآیند قدرتمند و لذت بخش است که به شما امکان می دهد رفتار اجزای خود را به طور موثر تأیید کنید. گاهی اوقات، این آزمایشها از شما میخواهند که در مورد ساختار اجزای خود تجدید نظر کنید، اما این شبیه آنچه در تستهای واحد اتفاق میافتد، است، جایی که بهبود روشی که روشهای خود را ایجاد میکنیم و مدیریت وابستگیها منجر به کد قویتر و قابل آزمایش میشود.
پیادهسازی این تستها هنگام ایجاد تغییرات در کد، اطمینان بیشتری به همراه میآورد و اطمینان میدهد که رفتار مورد نظر دست نخورده باقی میماند. بنابراین، اگر هنوز تست ویجت را در گردش کار خود ادغام نکرده اید، اکنون زمان مناسبی برای شروع است.
این همه برای امروز است! دفعه بعد می بینمت!