چگونه اپلیکیشن فلاتر را تست کنیم

1. مقدمه

Flutter جعبه ابزار UI گوگل برای ساخت برنامه های زیبا و بومی کامپایل شده برای موبایل، وب و دسکتاپ از یک پایگاه کد واحد است.

در این کد لبه، شما یک برنامه Flutter ساده را ساخته و آزمایش خواهید کرد. این برنامه از بسته ارائه دهنده برای مدیریت وضعیت استفاده می کند.

چیزی که یاد خواهید گرفت

  • نحوه ایجاد تست ویجت با استفاده از چارچوب تست ویجت
  • نحوه ایجاد یک تست یکپارچه سازی برای آزمایش رابط کاربری و عملکرد برنامه با استفاده از کتابخانه integration_test
  • نحوه تست کلاس های داده (ارائه دهندگان) با کمک تست های واحد

چیزی که خواهی ساخت

در این کد لبه، شما با ساختن یک برنامه کاربردی ساده با لیستی از موارد شروع خواهید کرد. ما کد منبع را برای شما فراهم می کنیم تا بتوانید مستقیماً به آزمایش بروید. این برنامه از عملیات زیر پشتیبانی می کند:

  • افزودن موارد به موارد دلخواه
  • مشاهده لیست علاقه مندی ها
  • حذف موارد از لیست علاقه مندی ها

پس از تکمیل برنامه، تست های زیر را می نویسید:

  • واحد تست می کند تا عملیات افزودن و حذف را تأیید کند
  • تست ویجت‌ها برای صفحه اصلی و مورد علاقه
  • تست های رابط کاربری و عملکرد برای کل برنامه با استفاده از تست های یکپارچه سازی

GIF برنامه در حال اجرا در اندروید

دوست دارید از این کد لبه چه چیزی یاد بگیرید؟

من با موضوع جدید هستم و می خواهم یک مرور کلی خوب داشته باشم. من چیزی در مورد این موضوع می دانم، اما می خواهم یک تجدید نظر کنم. من به دنبال یک کد نمونه برای استفاده در پروژه خود هستم. من به دنبال توضیح یک چیز خاص هستم.

2. محیط توسعه Flutter خود را تنظیم کنید

برای تکمیل این آزمایشگاه به دو نرم افزار نیاز دارید - Flutter SDK و یک ویرایشگر .

شما می توانید کدلب را با استفاده از هر یک از این دستگاه ها اجرا کنید:

  • یک دستگاه فیزیکی Android یا iOS که به رایانه شما متصل شده و روی حالت Developer تنظیم شده است.
  • شبیه ساز iOS (نیاز به نصب ابزار Xcode دارد).
  • شبیه ساز اندروید (نیاز به نصب در Android Studio دارد).
  • یک مرورگر (Chrome برای اشکال زدایی لازم است).
  • به عنوان یک برنامه دسکتاپ Windows ، Linux ، یا macOS . شما باید روی پلتفرمی که قصد استقرار در آن را دارید توسعه دهید. بنابراین، اگر می خواهید یک برنامه دسکتاپ ویندوز توسعه دهید، باید در ویندوز توسعه دهید تا به زنجیره ساخت مناسب دسترسی داشته باشید. الزامات خاص سیستم عامل وجود دارد که به طور مفصل در docs.flutter.dev/desktop پوشش داده شده است.

3. شروع به کار

یک برنامه Flutter جدید ایجاد کنید و وابستگی ها را به روز کنید

این آزمایشگاه کد روی آزمایش یک برنامه موبایل Flutter تمرکز دارد. با استفاده از فایل‌های منبعی که کپی و جای‌گذاری می‌کنید، به سرعت برنامه‌ای را ایجاد می‌کنید تا آزمایش شود. سپس بقیه بخش کد روی یادگیری انواع مختلف تست تمرکز می کند.

a3c16fc17be25f6c.png یک برنامه 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.

a3c16fc17be25f6c.png وابستگی های میخانه را در خط فرمان اضافه کنید.

  • provider برای مدیریت آسان ایالت،
  • integration_test برای تست خودرانی کد فلاتر در دستگاه ها و شبیه سازها،
  • 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.

وابستگی های زیر باید به 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

a3c16fc17be25f6c.png پروژه را در ویرایشگر کد مورد نظر خود باز کنید و برنامه را اجرا کنید. یا آن را در خط فرمان به صورت زیر اجرا کنید.

$ flutter run

4. برنامه را بسازید

