Flutter uygulamasını test etme

1. Giriş

Flutter, Google'ın tek bir kod tabanından mobil, web ve masaüstü için yerel olarak derlenmiş, güzel uygulamalar geliştirmeye yönelik kullanıcı arayüzü araç setidir.

Bu codelab'de basit bir Flutter uygulaması derleyip test edeceksiniz. Uygulama, durumu yönetmek için Provider paketini kullanır.

Neler öğreneceksiniz?

  • Widget test çerçevesini kullanarak widget testleri oluşturma
  • integration_test kitaplığını kullanarak uygulamanın kullanıcı arayüzünü ve performansını test etmek için entegrasyon testi oluşturma
  • Birim testleri yardımıyla veri sınıflarını (sağlayıcılar) test etme

Neler oluşturacaksınız?

Bu codelab'de öğe listesiyle basit bir uygulama oluşturarak başlayacaksınız. Teste hemen başlayabilmeniz için kaynak kodunu sizin yerinize sağlarız. Uygulama aşağıdaki işlemleri destekler:

  • Öğeler favorilere ekleniyor
  • Favoriler listesini görüntüleme
  • Öğeler favoriler listesinden kaldırılıyor

Uygulama tamamlandıktan sonra aşağıdaki testleri yazacaksınız:

  • Ekleme ve kaldırma işlemlerini doğrulamaya yönelik birim testleri
  • Ana sayfa ve favoriler sayfaları için widget testleri
  • Entegrasyon testlerini kullanarak uygulamanın tamamı için kullanıcı arayüzü ve performans testleri

Android'de çalışan uygulamanın GIF'i

Bu codelab'den ne öğrenmek istersiniz?

Konuyla yeni tanıştım ve iyi bir genel bakış istiyorum. Bu konuyla ilgili bilgim var ancak bilgilerinizi tazelemek istiyorum. Projemde kullanmak için örnek bir kod arıyorum. Belirli bir konuyla ilgili açıklama arıyorum.

2. Flutter geliştirme ortamınızı kurma

Bu laboratuvarı tamamlamak için iki yazılıma ihtiyacınız vardır: Flutter SDK'sı ve düzenleyici.

