Proyek akhir dinyatakan selesai bukan ketika kodenya selesai ditulis, melainkan ketika perilakunya terbukti dan tampilannya tuntas. Topik ini menyusun rencana pengujian aplikasi catatan beserta satu contoh berkas tes yang utuh, menuntaskan theme dengan dua slot tema, memeriksa kondisi error yang sudah ditangani, lalu menutupnya dengan daftar periksa sebelum rilis. Setelah seluruh butir daftar itu terpenuhi, aplikasi catatan siap dipindahkan ke tahap rilis.
Rencana pengujian aplikasi catatan#
Rencana pengujian disusun dari yang paling cepat ke yang paling mahal, sehingga sebagian besar temuan tertangkap sebelum pengujian manual di emulator dijalankan. Tabel berikut merangkum sasaran setiap jenis pengujian beserta alatnya.
| Jenis pengujian | Sasaran | Alat yang digunakan |
|---|---|---|
| Tes unit | Catatan.fromJson dan toJson bolak-balik | flutter_test |
| Tes unit | CatatanService dengan respons yang disiapkan | flutter_test dan klien tiruan dari package http |
| Tes widget | layar daftar pada keadaan terisi dan kosong | flutter_test dan Provider |
| Pemeriksaan manual | mode gelap, kondisi offline, dan form tidak valid | emulator atau perangkat |
Tes unit model memeriksa simetri fromJson dan toJson, yaitu objek Catatan yang diubah menjadi JSON lalu dibaca kembali harus bernilai sama. Tes unit service memanfaatkan parameter klien pada konstruktor yang sudah disiapkan sejak implementasi, dengan MockClient dari package http sebagai penggantinya, sehingga tidak ada permintaan nyata yang terkirim. Pengujian dijalankan dengan satu perintah berikut.
# tidak-dijalankan
# menjalankan seluruh tes proyek
flutter testTes widget layar daftar#
// tidak-dijalankan
// Tes widget layar daftar aplikasi catatan.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:aplikasi_catatan/models/catatan.dart';
import 'package:aplikasi_catatan/screens/layar_daftar.dart';
import 'package:aplikasi_catatan/services/catatan_service.dart';
import 'package:aplikasi_catatan/services/catatan_store.dart';
import 'package:aplikasi_catatan/services/sesi_store.dart';
CatatanStore _storeTerisi() {
final store = CatatanStore();
store.terima(const [
Catatan(id: '1', judul: 'Catatan kuliah', isi: 'Materi bab satu'),
Catatan(id: '2', judul: 'Rencana belajar', isi: 'Latihan tes widget'),
]);
return store;
}
Widget _bungkus(CatatanStore store) {
return MultiProvider(
providers: [
ChangeNotifierProvider<SesiStore>.value(
value: SesiStore(service: CatatanService(baseUrl: 'https://example.com/api')),
),
ChangeNotifierProvider<CatatanStore>.value(value: store),
],
child: const MaterialApp(home: LayarDaftar()),
);
}
void main() {
setUp(() {
// shared_preferences diganti nilai tiruan agar tidak menyentuh platform.
SharedPreferences.setMockInitialValues({});
});
testWidgets('menampilkan judul catatan yang tersimpan', (tester) async {
await tester.pumpWidget(_bungkus(_storeTerisi()));
await tester.pumpAndSettle();
expect(find.text('Catatan kuliah'), findsOneWidget);
expect(find.text('Rencana belajar'), findsOneWidget);
});
testWidgets('menampilkan pesan saat daftar masih kosong', (tester) async {
await tester.pumpWidget(_bungkus(CatatanStore()));
await tester.pumpAndSettle();
expect(find.text('Belum ada catatan'), findsOneWidget);
});
}Berkas ini menguji dua keadaan layar daftar sekaligus, yaitu terisi dan kosong. Store diisi melalui method terima, sehingga data ujinya pasti dan tidak ada jaringan yang diakses, sedangkan SharedPreferences.setMockInitialValues mengganti penyimpanan platform dengan nilai tiruan agar pemuatan lokal berjalan di dalam lingkungan tes. Setiap kasus uji membungkus layar dengan penyedianya sendiri karena LayarDaftar membaca SesiStore dan CatatanStore saat dibangun. pumpAndSettle menunggu seluruh animasi dan pekerjaan frame selesai sebelum expect dijalankan, sehingga hasil pemeriksaannya stabil.
Penyempurnaan theme terakhir#
Penyempurnaan tampilan pada proyek ini bertumpu pada keputusan yang sudah dibuat sejak layar-layarnya ditulis, yaitu seluruh warna diambil dari peran warna dan tidak ada satu pun warna yang ditulis manual. Dengan kebiasaan itu, penambahan mode gelap cukup dilakukan pada MaterialApp.router di main.dart tanpa menyunting satu layar pun.
// tidak-dijalankan
MaterialApp.router(
title: 'Aplikasi Catatan',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.teal,
brightness: Brightness.dark,
),
),
themeMode: ThemeMode.system,
routerConfig: AplikasiCatatan._router,
)Kedua slot menggunakan seedColor yang sama sehingga identitas warna aplikasi tetap terasa satu keluarga, sedangkan themeMode berisi ThemeMode.system agar aplikasi mengikuti pengaturan perangkat. Susunan ini persis mengikuti pola modul Penyempurnaan UI, dan tahap terakhirnya berupa pemeriksaan manual, yaitu membuka setiap layar dalam mode gelap untuk memastikan tidak ada elemen yang tersembunyi. Satu-satunya sumber risiko yang tersisa adalah warna yang ditulis di luar peran warna, dan pada proyek ini tidak ada warna semacam itu.
Kondisi error yang tertangani#
Keadaan wajar yang dialami aplikasi catatan sudah ditangani di tempat masing-masing sejak implementasi, dan daftar berikut merangkumnya sebagai bukti tuntasnya penanganan error.
| Kondisi | Tempat penanganannya | Tampilan bagi pengguna |
|---|---|---|
| Daftar kosong | pemeriksaan isEmpty pada layar daftar | pesan Belum ada catatan |
| Gagal jaringan saat memuat | blok catch pada CatatanStore.muat | data lokal tetap tampil |
| Gagal jaringan saat mengirim | blok catch pada _simpanDanKirim | catatan tetap tersimpan di perangkat |
| Form tidak valid | validator pada Form layar ubah | pesan di bawah field, Simpan tidak dijalankan |
| Autentikasi gagal | nilai kembali false dari SesiStore.masuk | pesan pada layar masuk |
Kondisi form tidak valid ditangani validator yang berjalan sebelum data apa pun diproses, sebagaimana berikut.
// tidak-dijalankan
validator: (nilai) {
if (nilai == null || nilai.trim().isEmpty) {
return 'Judul tidak boleh kosong';
}
return null;
},Satu keadaan sengaja dibiarkan, yaitu catatan yang gagal terkirim ke API hanya tersimpan aman di penyimpanan lokal tanpa antrean pengiriman ulang. Membangun antrean semacam itu menuntut keputusan tentang urutan dan umur data, dan pilihan yang wajar setelah jalur ini selesai adalah menempatkan antrean tersebut sebagai pekerjaan lanjutan yang pertama.
Daftar periksa sebelum rilis#
Perintah dan pemeriksaan berikut menutup proyek sebelum hasilnya didistribusikan.
# tidak-dijalankan
# memeriksa peringatan analisis statis
flutter analyze
# menjalankan seluruh tes
flutter test
# membangun paket rilis Android
flutter build appbundleflutter analyzeselesai tanpa temuan, sehingga tidak ada peringatan analisis statis yang tertunda.- Seluruh tes lolos pada emulator dan pada perangkat nyata, bukan hanya pada salah satunya.
- Setiap layar diperiksa dalam mode gelap, dan formnya diperiksa dengan masukan kosong maupun panjang.
- Nama aplikasi, ikonnya, dan layar pembuka bawaan proyek sudah diganti dari nilai bawaannya.
- Kunci penandatanganan rilis disiapkan di luar proyek dan tidak disertakan ke dalam repository.
- Tidak ada secret seperti sandi atau kunci API yang tertanam di dalam kode.
Setiap butir pada daftar ini dibahas lebih rinci pada modul Platform dan rilis, mulai dari pengujian, lokalisasi, pembangunan paket rilis, sampai petunjuk keamanannya. Daftar di atas adalah versi ringkasnya yang disusun khusus untuk aplikasi catatan ini, sehingga proyek akhir berakhir tepat pada titik awal modul Platform dan rilis.
Kesalahan umum#
- Tes yang memanggil API nyata. Tes menjadi lambat dan hasilnya berubah-ubah mengikuti keadaan jaringan, sehingga kegagalannya sulit ditafsirkan. Perbaikannya, isi store melalui
terimadan gunakan klien tiruan yang sudah disiapkan pada konstruktor service. - Melewatkan
pumpAndSettlesebelumexpect. Pemeriksaan dijalankan ketika indikator memuat masih tampil, sehingga judul yang dicari memang belum ada di pohon widget. Perbaikannya, tunggu denganpumpAndSettlesetelahpumpWidgetpada layar yang memuat data. - Menunda
flutter analyzesampai akhir proyek. Temuan menumpuk di banyak berkas sekaligus dan berbaur dengan penyuntingan baru. Perbaikannya, jalankan pemeriksaan setiap kali berkas ditambahkan agar temuannya selalu kecil.
Ringkasan#
- Rencana pengujian bergerak dari tes unit model dan service menuju tes widget layar daftar, dengan pemeriksaan manual untuk mode gelap dan kondisi offline.
- Tes widget mengisi store melalui
terima, menggantishared_preferencesdengan nilai tiruan, dan memeriksa keadaan terisi serta kosong layar daftar. - Dua slot tema dengan seedColor yang sama menuntaskan mode gelap tanpa menyunting layar, karena seluruh warna sudah dibaca dari peran warna.
- Lima kondisi error sudah memiliki tempat penanganannya masing-masing, dan daftar periksa sebelum rilis menyambungkan proyek ini ke modul Platform dan rilis.
Referensi#
- Testing Flutter apps — Flutter — Panduan resmi pengujian aplikasi Flutter, dari tes unit hingga tes widget.
- An introduction to widget testing — Flutter Cookbook — Resep resmi menyusun tes widget dengan pumpWidget dan finder.
- Build and release an Android app — Flutter — Panduan resmi membangun dan menandatangani paket rilis Android.