در مرحله بعد، برنامه را می سازید تا بتوانید آن را آزمایش کنید. این برنامه حاوی فایل های زیر است:

  • lib/models/favorites.dart - کلاس مدل را برای لیست علاقه مندی ها ایجاد می کند
  • lib/screens/favorites.dart - طرح بندی لیست علاقه مندی ها را ایجاد می کند
  • lib/screens/home.dart - لیستی از موارد را ایجاد می کند
  • lib/main.dart - فایل اصلی که برنامه از آنجا شروع می شود

ابتدا مدل Favorites را در lib/models/favorites.dart ایجاد کنید

a3c16fc17be25f6c.png یک دایرکتوری جدید به نام 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();
  }
}

صفحه Favorites را در lib/screens/favorites.dart اضافه کنید

a3c16fc17be25f6c.png یک دایرکتوری جدید به نام 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 اضافه کنید

a3c16fc17be25f6c.png در دایرکتوری 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 را جایگزین کنید

a3c16fc17be25f6c.png محتوای 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,
      ),
    );
  }
}

این برنامه اکنون کامل است، اما آزمایش نشده است.

a3c16fc17be25f6c.png برنامه را اجرا کنید. باید شبیه تصویر زیر باشد:

b74f843e42a28b0f.png

برنامه لیستی از موارد را نشان می دهد. روی نماد قلب در هر ردیف ضربه بزنید تا قلب پر شود و مورد به لیست علاقه مندی ها اضافه شود. دکمه Favorites در AppBar شما را به صفحه دوم حاوی لیست علاقه مندی ها می برد.

اکنون برنامه برای آزمایش آماده است. در مرحله بعد شروع به آزمایش برنامه خواهید کرد.

5. واحد تست ارائه دهنده

شما با واحد آزمایش مدل favorites شروع خواهید کرد. آزمون واحد چیست؟ تست واحد تأیید می‌کند که هر واحد نرم‌افزار، خواه یک تابع، شی یا ویجت باشد، وظیفه مورد نظر خود را به درستی انجام می‌دهد.

تمام فایل های آزمایشی در یک برنامه Flutter، به جز تست های یکپارچه سازی، در فهرست راهنمای test قرار می گیرند.

test/widget_test.dart را حذف کنید

a3c16fc17be25f6c.png قبل از شروع آزمایش، فایل widget_test.dart را حذف کنید. شما فایل های آزمایشی خود را اضافه خواهید کرد.

یک فایل آزمایشی جدید ایجاد کنید

ابتدا، متد add() در مدل Favorites آزمایش می‌کنید تا بررسی کنید که آیتم جدیدی به لیست اضافه می‌شود و اینکه لیست تغییر را منعکس می‌کند. طبق قرارداد، ساختار دایرکتوری در دایرکتوری test شبیه به آن است که در دایرکتوری lib و فایل های Dart هم نامی با _test ضمیمه شده دارند.

a3c16fc17be25f6c.png یک دایرکتوری 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);
    });    
  });
}

چارچوب تست فلاتر به شما امکان می دهد تست های مشابه مرتبط با یکدیگر را در یک گروه پیوند دهید. می‌تواند چندین گروه در یک فایل آزمایشی وجود داشته باشد که برای آزمایش بخش‌های مختلف فایل مربوطه در دایرکتوری lib در نظر گرفته شده است.

متد test() دو پارامتر موقعیتی دارد: description تست و callback که در آن واقعاً تست را می نویسید.

a3c16fc17be25f6c.png حذف یک مورد از لیست را آزمایش کنید. آزمایش زیر را در همان گروه 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);
});

تست را اجرا کنید

a3c16fc17be25f6c.png در خط فرمان، به دایرکتوری ریشه پروژه بروید و دستور زیر را وارد کنید:

$ 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 ، با این حال callback یک WidgetTester به عنوان آرگومان خود می گیرد.

تست‌های ویجت از TestFlutterWidgetsBinding استفاده می‌کنند، کلاسی که همان منابعی را در اختیار ویجت‌های شما قرار می‌دهد که در یک برنامه در حال اجرا دارند، به عنوان مثال اطلاعاتی درباره اندازه صفحه نمایش، توانایی زمان‌بندی انیمیشن‌ها، اما بدون اجرا در داخل برنامه. در عوض، از یک محیط مجازی برای نمونه سازی ویجت استفاده می شود و سپس نتایج را اجرا می کند. در اینجا، pumpWidget فرآیند را با گفتن به فریمورک آغاز می‌کند که یک ویجت خاص را درست مانند یک برنامه نصب و اندازه‌گیری کند.

چارچوب تست ویجت یاب‌ها را برای یافتن ویجت‌ها فراهم می‌کند، برای مثال text() , byType() و byIcon(). این چارچوب همچنین تطبیق دهنده هایی را برای تأیید نتایج ارائه می دهد.