Codelab'i aşağıdaki cihazlardan birini kullanarak çalıştırabilirsiniz:

  • Bilgisayarınıza bağlı ve Geliştirici moduna ayarlanmış fiziksel bir Android veya iOS cihaz.
  • iOS simülatörü (Xcode araçlarının yüklenmesini gerektirir).
  • Android Emülatör (Android Studio'da kurulum gerektirir).
  • Tarayıcı (hata ayıklama için Chrome gereklidir).
  • Windows, Linux veya macOS masaüstü uygulaması olarak Uygulamayı dağıtmayı planladığınız platformda gerçekleştirmeniz gerekir. Bu nedenle, bir Windows masaüstü uygulaması geliştirmek istiyorsanız uygun derleme zincirine erişmek için Windows'da geliştirme yapmanız gerekir. İşletim sistemine özgü gereksinimler docs.flutter.dev/desktop sayfasında ayrıntılı olarak açıklanmıştır.

3. Başlarken

Yeni bir Flutter uygulaması oluşturun ve bağımlılıkları güncelle

Bu codelab'de, Flutter mobil uygulamasını test etme konusu ele alınmaktadır. Kopyaladığınız ve yapıştırdığınız kaynak dosyaları kullanarak test edilecek uygulamayı hızlı bir şekilde oluşturacaksınız. Codelab'in geri kalanında farklı test türlerini öğrenmeye odaklanırsınız.

a3c16fc17be25f6c.pngİlk Flutter uygulamanızı kullanmaya başlama bölümündeki talimatları veya aşağıdaki komut satırını kullanarak basit bir şablonlu Flutter uygulaması oluşturun.

$ 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.pngKomut satırına yayıncı bağımlılıklarını ekleyin.

  • Kolay durum yönetimi için provider,
  • Cihazlar ve emülatörlerde Flutter kodunu kendi kendine test eden integration_test
  • flutter_driver: Gerçek cihazlarda ve emülatörlerde çalışan Flutter uygulamalarını test etmek için geliştirilmiş bir gelişmiş API,
  • Genel test araçları için test,
  • Uygulamada gezinmeyi yönetmek için 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.

Aşağıdaki bağımlılıklar pubspec.yaml dosyanıza eklenmiş olmalıdır:

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.pngProjeyi istediğiniz kod düzenleyicide açın ve uygulamayı çalıştırın. Alternatif olarak, aşağıdaki gibi komut satırında çalıştırabilirsiniz.

$ flutter run

4. Uygulamayı oluşturma

Ardından, test edebilmeniz için uygulamayı geliştirirsiniz. Uygulama aşağıdaki dosyaları içerir:

  • lib/models/favorites.dart - favoriler listesi için model sınıfını oluşturur
  • lib/screens/favorites.dart - sık kullanılanlar listesi düzenini oluşturur
  • lib/screens/home.dart - öğe listesi oluşturur
  • lib/main.dart - uygulamanın başlatılacağı ana dosya

İlk olarak lib/models/favorites.dart ürününde Favorites modelini oluşturun

a3c16fc17be25f6c.pnglib dizininde models adlı yeni bir dizin, ardından favorites.dart adlı yeni bir dosya oluşturun. Bu dosyaya aşağıdaki kodu ekleyin:

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

Favoriler sayfasını lib/screens/favorites.dart listesine ekleyin

a3c16fc17be25f6c.pnglib dizininde screens adlı yeni bir dizin ve bu dizinde favorites.dart adlı yeni bir dosya oluşturun. Bu dosyaya aşağıdaki kodu ekleyin:

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 uygulamasında ana sayfayı ekleyin

a3c16fc17be25f6c.pnglib/screens dizininde home.dart adlı başka bir yeni dosya oluşturun. lib/screens/home.dart alanına aşağıdaki kodu ekleyin:

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 içeriğini değiştir

a3c16fc17be25f6c.pnglib/main.dart içeriğini şu kodla değiştirin:

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

Uygulama tamamlandı ancak henüz test edilmedi.

a3c16fc17be25f6c.pngUygulamayı çalıştırın. Aşağıdaki ekran görüntüsü gibi görünmelidir:

b74f843e42a28b0f.png

Uygulamada bir öğe listesi gösterilir. Herhangi bir satırdaki kalp şeklindeki simgeye dokunarak kalp simgesini doldurun ve öğeyi favoriler listesine ekleyin. AppBar üzerindeki Favoriler düğmesi sizi favoriler listesini içeren ikinci bir ekrana götürür.

Uygulama artık test edilmeye hazır. Bir sonraki adımda uygulamayı test etmeye başlayacaksınız.

5. Sağlayıcı için birim testi

favorites modelini birim test ederek başlayacaksınız. Birim testi nedir? Birim testleri, yazılımın her biriminin (işlev, nesne veya widget) amaçlanan görevi doğru şekilde yerine getirdiğini doğrular.

Entegrasyon testleri hariç Flutter uygulamasındaki tüm test dosyaları test dizinine yerleştirilir.

test/widget_test.dart bilgisini kaldır

a3c16fc17be25f6c.pngTeste başlamadan önce widget_test.dart dosyasını silin. Kendi test dosyalarınızı ekleyeceksiniz.

Yeni test dosyası oluşturma

İlk olarak, listeye yeni bir öğenin eklendiğini ve listenin değişikliği yansıttığını doğrulamak için Favorites modelinde add() yöntemini test edersiniz. Genel olarak, test dizinindeki dizin yapısı, lib dizinindeki dizin yapısını ve Dart dosyalarının adını da _test eklenmiş olarak taklit eder.

a3c16fc17be25f6c.pngtest dizininde bir models dizini oluşturun. Bu yeni dizinde, aşağıdaki içeriğe sahip bir favorites_test.dart dosyası oluşturun:

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

Flutter test çerçevesi, bir grupta birbirleriyle ilişkili benzer testleri bağlamanızı sağlar. Tek bir test dosyasında, lib dizinindeki ilgili dosyanın farklı bölümlerini test etmeyi amaçlayan birden fazla grup olabilir.

test() yöntemi iki konum parametresi alır: testin description ve testi gerçekte yazdığınız callback parametresi.

a3c16fc17be25f6c.pngListeden bir öğeyi kaldırmayı test edin. Aşağıdaki testi aynı Testing App Provider grubuna ekleyin:

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

Testi çalıştırma

a3c16fc17be25f6c.pngKomut satırında projenin kök dizinine gidip aşağıdaki komutu girin:

$ flutter test test/models/favorites_test.dart 

Her şey çalışırsa aşağıdakine benzer bir mesaj görürsünüz:

00:06 +2: All tests passed!                                                    

Test dosyasının tamamı: test/models/favorites_test.dart.

Birim testi hakkında daha fazla bilgi için Birim testine giriş bölümünü ziyaret edin.

6. Widget testi

Bu adımda, widget'ları test etmek için kod ekleyeceksiniz. Widget testi sadece Flutter'a özeldir. Flutter'de her widget'ı ayrı ayrı test edebilirsiniz. Bu adımda HomePage ve FavoritesPage ekranları ayrı ayrı test edilir.

Widget testi, test() işlevi yerine testWidget() işlevini kullanır. test() işlevinde olduğu gibi testWidget() işlevi de iki parametre alır: description, ve callback. Ancak geri çağırma, bağımsız değişkeni olarak bir WidgetTester parametresi alır.

Widget testleri, çalışan bir uygulamadakilerle aynı kaynakları widget'larınıza sağlayan TestFlutterWidgetsBinding sınıfını kullanır (ör. animasyonları programlama yeteneği gibi, ancak uygulama içinde çalıştırmadan da ekran boyutu hakkında bilgi edinebilirsiniz. Bunun yerine, widget'ı örneklendirmek için bir sanal ortam kullanılır ve ardından testler sonuçları çalıştırır. Burada pumpWidget, çerçeveye bir uygulamada olduğu gibi belirli bir widget'ı eklemesini ve ölçmesini söyleyerek süreci başlatır.

Widget test çerçevesi, bulanların text(), byType() ve byIcon(). gibi widget'ları bulmasını sağlar. Çerçeve, sonuçları doğrulamak için eşleştiriciler de sağlar.

HomePage widget'ını test ederek başlayın.

Yeni test dosyası oluşturma

İlk test, HomePage kaydırma işleminin düzgün şekilde çalışıp çalışmadığını doğrular.

a3c16fc17be25f6c.pngtest dizininde yeni bir dosya oluşturun ve home_test.dart olarak adlandırın. Yeni oluşturulan dosyaya aşağıdaki kodu ekleyin:

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() işlevi, bir MaterialApp'te test edilecek widget'ı ChangeNotifierProvider'a sarmalanmış bir şekilde yükleyen bir uygulama oluşturmak için kullanılır. Ana Sayfa widget'ının, widget ağacında bu iki widget'ın üzerinde bulunması gerekir. Böylece, widget bunlardan devralabilir ve sundukları verilere erişebilir. Bu işlev, pumpWidget() işlevine parametre olarak iletilir.

Ardından, çerçevenin ekranda oluşturulmuş bir ListView bulup bulamayacağını test edin.

a3c16fc17be25f6c.pnghome_test.dart alanına şu kod snippet'ini ekleyin:

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

Testi çalıştırma

İlk olarak, birim testi çalıştırdığınız gibi testi şu komutu kullanarak çalıştırın:

$ flutter test test/home_test.dart 

Test hızlı bir şekilde çalıştırılır ve şuna benzer bir mesaj görürsünüz:

00:02 +2: All tests passed!                                                    

Widget testlerini bir cihaz veya emülatör kullanarak da çalıştırabilirsiniz. Bu testler, testin çalışmasını izlemenize olanak tanır. Çalışırken yeniden başlatma özelliğini de kullanabilirsiniz.

a3c16fc17be25f6c.pngCihazınızı takın veya emülatörünüzü başlatın. Testi bir masaüstü uygulaması olarak da çalıştırabilirsiniz.

a3c16fc17be25f6c.pngKomut satırından projenin kök dizinine gidin ve aşağıdaki komutu girin:

$ flutter run test/home_test.dart 

Testi çalıştıracağınız cihazı seçmeniz gerekebilir. Bu durumda, talimatları uygulayın ve bir cihaz seçin:

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

Her şey yolundaysa şuna benzer bir çıkış görürsünüz:

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!

Ardından, test dosyasında değişiklik yapın ve uygulamayı yeniden başlatıp tüm testleri yeniden çalıştırmak için Shift + R tuşuna basın. Uygulamayı durdurmayın.

a3c16fc17be25f6c.pngHomePage widget'larını test eden gruba daha fazla test ekleyin. Aşağıdaki testi dosyanıza kopyalayın:

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

Bu test, IconButton dokunuşunun Icons.favorite_border (açık kalp) yerine Icons.favorite (kalp dolu kalp) olduğunu, ardından tekrar dokunulduğunda tekrar Icons.favorite_border olduğunu doğrular.

a3c16fc17be25f6c.pngShift + R girin. Bu işlem, uygulamayı yeniden başlatır ve tüm testleri tekrar çalıştırır.

Test dosyasının tamamı: test/home_test.dart.

a3c16fc17be25f6c.pngFavoritesPage öğesini aşağıdaki kod ile test etmek için aynı işlemi uygulayın. Aynı adımları uygulayın ve çalıştırın.

test/favorites_test.dart (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);
    });
  });
}

