01 / 22
Flutter Mobile Development Β· Kelas 11 Β· Semester 2
πŸ§ͺUnit
βš™οΈCI/CD
🧩Widget
πŸ”—Integration
🎭Mock
πŸ“ŠCoverage

BAB 11
Automation Testing

Belajar memastikan aplikasi Flutter kamu bekerja dengan benar secara otomatis β€” dari menguji satu fungsi kecil sampai seluruh alur aplikasi, lalu menjalankannya otomatis lewat pipeline CI/CD.

⏱️ Estimasi: 22 slide · 9 topik · 3 latihan
02

Peta Materi

Klik kartu mana pun untuk langsung melompat ke topiknya.

πŸ§ͺ
01
Unit Test
🧩
02
Widget Test
πŸ”—
03
Integration Test
🎭
04
Mock Data
πŸ“Š
05
Test Coverage
πŸ”„
06
Automation Workflow
βš™οΈ
07
CI/CD Introduction
πŸš€
08
Mini Project
✍️
09
Latihan & Tugas
03

Unit Test

Menguji satu "fungsi" terisolasi

βš™οΈβš™οΈβš™οΈ
πŸ”§ Analogi: Mekanik Bengkel

Sebelum mobil jalan, mekanik mengecek tiap komponen satu per satu β€” rem, ban, oli. Kalau ada yang rusak, ketahuan dari awal. Unit test menguji tiap fungsi kecil di kodemu seperti itu: terpisah, cepat, dan teliti.

Unit test memastikan logika berjalan benar tanpa melibatkan UI, jaringan, atau database.

Ciri Unit Test

  • Menguji 1 fungsi / method / class
  • Sangat cepat (milidetik)
  • Tidak butuh layar atau emulator
  • Pakai paket package:test / flutter_test
  • Pola: Arrange β†’ Act β†’ Assert
πŸ“ Folder: test/
04

Unit Test β€” Kode & Demo

Kode yang diuji Β· counter.dart
class Counter {
  int value = 0;
  void increment() => value++;
  void decrement() => value--;
}
Unit test Β· counter_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:myapp/counter.dart';

void main() {
  test('value bertambah saat increment', () {
    // Arrange
    final counter = Counter();
    // Act
    counter.increment();
    // Assert
    expect(counter.value, 1);
  });
}
Output: flutter test
$ flutter test test/counter_test.dart
00:01 +1: value bertambah saat increment
All tests passed!

expect(actual, matcher)

expect membandingkan hasil aktual dengan yang diharapkan. Jika cocok β†’ hijau βœ…. Jika tidak β†’ merah ❌ dan test gagal.

  • equals(1) Β· nilai sama
  • isTrue / isNull
  • throwsException
05

Widget Test

Menguji tampilan & interaksi UI

MaterialApp
Scaffold
TextFloatingActionButton
πŸ“Ί Analogi: QC Pabrik Remote TV

Petugas Quality Control menekan setiap tombol remote untuk memastikan responsnya benar sebelum dikemas. Widget test menekan tombol & mengecek apakah teks di layar berubah seperti seharusnya.

Tools Penting

  • testWidgets(...) β€” bungkus test UI
  • tester.pumpWidget(...) β€” render widget
  • find.text('...') β€” cari elemen
  • tester.tap(...) β€” simulasi tap
  • tester.pump() β€” refresh frame

Widget test = lebih dari unit test, lebih cepat dari integration test.

06

Widget Test β€” Kode & Demo

widget_test.dart
testWidgets('counter naik saat tombol + ditekan',
    (WidgetTester tester) async {
  // 1. Render widget
  await tester.pumpWidget(const MyApp());

  // 2. Pastikan awalnya angka 0
  expect(find.text('0'), findsOneWidget);
  expect(find.text('1'), findsNothing);

  // 3. Tap tombol +
  await tester.tap(find.byIcon(Icons.add));
  await tester.pump();

  // 4. Angka berubah jadi 1
  expect(find.text('0'), findsNothing);
  expect(find.text('1'), findsOneWidget);
});
Simulasi langkah
pumpWidget β†’ render layar
find.text('0') β†’ ditemukan
tester.tap(+) β†’ tombol ditekan
pump() β†’ frame baru digambar
find.text('1') β†’ ditemukan
Widget test passed βœ…