با آزمایش ویجت HomePage شروع کنید.

یک فایل آزمایشی جدید ایجاد کنید

اولین آزمایش بررسی می کند که آیا پیمایش HomePage به درستی کار می کند یا خیر.

a3c16fc17be25f6c.png یک فایل جدید در دایرکتوری 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 که روی صفحه نمایش داده شده است پیدا کند یا خیر.

a3c16fc17be25f6c.png قطعه کد زیر را به 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!                                                    

همچنین می‌توانید تست‌های ویجت را با استفاده از یک دستگاه یا شبیه‌ساز اجرا کنید، که به شما امکان می‌دهد آزمایش را در حال اجرا تماشا کنید. همچنین به شما امکان استفاده از راه اندازی مجدد داغ را می دهد.

a3c16fc17be25f6c.png دستگاه خود را وصل کنید یا شبیه ساز خود را راه اندازی کنید. همچنین می توانید آزمون را به عنوان یک برنامه دسکتاپ اجرا کنید.

a3c16fc17be25f6c.png از خط فرمان، به دایرکتوری ریشه پروژه بروید و دستور زیر را وارد کنید:

$ 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 را فشار دهید تا برنامه دوباره راه‌اندازی شود و همه آزمایش‌ها دوباره اجرا شوند. برنامه را متوقف نکنید.

a3c16fc17be25f6c.png تست های بیشتری را به گروهی که ویجت های صفحه اصلی را آزمایش می کند اضافه کنید. تست زیر را در فایل خود کپی کنید:

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 برمی‌گردد.

a3c16fc17be25f6c.png Shift + R را وارد کنید. این داغ برنامه را دوباره راه اندازی می کند و همه آزمایش ها را دوباره اجرا می کند.

فایل تست کامل: test/home_test.dart .

a3c16fc17be25f6c.png از همین فرآیند برای تست 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 استفاده می شود. این نسخه فلاتر از Selenium WebDriver، Protractor، Espresso، یا Earl Grey است. این بسته از flutter_driver به صورت داخلی برای اجرای تست روی دستگاه استفاده می‌کند.

نوشتن تست‌های یکپارچه‌سازی در فلاتر شبیه به نوشتن تست‌های ویجت است، با این تفاوت که تست‌های یکپارچه‌سازی روی یک دستگاه تلفن همراه، مرورگر یا برنامه دسکتاپ به نام دستگاه هدف اجرا می‌شوند.

تست رو بنویس

a3c16fc17be25f6c.png یک دایرکتوری به نام 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);
      }
    });
  });
}

تست را اجرا کنید

a3c16fc17be25f6c.png دستگاه خود را وصل کنید یا شبیه ساز خود را راه اندازی کنید. همچنین می توانید آزمون را به عنوان یک برنامه دسکتاپ اجرا کنید.

a3c16fc17be25f6c.png در خط فرمان، به دایرکتوری ریشه پروژه بروید و دستور زیر را وارد کنید:

$ 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,
        );
      }
    },
  );
}

تست را اجرا کنید

a3c16fc17be25f6c.png دستگاه خود را وصل کنید یا شبیه ساز خود را راه اندازی کنید.

a3c16fc17be25f6c.png در خط فرمان، به دایرکتوری ریشه پروژه بروید و دستور زیر را وارد کنید:

$ 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.

پس از اتمام موفقیت آمیز تست، دایرکتوری ساخت در ریشه پروژه شامل دو فایل است:

  1. scrolling_summary.timeline_summary.json حاوی خلاصه است. فایل را با هر ویرایشگر متنی باز کنید تا اطلاعات موجود در آن را مرور کنید.
  2. scrolling_summary.timeline.json شامل اطلاعات کامل جدول زمانی است.

برای جزئیات بیشتر در مورد تست ادغام، به آدرس زیر مراجعه کنید:

9. تبریک!

شما نرم افزار Codelab را تکمیل کرده اید و روش های مختلفی را برای آزمایش یک برنامه Flutter یاد گرفته اید.

چیزی که یاد گرفتی

  • نحوه آزمایش ارائه دهندگان با کمک تست های واحد
  • نحوه تست ویجت ها با استفاده از چارچوب تست ویجت
  • نحوه تست UI برنامه با استفاده از تست های یکپارچه سازی
  • نحوه تست عملکرد برنامه با استفاده از تست های یکپارچه سازی

برای کسب اطلاعات بیشتر در مورد تست کردن در فلاتر، به سایت مراجعه کنید