Bu test, kapat (kaldır) düğmesine basıldığında bir öğenin kaybolup kaybolmadığını doğrular.

Widget testi hakkında daha fazla bilgi için şu adresi ziyaret edin:

7. Entegrasyon testleriyle uygulama kullanıcı arayüzünü test etme

Entegrasyon testleri, uygulamanın her bir parçasının bir bütün olarak birlikte nasıl çalıştığını test etmek için kullanılır. integration_test kitaplığı, Flutter'da entegrasyon testleri yapmak için kullanılır. Bu, Flutter'ın Selenium WebDriver, Protractor, Espresso veya Earl Gray sürümüdür. Paket, bir cihazda testi yürütmek için dahili olarak flutter_driver kullanır.

Flutter'da entegrasyon testleri yazmak, widget testleri yazmaya benzer. Tek fark, entegrasyon testlerinin hedef cihaz olarak adlandırılan bir mobil cihaz, tarayıcı veya masaüstü uygulamasında çalıştırılmasıdır.

Testi yazın

a3c16fc17be25f6c.pngProjenin kök dizininde integration_test adlı bir dizin, ardından bu dizinde app_test.dart adlı yeni bir dosya oluşturun.

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

Testi çalıştırma

a3c16fc17be25f6c.pngCihazınızı takın veya emülatörünüzü başlatın. Testi bir masaüstü uygulaması olarak da çalıştırabilirsiniz.

