Kiểm thử ứng dụng Flutter: unit test, widget test và integration test

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Sửa một chỗ làm hỏng chỗ khác là cơn ác mộng quen thuộc của ứng dụng lớn dần. Kiểm thử tự động giúp bạn tự tin thay đổi mã. Flutter hỗ trợ ba tầng kiểm thử: unit test, widget test và integration test. Bài này giới thiệu từng loại cùng ví dụ.

Tháp kiểm thử

Nên có nhiều bài kiểm thử nhỏ, nhanh ở đáy và ít bài lớn, chậm ở đỉnh:

  • Unit test: kiểm tra hàm hoặc lớp riêng lẻ, chạy rất nhanh.
  • Widget test: kiểm tra một widget hay màn hình trong môi trường giả lập, không cần thiết bị thật.
  • Integration test: chạy ứng dụng gần như thật trên thiết bị hoặc máy ảo để kiểm tra luồng người dùng.

Unit test

Giả sử có một hàm tính tổng giỏ hàng:

// lib/cart.dart
class CartItem {
  final String name;
  final int price;
  final int qty;
  const CartItem(this.name, this.price, this.qty);
}

int cartTotal(List<CartItem> items) =>
    items.fold(0, (sum, i) => sum + i.price * i.qty);
// test/cart_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/cart.dart';

void main() {
  group('cartTotal', () {
    test('giỏ rỗng có tổng bằng 0', () {
      expect(cartTotal([]), 0);
    });

    test('tính đúng tổng nhiều mặt hàng', () {
      final items = [
        const CartItem('A', 10000, 2),
        const CartItem('B', 5000, 1),
      ];
      expect(cartTotal(items), 25000);
    });
  });
}

Chạy bằng lệnh flutter test. Đặt tên bài kiểm thử mô tả hành vi mong đợi để khi lỗi, bạn đọc tên là hiểu vấn đề.

Widget test

Kiểm thử một bộ đếm: bấm nút thì số tăng lên.

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/counter_page.dart';

void main() {
  testWidgets('bấm nút tăng bộ đếm', (WidgetTester tester) async {
    await tester.pumpWidget(const MaterialApp(home: CounterPage()));

    expect(find.text('Đã bấm 0 lần'), findsOneWidget);

    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    expect(find.text('Đã bấm 1 lần'), findsOneWidget);
  });
}

Các hàm quan trọng: find để tìm widget, tap và enterText để mô phỏng thao tác, pump để vẽ lại sau thay đổi, pumpAndSettle để chờ hoạt ảnh xong.

Giả lập phụ thuộc

Kiểm thử không nên gọi mạng thật. Hãy tách phần truy cập dữ liệu sau một giao diện và cung cấp bản giả:

abstract class UserRepository {
  Future<String> fetchName();
}

class FakeUserRepository implements UserRepository {
  @override
  Future<String> fetchName() async => 'Bem';
}

Truyền bản giả vào widget hoặc qua hệ thống tiêm phụ thuộc trong bài kiểm thử. Thư viện như mocktail giúp tạo đối tượng giả nhanh và kiểm tra xem hàm có được gọi đúng không.

Integration test

Gói integration_test cho phép viết kịch bản chạy trên thiết bị:

import 'package:integration_test/integration_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('đăng nhập rồi thấy trang chủ', (tester) async {
    app.main();
    await tester.pumpAndSettle();

    await tester.enterText(find.byKey(const Key('email')), 'a@b.com');
    await tester.enterText(find.byKey(const Key('password')), '123456');
    await tester.tap(find.byKey(const Key('login_button')));
    await tester.pumpAndSettle();

    expect(find.text('Trang chủ'), findsOneWidget);
  });
}

Chạy bằng flutter test integration_test với thiết bị đang kết nối. Loại này chậm, nên chỉ dùng cho các luồng quan trọng như đăng nhập, thanh toán.

Thói quen kiểm thử tốt

  1. Viết code dễ kiểm thử: tách logic nghiệp vụ khỏi widget.
  2. Mỗi bài kiểm thử chỉ kiểm tra một điều và độc lập với bài khác.
  3. Dùng Key cho các phần tử cần tương tác trong kiểm thử.
  4. Thêm bài kiểm thử mỗi khi sửa một lỗi, để lỗi đó không quay lại.
  5. Chạy kiểm thử tự động trong quy trình tích hợp liên tục trước khi gộp mã.

Đừng chạy theo độ phủ

Độ phủ mã cao không có nghĩa là chất lượng cao. Tập trung kiểm thử phần logic quan trọng, dễ lỗi và khó thấy bằng mắt thường, thay vì cố đạt một con số.

Kết luận

Bắt đầu với unit test cho logic, thêm widget test cho các thành phần chính và vài integration test cho luồng cốt lõi. Dần dần bạn sẽ thấy việc thay đổi code bớt đáng sợ hơn nhiều.

Golden test và kiểm thử giao diện

Ngoài ba loại trên, Flutter hỗ trợ golden test: so sánh ảnh chụp của widget với ảnh chuẩn đã lưu để phát hiện thay đổi giao diện ngoài ý muốn. Loại này hữu ích cho các thành phần dùng chung như nút hay thẻ. Hạn chế là ảnh có thể khác nhau chút ít giữa các nền tảng và phiên bản, nên cần cố định môi trường chạy trong quy trình tự động. Hãy dùng có chọn lọc cho những widget quan trọng, đừng áp dụng cho mọi màn hình vì chi phí bảo trì ảnh chuẩn khá cao.

Tổ chức thư mục kiểm thử

Hãy đặt tệp kiểm thử phản chiếu cấu trúc thư mục lib, với hậu tố _test, để ai cũng tìm được bài kiểm thử tương ứng. Tách các hàm tạo dữ liệu mẫu và đối tượng giả vào thư mục dùng chung để tránh lặp lại. Chạy toàn bộ kiểm thử trước mỗi lần gộp mã và đặt tiêu chí không gộp khi kiểm thử đỏ.

Để học cách xây dựng ứng dụng có kiểm thử từ đầu, bạn có thể xem các khóa học tại codebem.com.

author_photo
Nguyễn Đình Cường

0 comment