FsCheck adalah pustaka pengujian berbasis properti (property-based testing) untuk .NET yang berakar pada QuickCheck dari Haskell. Alih-alih menuliskan contoh masukan satu per satu, pengguna menyatakan properti yang harus benar untuk masukan apa pun, lalu FsCheck membuat ratusan masukan secara otomatis dan mencari yang melanggar. Topik ini membahas cara menyatakan properti, menjalankannya dari skrip fsx, membaca keluaran kegagalan, serta memperbaiki kegagalan yang dapat direproduksi dengan seed terkunci.
Dari tes contoh ke properti#
Tes berbasis contoh mengunci masukannya, misalnya membalik list tertentu dua kali dan membandingkan hasilnya. Properti mengangkat pola itu ke level yang lebih umum: untuk list bilangan bulat apa pun, membalik dua kali harus mengembalikan list awal. FsCheck menyebut fungsi yang diberi awalan prop_ sebagai properti, membuat seratus list lewat generator bawaan, dan melaporkan hasilnya dalam satu baris. Skrip berikut memuat properti pertama beserta eksekusinya.
// properti pertama: membalik list dua kali mengembalikan list awal
#r "nuget: FsCheck, 3.3.0"
open FsCheck
let prop_bolakBalik (xs: int list) =
List.rev (List.rev xs) = xs
Check.Quick prop_bolakBalikOk, passed 100 tests.Direktif #r "nuget: FsCheck, 3.3.0" memuat pustaka dengan versi yang dipatok, dan Check.Quick menjalankan properti dengan konfigurasi bawaan berjumlah seratus kasus. Fungsi properti menerima satu argumen bertipe int list, dan generator bawaan FsCheck menyediakan nilai bertipe tersebut tanpa pendaftaran tambahan. Setiap eksekusi menghasilkan list yang berbeda-beda, tetapi keluaran lolosnya tetap satu baris karena FsCheck hanya melaporkan ringkasan, bukan daftar masukannya.
Membaca kegagalan properti#
Properti yang salah akan dilaporkan dengan kata Falsifiable beserta kasus yang melanggarnya, disebut counterexample. Skrip berikut memuat properti yang sengaja salah, yaitu mengklaim setiap nilai dari generator kurang dari 3, sedangkan nilai konstantanya adalah 5. Seed generator dikunci melalui Replay agar kasus kegagalan selalu sama di setiap eksekusi, sebuah cara yang juga berguna untuk mereproduksi kegagalan yang ditemukan di mesin lain.
// properti yang sengaja salah, dengan seed dikunci agar kegagalan dapat direproduksi
#r "nuget: FsCheck, 3.3.0"
open FsCheck
open FsCheck.Fluent
let prop_semuaKecil =
Prop.ForAll(Arb.From(Gen.Constant(5)), fun n -> n < 3)
// Replay menyematkan seed generator sehingga kasus kegagalan selalu sama
let config = Config.Quick.WithReplay(Some { Rnd = Rnd(42UL, 7UL); Size = None })
Check.One(config, prop_semuaKecil)Falsifiable, after 1 test (0 shrinks) (42,7)
Last step was invoked with size of 2 and seed of (7072182788822321375,15354929984774607589):
Original:
5
with exception:
System.Exception: Expected true, got false.Baris pertama melaporkan bahwa properti terbantahkan setelah satu kasus, tanpa penyusutan (shrink) karena masukannya konstan. Pasangan angka (42,7) adalah seed yang disematkan lewat Replay, dan baris Original menunjukkan kasus yang melanggar, yaitu 5. Fungsi Prop.ForAll dari ruang nama FsCheck.Fluent membentuk properti dari pasangan generator dan fungsi pengujinya, sedangkan Check.One menjalankannya dengan konfigurasi yang diberikan. Dalam pekerjaan nyata, seed pada laporan kegagalan disematkan kembali pada Replay agar tim lain dapat menjalankan ulang kegagalan yang persis sama.
Penyusutan kasus kegagalan#
Counterexample pertama sering kali berukuran besar dan menyulitkan diagnosis, sehingga FsCheck menyusutkannya secara otomatis ke bentuk yang paling sederhana yang masih melanggar properti. Proses penyusutan bekerja pada tipe generik bawaan, misalnya menyusutkan bilangan ke arah nol dan list ke arah list yang lebih pendek. Contoh berikut memuat properti yang salah, yaitu mengklaim setiap list sudah dalam keadaan terurut, dengan seed terkunci agar hasil penyusutannya dapat diperiksa ulang.
// penyusutan: counterexample disusutkan ke bentuk terkecil
#r "nuget: FsCheck, 3.3.0"
open FsCheck
open FsCheck.Fluent
let prop_terurut (xs: int list) =
List.sort xs = xs
// seed dikunci agar kasus kegagalan dan hasil penyusutannya selalu sama
let config = Config.Quick.WithReplay(Some { Rnd = Rnd(42UL, 7UL); Size = None })
Check.One(config, prop_terurut)Falsifiable, after 3 tests (1 shrink) (42,7)
Last step was invoked with size of 4 and seed of (3240998215670010520,1104482430089510541):
Original:
[0; 0; -1]
Shrunk:
[0; -1]
with exception:
System.Exception: Expected true, got false.Baris Original memuat kasus pertama yang melanggar, yaitu [0; 0; -1], sedangkan baris Shrunk memuat versi tersusutnya, yaitu [0; -1]. Kegagalan yang sesungguhnya hanya satu elemen yang menurun di akhir list, dan penyusutan memperlihatkan inti masalah itu tanpa gangguan data lain. Angka (1 shrink) pada baris pertama mencatat berapa kali penyusutan dilakukan, sehingga laporan kegagalan langsung menyediakan kasus terkecil untuk diagnosis dan untuk dipatok sebagai unit test.
Properti dan unit test saling melengkapi#
Properti dan unit test menangani kebutuhan yang berbeda. Unit test mematok contoh konkret, termasuk kasus tepi yang diketahui, sedangkan properti memeriksa hubungan umum yang harus berlaku untuk keluarga masukan. Dua pendekatan itu digunakan bersamaan pada satu pustaka: properti menggali kasus tak terduga, dan unit test mengunci perilaku setelah kasus itu diperbaiki. Tabel berikut merangkum pembagiannya.
| Aspek | Unit test berbasis contoh | Properti FsCheck |
|---|---|---|
| Masukan | ditulis tangan, pasti | dihasilkan generator, berubah tiap eksekusi |
| Yang dinyatakan | satu contoh perilaku | hubungan umum untuk semua masukan |
| Keluaran lolos | per tes | satu baris ringkasan untuk seratus kasus |
| Kegagalan | perbandingan asersi | counterexample yang dapat disusutkan |
| Cocok untuk | kasus tepi yang diketahui | invarian dan sifat matematika |
Properti yang menarik biasanya menyatakan invarian, yaitu sifat yang tidak diubah oleh suatu operasi. Pengurutan tidak mengubah jumlah elemen. Properti semacam itu diujikan pada list apa pun yang dihasilkan generator tanpa perlu menyebut satu list pun secara konkret.
// pengurutan tidak mengubah jumlah elemen, apa pun list yang dihasilkan generator
#r "nuget: FsCheck, 3.3.0"
open FsCheck
let prop_panjangTetap (xs: int list) =
List.length (List.sort xs) = List.length xs
Check.Quick prop_panjangTetapOk, passed 100 tests.Seratus list yang panjang dan isinya berbeda-beda telah diuji dalam satu eksekusi, sementara penulisan unit test untuk perilaku yang sama akan berhenti pada beberapa contoh saja. Ketika suatu saat properti ini terbantahkan oleh masukan yang tidak terduga, laporan FsCheck menyediakan kasusnya, dan kasus itu dipatok sebagai unit test agar tidak pernah terulang tanpa terdeteksi.
Ringkasan#
- FsCheck menggantikan daftar contoh dengan properti, yaitu fungsi boolean yang diuji terhadap seratus masukan hasil generator bawaan.
- Kegagalan dilaporkan dengan counterexample dan seed, sedangkan
Replaymenyematkan seed agar kegagalan yang sama dapat direproduksi kapan pun. - Properti dan unit test digunakan berdampingan: properti menemukan kasus tak terduga, dan unit test mengunci kasus tersebut setelah diperbaiki.
Referensi#
- FsCheck — Documentation — Dokumentasi resmi FsCheck untuk API properti, generator, dan konfigurasi.
- Lists — Microsoft Learn
— Modul List yang digunakan pada properti
List.revdanList.sort. - F# Interactive (dotnet fsi) — Microsoft Learn
— Direktif
#runtuk memuat pustaka NuGet pada skrip fsx.