a3c16fc17be25f6c.pngKomut satırında projenin kök dizinine gidip aşağıdaki komutu girin:

$ flutter test integration_test/app_test.dart

Her şey yolundaysa şuna benzer bir çıkış görürsünüz:

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 ile uygulama performansını test etme

Performans testi yazma

integration_test klasöründe aşağıdaki içeriğe sahip perf_test.dart adlı yeni bir test dosyası oluşturun:

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() işlevi, entegrasyon testi sürücüsünün başlatılıp başlatılmadığını doğrular ve gerekirse cihazı yeniden başlatır. framePolicy politikasını fullyLive değerine ayarlamak, animasyonlu kodu test etmek için uygundur.

Bu test, öğe listesinde gerçekten hızlı bir şekilde ilerler ve ardından en fazla yukarı kaydırır. traceAction() işlevi, işlemleri kaydeder ve zaman çizelgesi özeti oluşturur.

Performans sonuçlarını yakalayın

Sonuçları yakalamak için perf_driver.dart adlı dosya ile test_driver adlı bir klasör oluşturun ve aşağıdaki kodu ekleyin:

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

Testi çalıştırma

a3c16fc17be25f6c.pngCihazınızı takın veya emülatörünüzü başlatın.

a3c16fc17be25f6c.pngKomut satırında projenin kök dizinine gidip aşağıdaki komutu girin:

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

Her şey yolundaysa şuna benzer bir çıkış görürsünüz:

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.

Test başarıyla tamamlandıktan sonra, projenin kökündeki derleme dizini iki dosya içerir:

  1. scrolling_summary.timeline_summary.json özeti içeriyor. İçindeki bilgileri incelemek için dosyayı herhangi bir metin düzenleyicide açın.
  2. scrolling_summary.timeline.json, zaman çizelgesi verilerinin tamamını içerir.

Entegrasyon testi hakkında daha fazla bilgi edinmek için şu adresi ziyaret edin:

9. Tebrikler!

Codelab'i tamamladınız ve Flutter uygulamalarını test etmenin farklı yollarını öğrendiniz.

Öğrendikleriniz

  • Birim testleri yardımıyla sağlayıcılar nasıl test edilir?
  • Widget test çerçevesini kullanarak widget'ları test etme
  • Entegrasyon testlerini kullanarak uygulamanın kullanıcı arayüzünü test etme
  • Entegrasyon testlerini kullanarak uygulamanın performansını test etme

Flutter'da test yapma hakkında daha fazla bilgi edinmek için şu adresi ziyaret edin: