Cách kiểm thử một ứng dụng Flutter

1. Giới thiệu

Flutter là bộ công cụ giao diện người dùng của Google để xây dựng những ứng dụng đẹp mắt, được biên dịch tự nhiên cho thiết bị di động, web và máy tính chỉ từ một cơ sở mã duy nhất.

Trong lớp học lập trình này, bạn sẽ xây dựng và kiểm thử một ứng dụng Flutter đơn giản. Ứng dụng sẽ sử dụng gói Provider để quản lý trạng thái.

Kiến thức bạn sẽ học được

  • Cách tạo bài kiểm thử tiện ích bằng khung kiểm thử tiện ích
  • Cách tạo bài kiểm thử tích hợp để kiểm thử hiệu suất và giao diện người dùng của ứng dụng bằng cách sử dụng thư viện integration_test
  • Cách kiểm thử các lớp dữ liệu (nhà cung cấp) với sự trợ giúp của kiểm thử đơn vị

Sản phẩm bạn sẽ tạo ra

Trong lớp học lập trình này, bạn sẽ bắt đầu bằng cách xây dựng một ứng dụng đơn giản với danh sách các mục. Chúng tôi sẽ cung cấp mã nguồn cho bạn để bạn có thể tham gia ngay thử nghiệm. Ứng dụng hỗ trợ các thao tác sau:

  • Đang thêm mục vào danh sách yêu thích
  • Xem danh sách mục yêu thích
  • Đang xoá các mục khỏi danh sách yêu thích

Sau khi ứng dụng hoàn tất, bạn sẽ viết các mã kiểm thử sau:

  • Phương thức kiểm thử đơn vị để xác thực thao tác thêm và xoá
  • Kiểm thử tiện ích cho trang chủ và trang yêu thích
  • Thử nghiệm giao diện người dùng và hiệu suất cho toàn bộ ứng dụng bằng cách sử dụng thử nghiệm tích hợp

Ảnh GIF của ứng dụng chạy trên Android

Bạn muốn tìm hiểu gì từ lớp học lập trình này?

Tôi mới biết đến chủ đề này và tôi muốn có thông tin tổng quan đầy đủ. Tôi đã biết đôi chút về chủ đề này, nhưng tôi muốn ôn lại kiến thức. Tôi đang tìm mã mẫu để sử dụng trong dự án của mình. Tôi muốn được giải thích cụ thể hơn.

2. Thiết lập môi trường phát triển Flutter

Bạn cần có 2 phần mềm để hoàn thành phòng thí nghiệm này – Flutter SDKtrình chỉnh sửa.

Bạn có thể chạy lớp học lập trình bằng bất kỳ thiết bị nào sau đây:

  • Một thiết bị Android hoặc iOS thực kết nối với máy tính của bạn và được đặt ở Chế độ nhà phát triển.
  • Trình mô phỏng iOS (yêu cầu cài đặt công cụ Xcode).
  • Trình mô phỏng Android (yêu cầu thiết lập trong Android Studio).
  • Trình duyệt (cần có Chrome để gỡ lỗi).
  • Dưới dạng ứng dụng Windows, Linux hoặc macOS. Bạn phải phát triển trên nền tảng mà bạn dự định triển khai. Vì vậy, nếu muốn phát triển một ứng dụng Windows dành cho máy tính, bạn phải phát triển trên Windows để truy cập vào chuỗi bản dựng phù hợp. Có các yêu cầu cụ thể theo hệ điều hành được đề cập chi tiết trên docs.flutter.dev/desktop.

3. Bắt đầu

Tạo một ứng dụng Flutter mới và cập nhật phần phụ thuộc

Lớp học lập trình này tập trung vào việc kiểm thử ứng dụng di động Flutter. Bạn sẽ nhanh chóng tạo ứng dụng cần kiểm thử bằng cách sử dụng các tệp nguồn mà bạn sao chép và dán. Sau đó, phần còn lại của lớp học lập trình sẽ tập trung vào việc tìm hiểu các loại hình kiểm thử.

