FsCheck

Pengujian berbasis properti, port dari QuickCheck, untuk menemukan kasus yang tak terduga.

Daftar isi

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.

fsharp identitas.fsx
// 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_bolakBalik
text Keluaran
Ok, 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.

fsharp gagal-teratur.fsx
// 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)
text Keluaran
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.

fsharp penyusutan.fsx
// 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)
text Keluaran
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.

AspekUnit test berbasis contohProperti FsCheck
Masukanditulis tangan, pastidihasilkan generator, berubah tiap eksekusi
Yang dinyatakansatu contoh perilakuhubungan umum untuk semua masukan
Keluaran lolosper tessatu baris ringkasan untuk seratus kasus
Kegagalanperbandingan asersicounterexample yang dapat disusutkan
Cocok untukkasus tepi yang diketahuiinvarian 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.

fsharp panjang-sortir.fsx
// 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_panjangTetap
text Keluaran
Ok, 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 Replay menyematkan 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#