findsOneWidget?

Matcher khusus widget:

  • findsOneWidget β€” tepat 1
  • findsNothing β€” tidak ada
  • findsNWidgets(3) β€” tepat 3
  • findsWidgets β€” 1 atau lebih
07

Integration Test

Menguji seluruh alur aplikasi (end-to-end)

πŸ“±
➜
πŸ”‘
➜
🏠
➜
βœ…
🏭 Analogi: Rantai Produksi Pabrik

Di pabrik, produk berpindah dari satu mesin ke mesin lain dalam satu jalur. Kalau satu mesin macet, seluruh jalur berhenti. Integration test mengecek apakah semua bagian aplikasi bekerja sama dari awal sampai akhir.

Kapan dipakai?

  • Menguji alur lengkap: login β†’ dashboard β†’ logout
  • Berjalan di emulator / device asli
  • Paling lambat tapi paling realistis
  • Paket: integration_test
πŸ“ Folder: integration_test/

Piramida testing: banyak Unit, sedang Widget, sedikit Integration.

08

Integration Test β€” Kode & Demo

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

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('alur login penuh', (tester) async {
    app.main();
    await tester.pumpAndSettle();

    await tester.enterText(find.byKey(Key('email')), 'siswa@smk.id');
    await tester.enterText(find.byKey(Key('pass')), '123456');
    await tester.tap(find.text('Login'));
    await tester.pumpAndSettle();

    expect(find.text('Selamat Datang'), findsOneWidget);
  });
}
Output: flutter test integration_test
$ flutter test integration_test/app_test.dart
Launching on emulator-5554...
enterText email & password βœ“
tap Login βœ“
'Selamat Datang' tampil βœ“
All tests passed!

pumpAndSettle()

Menunggu semua animasi & proses async selesai (misalnya transisi halaman) sebelum melanjutkan pengecekan. Penting untuk alur yang nyata.

09

Mock Data

Data palsu untuk isolasi dependency

🌐
API Asli (lambat)
⇄
🎭
Mock (instan)
🎬 Analogi: Aktor & Properti Film

Di film aksi, aktor pakai pistol mainan, bukan asli β€” lebih aman dan terkontrol. Mock adalah "pemeran pengganti" untuk API/database: kita atur jawabannya sendiri, jadi test cepat dan tidak tergantung internet.

Kenapa pakai Mock?

  • Tidak perlu koneksi internet sungguhan
  • Bisa simulasi error 404 / 500
  • Hasil konsisten setiap kali test
  • Test jadi cepat & stabil

Paket populer: mockito & mocktail.

10

Mock Data β€” Kode & Demo

mocktail Β· auth_service_test.dart
import 'package:mocktail/mocktail.dart';
import 'package:flutter_test/flutter_test.dart';

class MockApiClient extends Mock implements ApiClient {}

void main() {
  test('login berhasil mengembalikan User', () async {
    final api = MockApiClient();
    final service = AuthService(api);

    // Atur jawaban palsu
    when(() => api.login('a@b.id', '123'))
        .thenAnswer((_) async => User(name: 'Sahil'));

    final user = await service.login('a@b.id', '123');

    expect(user.name, 'Sahil');
    verify(() => api.login('a@b.id', '123')).called(1);
  });
}
Simulasi
when(...) β†’ atur respons palsu
service.login() β†’ panggil mock
user.name == 'Sahil' βœ“
api.login dipanggil 1x βœ“

3 Kata Kunci Mock

  • when() β€” atur perilaku
  • thenAnswer / thenReturn β€” hasil palsu
  • verify() β€” pastikan dipanggil
11

Test Coverage

Seberapa banyak kode yang sudah diuji?

Coverage: 82%
πŸ—ΊοΈ Analogi: Peta Wilayah Inspektur

Inspektur menandai area yang sudah ia periksa di peta. Area yang masih gelap = belum dicek. Coverage menunjukkan baris kode mana yang sudah "dikunjungi" oleh test-mu.

Cara mengukur

flutter test --coverage

Hasilnya tersimpan di coverage/lcov.info. Ubah jadi HTML:

genhtml coverage/lcov.info \
  -o coverage/html

⚠️ Coverage 100% bukan berarti bebas bug β€” kualitas test lebih penting dari sekadar angka.

12

Membaca Laporan Coverage

Warna di Laporan

  • Hijau β€” baris ini dieksekusi test
  • Merah β€” baris belum pernah diuji
  • Abu β€” bukan kode eksekusi (komentar)
int tambah(a,b) => a+b;
if (a > 0) {...}
else {...} // belum diuji!

Tips Naikkan Coverage

  • Uji kedua cabang if/else
  • Uji kondisi error & input kosong
  • Fokus pada logika bisnis penting
  • Jangan kejar 100% di kode sepele
🎯 Target Realistis

Banyak tim menargetkan 70–85% coverage pada logika inti. Itu sudah sangat sehat untuk proyek belajar maupun produksi.

13

Automation Workflow

Test berjalan otomatis, tanpa klik manual

πŸ”„ Analogi: Ban Berjalan Otomatis Pabrik

Begitu barang diletakkan, ban berjalan membawanya melewati tiap stasiun secara otomatis. Sama seperti workflow: begitu kamu push kode, test langsung jalan sendiri tanpa perlu diingatkan.

git push Build Run Tests Report Deploy

Trigger β†’ Build β†’ Test β†’ Report β†’ (jika lolos) Deploy. Inilah inti otomasi: manusia menulis kode, mesin yang menguji.

14

Anatomi Sebuah Workflow

Komponen Utama

  • Trigger β€” kapan workflow jalan (push, pull request, jadwal)
  • Job β€” sekumpulan langkah pada satu mesin
  • Step β€” perintah satuan (checkout, install, test)
  • Runner β€” komputer virtual yang menjalankannya

Manfaat untuk Tim

  • Bug ketahuan sebelum masuk ke produk
  • Semua anggota tim dapat hasil yang sama
  • Hemat waktu β€” tidak ada test manual berulang
  • Kualitas kode terjaga otomatis
πŸ€– "Set it once, runs forever"
πŸ’‘ Ingat

Workflow hanyalah resep langkah-langkah yang ditulis dalam file teks (biasanya YAML). Mesin membaca resep itu dan menjalankannya persis urutannya.

15

CI/CD Introduction

Continuous Integration & Continuous Deployment

πŸ“¦
πŸ“¦
πŸ“¦
πŸ›„ Analogi: Konveyor Belt Bandara

Koper diletakkan di ban berjalan, otomatis di-scan keamanan, lalu dikirim ke pesawat. CI/CD = jalur otomatis kode: diintegrasi, diuji, lalu dirilis tanpa proses manual yang ribet.

Dua Bagian

  • CI (Integration) β€” setiap kode baru otomatis di-build & di-test, sering & cepat
  • CD (Deployment) β€” kode yang lolos otomatis dirilis ke pengguna / app store

Tools: GitHub Actions, Codemagic, GitLab CI, Bitrise.

16

Contoh GitHub Actions

.github/workflows/flutter.yml
name: Flutter CI

on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.x'
      - run: flutter pub get
      - run: flutter analyze
      - run: flutter test --coverage
Hasil di GitHub
Push ke branch main...
checkout repo βœ“
setup flutter βœ“
pub get βœ“
flutter analyze βœ“
flutter test βœ“ (24 passed)
βœ” Workflow selesai β€” badge hijau!

Membaca file YAML

  • on: β€” kapan dijalankan
  • jobs: β€” pekerjaan
  • steps: β€” langkah berurutan
  • run: β€” perintah terminal
17

Mini Project β€” Testing Existing App

Tahap 1 Β· Persiapan

Kita akan menambahkan test ke aplikasi To-Do List sederhana yang sudah ada.

1

Tambah dependency

dev_dependencies:
  flutter_test:
    sdk: flutter
  mocktail: ^1.0.0
  integration_test:
    sdk: flutter
2

Siapkan struktur folder

myapp/
 β”œβ”€ lib/
 β”œβ”€ test/              # unit & widget test
 └─ integration_test/  # end-to-end test
3

Identifikasi yang akan diuji

Fungsi addTodo(), removeTodo(), tampilan list, dan alur menambah item baru.