a3c16fc17be25f6c.pngTạo một ứng dụng Flutter theo mẫu đơn giản bằng cách làm theo hướng dẫn trong bài viết Bắt đầu sử dụng ứng dụng Flutter đầu tiên hoặc trên dòng lệnh như sau.

$ 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.pngThêm phần phụ thuộc của nhà xuất bản vào dòng lệnh.

  • provider để dễ dàng quản lý trạng thái,
  • integration_test để kiểm thử tự chạy mã Flutter trên các thiết bị và trình mô phỏng,
  • flutter_driver để sử dụng một API nâng cao giúp kiểm thử các ứng dụng Flutter chạy trên trình mô phỏng và thiết bị thực,
  • test cho công cụ kiểm thử chung,
  • go_router để xử lý hoạt động điều hướng trong ứng dụng.
$ 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.

Bạn phải thêm các phần phụ thuộc sau đây vào 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.pngMở dự án trong trình soạn thảo mã mà bạn chọn rồi chạy ứng dụng. Ngoài ra, hãy chạy trên dòng lệnh như sau.

$ flutter run

4. Xây dựng ứng dụng

Tiếp theo, bạn sẽ xây dựng ứng dụng để có thể kiểm thử. Ứng dụng này chứa các tệp sau:

  • lib/models/favorites.dart – tạo lớp mô hình cho danh sách yêu thích
  • lib/screens/favorites.dart – tạo bố cục cho danh sách yêu thích
  • lib/screens/home.dart – tạo một danh sách các mục
  • lib/main.dart – tệp chính nơi ứng dụng khởi động

Trước tiên, hãy tạo mô hình Favorites trong lib/models/favorites.dart

a3c16fc17be25f6c.pngTạo một thư mục mới có tên models trong thư mục lib, sau đó tạo một tệp mới có tên favorites.dart. Trong tệp đó, hãy thêm mã sau:

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();
  }
}

Thêm trang Mục yêu thích trong lib/screens/favorites.dart

a3c16fc17be25f6c.pngTạo một thư mục mới có tên screens trong thư mục lib và trong thư mục đó, hãy tạo một tệp mới có tên favorites.dart. Trong tệp đó, hãy thêm mã sau:

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

Thêm Trang chủ trong lib/screens/home.dart

a3c16fc17be25f6c.pngTrong thư mục lib/screens, hãy tạo một tệp mới khác có tên home.dart. Trong lib/screens/home.dart, hãy thêm mã sau:

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

Thay thế nội dung của lib/main.dart

a3c16fc17be25f6c.pngThay thế nội dung của lib/main.dart bằng mã sau:

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

Ứng dụng hiện đã hoàn thiện, nhưng chưa được kiểm thử.

a3c16fc17be25f6c.pngChạy ứng dụng. Giao diện sẽ giống như ảnh chụp màn hình sau:

b74f843e42a28b0f.png

Ứng dụng hiển thị một danh sách các mục. Nhấn vào biểu tượng hình trái tim trên hàng bất kỳ để điền hình trái tim và thêm mặt hàng vào danh sách yêu thích. Nút Yêu thích trên AppBar sẽ đưa bạn đến màn hình thứ hai chứa danh sách yêu thích.

Ứng dụng hiện đã sẵn sàng để thử nghiệm. Bạn sẽ bắt đầu kiểm thử ứng dụng trong bước tiếp theo.

5. Kiểm thử đơn vị nhà cung cấp

Bạn sẽ bắt đầu bằng cách kiểm thử đơn vị mô hình favorites. Kiểm thử đơn vị là gì? Kiểm thử đơn vị xác minh rằng mọi đơn vị phần mềm riêng lẻ, cho dù đó là một chức năng, đối tượng hay tiện ích, đều thực hiện đúng tác vụ dự định của nó.

Ngoại trừ các bài kiểm thử tích hợp, tất cả tệp kiểm thử trong một ứng dụng Flutter đều được đặt trong thư mục test.

Xoá test/widget_test.dart

a3c16fc17be25f6c.pngTrước khi bạn bắt đầu thử nghiệm, hãy xoá tệp widget_test.dart. Bạn sẽ thêm các tệp kiểm thử của riêng mình.

Tạo tệp thử nghiệm mới

Trước tiên, bạn sẽ kiểm thử phương thức add() trong mô hình Favorites để xác minh một mục mới được thêm vào danh sách và danh sách này phản ánh thay đổi. Theo quy ước, cấu trúc thư mục trong thư mục test giống với cấu trúc trong thư mục lib và các tệp Dart có cùng tên với _test được nối thêm.

a3c16fc17be25f6c.pngTạo thư mục models trong thư mục test. Trong thư mục mới này, hãy tạo một tệp favorites_test.dart có nội dung sau:

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

Khung kiểm thử Flutter cho phép bạn liên kết các chương trình kiểm thử tương tự liên quan với nhau trong một nhóm. Có thể có nhiều nhóm trong một tệp kiểm thử duy nhất nhằm kiểm thử nhiều phần của tệp tương ứng trong thư mục lib.

Phương thức test() nhận 2 tham số vị trí: description của chương trình kiểm thử và callback nơi bạn thực sự viết chương trình kiểm thử.

a3c16fc17be25f6c.pngKiểm thử việc xoá một mục khỏi danh sách. Chèn chương trình kiểm thử sau vào cùng một nhóm 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);
});

Chạy công cụ kiểm tra

a3c16fc17be25f6c.pngTại dòng lệnh, hãy chuyển đến thư mục gốc của dự án và nhập lệnh sau:

$ flutter test test/models/favorites_test.dart 

Nếu mọi thứ đều hoạt động, bạn sẽ thấy một thông báo tương tự như sau:

00:06 +2: All tests passed!                                                    

Tệp thử nghiệm hoàn chỉnh: test/models/favorites_test.dart.

Để biết thêm thông tin về kiểm thử đơn vị, hãy xem Giới thiệu về kiểm thử đơn vị.

6. Kiểm thử tiện ích

Ở bước này, bạn sẽ thêm mã để kiểm thử các tiện ích. Tính năng kiểm thử tiện ích là chỉ dành cho Flutter, ở đó bạn có thể kiểm thử từng tiện ích theo một cách riêng biệt. Bước này sẽ kiểm thử từng màn hình HomePageFavoritesPage.

Tính năng kiểm thử tiện ích sử dụng hàm testWidget() thay vì hàm test(). Giống như hàm test(), hàm testWidget() nhận 2 tham số: description,callback. Tuy nhiên, lệnh gọi lại sẽ lấy WidgetTester làm đối số.

Kiểm thử tiện ích sử dụng TestFlutterWidgetsBinding, một lớp cung cấp tài nguyên cho các tiện ích mà chúng sẽ có trong một ứng dụng đang chạy, ví dụ: thông tin về kích thước màn hình, khả năng lên lịch ảnh động, nhưng không cần chạy bên trong ứng dụng. Thay vào đó, một môi trường ảo sẽ được dùng để tạo thực thể cho tiện ích này, rồi chạy kiểm thử kết quả. Tại đây, pumpWidget sẽ bắt đầu quy trình này bằng cách yêu cầu khung gắn kết và đo lường một tiện ích cụ thể giống như trong ứng dụng.

Khung kiểm thử tiện ích cung cấp trình tìm kiếm để tìm các tiện ích, ví dụ: text(), byType()byIcon().. Khung này cũng cung cấp trình so khớp để xác minh kết quả.

Hãy bắt đầu bằng cách kiểm thử tiện ích HomePage.

Tạo tệp thử nghiệm mới

Lần kiểm thử đầu tiên xác minh xem thao tác cuộn HomePage có hoạt động đúng cách hay không.

a3c16fc17be25f6c.pngTạo một tệp mới trong thư mục test rồi đặt tên cho tệp đó là home_test.dart. Trong tệp mới tạo, hãy thêm mã sau:

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

Hàm createHomeScreen() dùng để tạo một ứng dụng tải tiện ích cần kiểm thử trong MaterialApp, được gói vào một ChangeNotifierProvider. Tiện ích Trang chủ cần cả hai tiện ích này đều xuất hiện phía trên nó trong cây tiện ích để tiện ích có thể kế thừa từ tiện ích và nhận được quyền truy cập vào dữ liệu mà tiện ích cung cấp. Hàm này được truyền dưới dạng tham số vào hàm pumpWidget().

Tiếp theo, hãy kiểm thử xem khung có thể tìm thấy ListView được kết xuất trên màn hình hay không.

a3c16fc17be25f6c.pngThêm đoạn mã sau vào 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);
    });
});

Chạy công cụ kiểm tra

Trước tiên, hãy chạy kiểm thử giống như cách bạn chạy kiểm thử đơn vị, bằng lệnh:

$ flutter test test/home_test.dart 

Chương trình kiểm thử sẽ chạy nhanh và bạn sẽ thấy một thông báo như sau:

00:02 +2: All tests passed!                                                    

Bạn cũng có thể chạy kiểm thử tiện ích bằng một thiết bị hoặc trình mô phỏng để có thể xem quá trình kiểm thử đang diễn ra. Tính năng này cũng cho phép bạn sử dụng tính năng khởi động nóng.

a3c16fc17be25f6c.pngCắm thiết bị vào hoặc khởi động trình mô phỏng. Bạn cũng có thể chạy bài kiểm thử dưới dạng một ứng dụng dành cho máy tính.

a3c16fc17be25f6c.pngTừ dòng lệnh, hãy chuyển đến thư mục gốc của dự án và nhập lệnh sau:

$ flutter run test/home_test.dart 

Có thể bạn sẽ phải chọn thiết bị để chạy kiểm thử. Trong trường hợp đó, hãy làm theo hướng dẫn và chọn một thiết bị:

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"): 

Nếu mọi thứ đều hoạt động, bạn sẽ thấy kết quả tương tự như sau:

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!

Tiếp theo, bạn sẽ thực hiện các thay đổi đối với tệp kiểm thử và nhấn Shift + R để khởi động lại ứng dụng và chạy lại mọi bài kiểm thử. Đừng dừng ứng dụng.

a3c16fc17be25f6c.pngThêm nhiều thử nghiệm hơn vào nhóm thử nghiệm tiện ích Trang chủ. Sao chép mã kiểm thử sau vào tệp:

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

Kiểm thử này xác minh rằng thao tác nhấn vào IconButton sẽ thay đổi từ Icons.favorite_border (một trái tim mở) thành Icons.favorite (một trái tim đã điền) và quay trở lại Icons.favorite_border khi được nhấn lại.

a3c16fc17be25f6c.pngNhập Shift + R. Thao tác nóng này sẽ khởi động lại ứng dụng và chạy lại tất cả các bài kiểm thử.

Tệp thử nghiệm hoàn chỉnh: test/home_test.dart.

a3c16fc17be25f6c.pngSử dụng quy trình tương tự để kiểm thử FavoritesPage bằng mã sau. Làm theo các bước tương tự và chạy quy trình đó.

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

Quy trình kiểm thử này xác minh xem một mục có biến mất khi nhấn nút đóng (xoá) hay không.

Để biết thêm thông tin về kiểm thử tiện ích, hãy truy cập:

7. Kiểm thử giao diện người dùng ứng dụng bằng các bài kiểm thử tích hợp

Kiểm thử tích hợp được dùng để kiểm thử cách các thành phần riêng lẻ của ứng dụng hoạt động cùng nhau trên tổng thể. Thư viện integration_test được dùng để thực hiện kiểm thử tích hợp trong Flutter. Đây là phiên bản Selenium WebDriver, Protractor, Espresso hoặc Earl Gray của Flutter. Gói này sử dụng flutter_driver nội bộ để lái thử nghiệm trên một thiết bị.

