Chuyển đến nội dung chính

Unit test for Flutter: Best practice UI test, widget test, logic test

 

1. Install Flutter Unit test: coverage

brew install lcov

Unit test gồm những gì?
  • Unit Test case: là 1 chuỗi code để đảm bảo rằng đoạn code được kiểm thử làm việc như mong đợi. Mỗi function sẽ có nhiều test case, ứng với mỗi trường hợp function chạy.
  • Setup: Đây là hàm được chạy trước khi chạy các test case, thường dùng để chuẩn bị dữ liệu để chạy test.
  • Teardown: Đây là hàm được chạy sau khi các test case chạy xong, thường dùng để xóa dữ liệu, giải phóng bộ nhớ.
  • Mock: là một đối tượng ảo, mô phỏng các tính chất và hành vi giống hệt như đối tượng thực được truyền vào bên trong khối mã đang vận hành nhằm kiểm tra tính đúng đắn của các hoạt động bên trong. Giả sử chương trình của chúng ta được chia làm 2 module: A và B. Module A đã code xong, B thì chưa. Để test module A, ta dùng mock để làm giả module B, không cần phải đợi tới khi module B code xong mới test được.
  • Test Suite : Test suite là một tập các test case và nó cũng có thể bao gồm nhiều test suite khác, test suite chính là tổ hợp các test.

    2.  Logic test:  Dart language

    Code cần viết unit test 

    → Ready add Unit test 
    import 'package:flutter_test/flutter_test.dart';
    import 'package:appflutter/common/utils/string_utils.dart';

    void main() {
      test('shoud append String correct format default', () {
        expect(StringUtils.append(['a', 'b', 'c']), 'abc');
        expect(StringUtils.append(['a', 'b']), 'ab');
        expect(StringUtils.append(['a']), 'a');
      });
      test('shoud append String correct format with character', () {
        expect(StringUtils.append(['a', 'b', 'c'], character: '+'), 'a+b+c');
        expect(StringUtils.append(['a', 'b'], character: '+'), 'a+b');
        expect(StringUtils.append(['a'], character: '+'), 'a');
      });
      test('shoud append String correct format with character', () {
        expect(StringUtils.append(['a', 'b', 'c'], character: ' '), 'a b c');
        expect(StringUtils.append(['a', 'b'], character: ' '), 'a b');
        expect(StringUtils.append(['a'], character: ' '), 'a');
      });
    }

    3. UI test 

    Dùng lệnh để chạy test thư mục 
    flutter test ./lib/presentation/widgets/appbar_widget

    Unit Test  trường hợp sẽ hiện đỏ  


    Unit Test coverage 100% 

    import 'package:flutter/material.dart';
    import 'package:flutter_test/flutter_test.dart';
    import 'package:appflutter/common/test/material_test_widget.dart';
    import 'package:appflutter/presentation/widgets/appbar_widget/appbar_widget.dart';

    void main() {
      testWidgets('should render success Appbar Widget basic',
          (WidgetTester tester) async {
        // Give
        final Widget child =
            wrapWidgetWithBuilderContext(renderWidget: (BuildContext context) {
          return AppbarWidget.normal(
            context,
            title: 'abc',
          );
        });
        // When
        await tester.pumpWidget(child);
        // Then
        expect(find.byType(Text), findsOneWidget);
        expect(find.byType(IconButton), findsOneWidget);
        final btnLeading = find.byKey(leadingButtonKey);
        expect(btnLeading, findsOneWidget);
        await tester.tap(btnLeading);
      });

      testWidgets('should render success Appbar Widget not show button leading',
          (WidgetTester tester) async {
        // Give
        final Widget child =
            wrapWidgetWithBuilderContext(renderWidget: (BuildContext context) {
          return AppbarWidget.normal(
            context,
            title: 'abc',
            isShowLeading: false,
          );
        });
        // When
        await tester.pumpWidget(child);
        // Then
        final btnLeading = find.byKey(leadingButtonKey);
        expect(btnLeading, findsNothing);
        expect(find.byType(SizedBox), findsOneWidget);
      });

      testWidgets('should render success Appbar Widget custom onLeading',
          (WidgetTester tester) async {
        // Give
        bool tap = false;
        final Widget child =
            wrapWidgetWithBuilderContext(renderWidget: (BuildContext context) {
          return AppbarWidget.normal(
            context,
            title: 'abc',
            onLeading: () {
              tap = true;
            },
          );
        });
        // When
        await tester.pumpWidget(child);
        expect(tap, false);
        // Then
        final btnLeading = find.byKey(leadingButtonKey);
        expect(btnLeading, findsOneWidget);
        await tester.tap(btnLeading);
        expect(tap, true);
      });
    }


    Widget Cần test 
    import 'package:flutter/material.dart';
    import 'package:appflutter/common/constants/layout_constants.dart';
    import 'package:appflutter/presentation/theme/theme_color.dart';

    const leadingButtonKey = ValueKey('appbarWidget_leadingButton');
    class AppbarWidget extends AppBar {
      AppbarWidget.normal(
        BuildContext context, {
        Key key,
        @required String title,
        Function onLeading,
        bool isShowLeading = true,
      }) : super(
              key: key,
              title: Text(
                title ?? '',
                style: const TextStyle(
                  fontSize: LayoutConstants.sizeTitleAppbar,
                ),
              ),
              centerTitle: true,
              elevation: 0,
              leading: isShowLeading
                  ? IconButton(
                      key: leadingButtonKey,
                      icon: Icon(
                        Icons.chevron_left,
                        size: LayoutConstants.sizeIconAppBar,
                        color: AppColor.iconLight,
                      ),
                      onPressed: () => onLeading == null
                          ? Navigator.of(context).pop()
                          : onLeading(),
                    )
                  : const SizedBox(),
            );
    }



    Code có sủ dụng hàm để wrap lấy context 
    Widget wrapWidgetWithBuilderContext({Function renderWidget}) {
      return MaterialApp(home: Builder(builder: (context) {
        return Scaffold(body: renderWidget(context));
      }));
    }


    Tham khảo

    Nhận xét

    Bài đăng phổ biến từ blog này

    Thiết kế giao diện với DotNetBar (Phần 1)

    Đây là phiên bản DotNetBar hỗ trợ C# và Visual Basic https://www.dropbox.com/s/wx80jpvgnlrmtux/DotNetBar.rar  , phiên bản này hỗ trợ giao diện Metro cực kỳ “dễ thương” Các bạn load về và cài đặt, khi cài đặt xong sẽ có source code mẫu của tất cả các control. Để sử dụng được các control của DotNetBar các bạn nhớ add item vào controls box. Thiết kế giao diện với DotNetBar, giao diện sẽ rất đẹp. Link các video hướng dẫn chi tiết cách sử dụng và coding: http://www.devcomponents.com/dotnetbar/movies.aspx Hiện tại DotNetBar có rất nhiều công cụ cực mạnh, trong đó có 3 công cụ dưới đây: DotNetBar for Windows Forms Requires with Visual Studio 2003, 2005, 2008, 2010 or 2012.   DotNetBar for WPF Requires with Visual Studio 2010 or 2012 and Windows Presentation Foundation.   DotNetBar for Silverlight Requires with Visual Studio 2010 or 2012 and Silverlight. Dưới đây là một số hình ảnh về các control trong DotnetBar.   Metro User Interface  controls with Metro Tiles, toolba...

    Jetpack Compose: Lists

      Hai cách chúng ta có thể triển khai Lists trong Jetpack Compose. Lưu ý: Tôi đang sử dụng Jetpack Compose phiên bản 0.1.0-dev08 tại thời điểm viết bài này. Sử dụng AdapterList Chúng ta có thể sử dụng AdapterList composable - nó tương đương với RecyclerViews + RecyclerViewAdapters - nhưng với ít mã hơn đáng kể 😍 Phương thức khởi tạo AdapterList nhận dữ liệu, là danh sách các mục bạn muốn hiển thị và chuyển các mục riêng lẻ đến lambda. Ở đó bạn có thể xác định mục danh sách. Hiện tại, nó chỉ giới hạn ở việc cuộn dọc. Điều này có thể thay đổi trong các bản phát hành trong tương lai. Multiple Types Chúng ta có thể dễ dàng phân biệt giữa các loại chế độ xem khác nhau - chỉ cần thêm điều kiện if. Dưới đây là một ví dụ nếu chúng tôi muốn hiển thị một tiêu đề ở đầu danh sách: 2.Using Vertical and Horizontal Scrollers Một cách khác để triển khai danh sách là sử dụng các Scrollers đơn giản - những công cụ này sẽ không sử dụng các chế độ xem tái chế. Vì vậy, không thực sự lý tưởng cho lists...

    5 concepts every Flutter dev should know

      Phụ lục: State management architecture Testing IDE Shortcuts Platform channel Maintaining a project Tôi đã làm việc với Flagship trong một thời gian dài, và đây là những điều mà tôi phát hiện ra là điều cần phải có đối với bất kỳ nhà phát triển Flagship nào, về tổng thể nó sẽ khiến bạn trở thành một nhà phát triển Flagship giỏi trong thời gian dài. 1. State management architecture Đây là một trong những chủ đề quan trọng nhất trong cộng đồng thiết bị rung, nó khá quan trọng nếu bạn muốn duy trì một dự án rung kích thước trung bình hoặc lớn. Nó sẽ giúp tạo một dự án suôn sẻ và thêm các tính năng mới một cách hoàn hảo.  2. Testing Đây là một chủ đề duy nhất mà tôi không hiểu tại sao nó lại quan trọng trước đó trong sự nghiệp của tôi, nhưng khi tôi tiến lên trong sự nghiệp của mình và có kinh nghiệm với nhiều dự án và vấn đề xảy ra trong môi trường sản xuất. Tôi đã nhận ra một cách khó khăn, tại sao điều này lại quan trọng như vậy. Nếu bạn vẫn muốn có thêm lý do để cân nhắc thử...