18

Mini Project β€” Tahap 2

Menulis Unit & Widget Test

test/todo_model_test.dart
test('addTodo menambah item ke list', () {
  final list = TodoList();
  list.add('Belajar testing');
  expect(list.items.length, 1);
  expect(list.items.first.title, 'Belajar testing');
});

test('removeTodo menghapus item', () {
  final list = TodoList()..add('A')..add('B');
  list.removeAt(0);
  expect(list.items.length, 1);
  expect(list.items.first.title, 'B');
});
test/todo_widget_test.dart
testWidgets('menambah todo lewat UI', (tester) async {
  await tester.pumpWidget(const TodoApp());

  await tester.enterText(
      find.byType(TextField), 'Push code');
  await tester.tap(find.byIcon(Icons.add));
  await tester.pump();

  expect(find.text('Push code'), findsOneWidget);
});
addTodo βœ“
removeTodo βœ“
UI menambah todo βœ“
19

Mini Project β€” Tahap 3

Jalankan Semua & Cek Coverage

1

Jalankan semua test

flutter test --coverage
2

Jalankan integration test

flutter test integration_test/
3

Hasil Akhir

12 unit + widget tests passed
2 integration tests passed
Coverage: 78% β€” sehat untuk pemula! πŸŽ‰
βœ… Aplikasi To-Do kini punya jaring pengaman test
20

✍️ Latihan 1

Tingkat: Mudah

Unit Test Sederhana

Diberikan sebuah class Kalkulator berikut:

class Kalkulator {
  int tambah(int a, int b) => a + b;
  int kurang(int a, int b) => a - b;
  double bagi(int a, int b) {
    if (b == 0) throw ArgumentError('Tidak bisa bagi 0');
    return a / b;
  }
}
  1. Tulis unit test untuk memastikan tambah(2, 3) menghasilkan 5.
  2. Tulis unit test untuk kurang(10, 4) menghasilkan 6.
  3. Tulis test yang memastikan bagi(5, 0) melempar ArgumentError (gunakan throwsArgumentError).
🎯 Target: 3 fungsi test() dengan pola Arrange–Act–Assert, semua hijau saat flutter test.

Hint: untuk soal 3, gunakan expect(() => kalk.bagi(5,0), throwsArgumentError);

21

🧩 Latihan 2

Tingkat: Menengah

Widget Test dengan Mock

Sebuah halaman ProfilePage mengambil nama user dari UserRepository lalu menampilkannya di sebuah Text.

class ProfilePage extends StatelessWidget {
  final UserRepository repo;
  const ProfilePage(this.repo, {super.key});
  // memanggil repo.getName() lalu menampilkannya
}
  1. Buat MockUserRepository menggunakan mocktail.
  2. Atur when(() => mock.getName()).thenReturn('Sahil').
  3. Render ProfilePage(mock) dengan pumpWidget.
  4. Verifikasi find.text('Sahil') menghasilkan findsOneWidget.
🎯 Target: Widget test lolos tanpa memanggil repository asli β€” bukti mock bekerja.

Hint: bungkus widget dengan MaterialApp(home: ...) agar tidak error layout saat di-render.

22

πŸš€ Tugas Akhir

Tingkat: Proyek

Integration Test pada Project Nyata

Pilih satu aplikasi Flutter yang sudah kamu buat di BAB sebelumnya (mis. app login dari BAB 3 atau app API dari BAB 1).

  1. Tambahkan paket integration_test ke project.
  2. Tulis minimal 1 integration test yang menguji alur penting end-to-end (contoh: buka app β†’ isi form β†’ submit β†’ lihat hasil).
  3. Gunakan pumpAndSettle() untuk menunggu transisi.
  4. Jalankan flutter test --coverage dan catat persentase coverage.
  5. Bonus: buat file .github/workflows/flutter.yml agar test jalan otomatis saat push.
🎯 Kumpulkan: (1) file test, (2) screenshot hasil test hijau, (3) angka coverage, (4) opsional badge CI hijau dari GitHub.

Selamat! Setelah ini kamu sudah menguasai seluruh piramida testing Flutter: Unit β†’ Widget β†’ Integration β†’ Automation. πŸ†