Việc viết chương trình kiểm thử tích hợp trong Flutter tương tự như việc viết chương trình kiểm thử tiện ích, ngoại trừ việc các chương trình kiểm thử tích hợp chạy trên một thiết bị di động, trình duyệt hoặc ứng dụng dành cho máy tính, được gọi là thiết bị mục tiêu.

Viết thử nghiệm

a3c16fc17be25f6c.pngTạo thư mục có tên integration_test trong thư mục gốc của dự án, sau đó tạo một tệp mới có tên app_test.dart trong thư mục đó.

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

Chạy công cụ kiểm tra

a3c16fc17be25f6c.pngCắm thiết bị vào hoặc khởi động trình mô phỏng. Bạn cũng có thể chạy bài kiểm thử dưới dạng một ứng dụng dành cho máy tính.

a3c16fc17be25f6c.pngTại dòng lệnh, hãy chuyển đến thư mục gốc của dự án và nhập lệnh sau:

$ flutter test integration_test/app_test.dart

Nếu mọi thứ đều hoạt động, bạn sẽ thấy kết quả tương tự như sau:

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. Kiểm thử hiệu suất của ứng dụng bằng Trình điều khiển Flutter

Viết bài kiểm thử hiệu suất

Tạo một tệp kiểm thử mới có tên perf_test.dart trong thư mục Integration_test với nội dung sau:

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');
    });
  });
}

Hàm ensureInitialized() xác minh xem trình điều khiển kiểm thử tích hợp có được khởi tạo hay không và khởi động lại nếu cần. Bạn nên đặt framePolicy thành fullyLive để kiểm thử mã ảnh động.

Chương trình kiểm thử này sẽ cuộn qua danh sách các mục rất nhanh rồi cuộn lên trên. Hàm traceAction() ghi lại các thao tác và tạo một bản tóm tắt tiến trình.

Nhận kết quả hiệu suất

Để thu được kết quả, hãy tạo thư mục có tên test_driver với tệp có tên perf_driver.dart và thêm đoạn mã sau:

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

Chạy công cụ kiểm tra

a3c16fc17be25f6c.pngCắm thiết bị vào hoặc khởi động trình mô phỏng.

a3c16fc17be25f6c.pngTại dòng lệnh, hãy chuyển đến thư mục gốc của dự án và nhập lệnh sau:

$ flutter drive \
  --driver=test_driver/perf_driver.dart \
  --target=integration_test/perf_test.dart \
  --profile \
  --no-dds

Nếu mọi thứ đều hoạt động, bạn sẽ thấy kết quả tương tự như sau:

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.

Sau khi kiểm thử hoàn tất thành công, thư mục bản dựng ở gốc của dự án sẽ chứa hai tệp:

  1. scrolling_summary.timeline_summary.json chứa phần tóm tắt. Mở tệp bằng bất kỳ trình chỉnh sửa văn bản nào để xem lại thông tin chứa trong tệp đó.
  2. scrolling_summary.timeline.json chứa dữ liệu dòng thời gian đầy đủ.

Để biết thêm thông tin về quy trình kiểm thử quá trình tích hợp, hãy truy cập vào:

9. Xin chúc mừng!

Bạn đã hoàn thành lớp học lập trình và học nhiều cách kiểm thử ứng dụng Flutter.

Kiến thức bạn học được

  • Cách kiểm thử nhà cung cấp với sự trợ giúp của kiểm thử đơn vị
  • Cách kiểm thử tiện ích bằng khung kiểm thử tiện ích
  • Cách kiểm thử giao diện người dùng của ứng dụng bằng các bài kiểm thử tích hợp
  • Cách kiểm thử hiệu suất của ứng dụng bằng các bài kiểm thử tích hợp

Để tìm hiểu thêm về cách thử nghiệm trong Flutter, hãy truy cập vào