PropEr dan ct_doctest

Pengujian berbasis properti dengan PropEr dan contoh dokumentasi dengan ct_doctest.

Daftar isi

Topik ini menuntaskan dua bentuk pengujian yang melengkapi EUnit dan Common Test: PropEr yang menguji properti terhadap ratusan data yang dihasilkan acak, dan ct_doctest dari OTP 29 yang mengeksekusi contoh di dalam dokumentasi modul. Keduanya mengubah pertanyaan pengujian dari satu contoh menjadi kaidah umum.

Properti alih-alih contoh#

Pengujian berbasis contoh memilih beberapa masukan dan menuntut hasilnya. Pengujian berbasis properti (property-based testing) menyatakan kaidah yang benar untuk semua masukan, lalu kerangkanya menghasilkan ratusan masukan acak untuk mengujinya. PropEr adalah kerangka properti utama ekosistem Erlang, dipasang sebagai dependensi dan dieksekusi lewat makro ?FORALL:

erlang ujiprop_prop.erl
-module(ujiprop_prop).
-include_lib("proper/include/proper.hrl").
-export([prop_komutatif/0, prop_dibawah_seratus/0]).

prop_komutatif() ->
    ?FORALL({A, B}, {integer(), integer()}, A + B =:= B + A).

prop_dibawah_seratus() ->
    ?FORALL(X, integer(), X < 100).

Properti di atas menyatakan penjumlahan itu komutatif untuk sepasang bilangan bulat apa pun. PropEr membaca tipe yang dinyatakan, menghasilkan seratus pasangan acak secara bawaan, dan menyusutkan (shrinking) masukan yang gagal ke bentuk terkecilnya agar akar masalah terbaca langsung. Hasil eksekusinya:

text Keluaran
....................................................................................................
OK: Passed 100 test(s).

Titik-titik mewakili masukan yang lolos, dan pilihan tipe generator, mulai dari integer() hingga oneof([...]) untuk alternatif bentuk, menjadi bagian dari desain propertinya. Properti yang paling bernilai bukan sifat matematika semata, melainkan invarian sistem nyata, misalnya operasi menghapus lalu menambah kembali tidak mengubah isi, dan setiap pesan yang dikirim selalu menerima balasan.

Menjalankan PropEr#

PropEr dipasang sebagai dependensi profil test pada rebar3, dan propertinya diletakkan di folder test/ proyek. Setelah rebar3 as test compile, eksekusi manualnya dari Erlang adalah sebagai berikut, dan plugin rebar3_proper menyediakan pintu rebar3 proper untuk proyek yang memakainya:

bash
# jalankan properti dari folder proyek
erl -pa _build/test/lib/ujiprop/ebin -pa _build/test/lib/proper/ebin -noshell -eval 'proper:quickcheck(ujiprop_prop:prop_komutatif(), 100), halt().'

Argumen kedua quickcheck/2 menetapkan jumlah kasus, dan kembalian true menandai seluruhnya lolos. Saat properti gagal, PropEr mencetak masukan penyebab yang sudah disusutkan beserta hasilnya, yang membuat perbaikan dimulai dari kasus terkecil yang pasti gagal, bukan dari tumpukan data besar.

Penyusutan: kasus gagal yang menyusut#

Sisi paling berguna PropEr terlihat saat properti gagal. Properti berikut sengaja salah, yaitu menuntut setiap bilangan bulat bernilai di bawah seratus:

erlang Properti yang tidak mungkin
prop_dibawah_seratus() ->
    ?FORALL(X, integer(), X < 100).

Hasil eksekusinya memperlihatkan penyusutan secara utuh:

text Keluaran
.................................................................!
Failed: After 66 test(s).
1207

Shrinking .(1 time(s))
100
HASIL: false

PropEr menemukan pelanggaran pada masukan ke-66, yaitu 1207, lalu menyusutnya menjadi 100, bentuk terkecil yang masih melanggar klaim. Proses penyusutan ini adalah nilai yang membuat properti lebih mudah didebug daripada contoh besar: laporan pengujian selalu berakhir pada kasus terkecil yang pasti gagal, dan tugas pengembang tinggal membaca batas yang terungkap dari sifat datanya.

ct_doctest pada OTP 29#

Contoh dokumentasi sering tidak sinkron dengan kodenya. Ct_doctest, yang ditambahkan pada OTP 29, menutup celah itu dengan mengeksekusi contoh shell di dalam atribut -doc dan membandingkan keluarannya persis:

erlang Modul dengan contoh pada atribut -doc
-module(dok_cek).
-export([dua_kali/1]).

-doc """
Menggandakan sebuah bilangan.

```
1> dok_cek:dua_kali(21).
42
```
""".
dua_kali(N) -> N * 2.

Pemanggilan ct_doctest:module/1 menguraikan blok kode yang berformat sesi shell, menjalankannya, dan membandingkan hasilnya baris demi baris. Ekspektasi yang benar berjalan tanpa keluaran dan mengembalikan ok, sedangkan ekspektasi yang salah tercetak beserta error pencocokannya:

text Keluaran saat ekspektasi salah
A test failed in function dua_kali/1:

1> dok_cek:dua_kali(21).
40

** exception error: no match of right hand side value 42

Aturan formatnya tegas: prompt mulai dari 1> pada setiap blok dan bertambah berurutan, sehingga contoh yang ditulis seperti sesi shell asli langsung menjadi uji. Bentuk ini menutup modul tipe dan pengujian dengan prinsip yang sama sejak awal: segala klaim yang bisa dieksekusi harus dieksekusi.

Generator dan properti pada protokol proyek#

Tipe generator PropEr jauh melampaui bilangan: oneof([...]) memilih salah satu alternatif, frequency([...]) menimbang kemungkinannya, dan ?LET membangun data bertingkat dari data lain, sehingga bentuk pesan sistem nyata dapat dihasilkan utuh. Properti yang paling bernilai pada proyek puncak jalur ini adalah invarian protokolnya, misalnya setiap perintah yang sah menghasilkan tepat satu balasan dari tipe balasan(), dan pengulangan kirim yang sama tidak menggandakan riwayat. Properti semacam ini ditulis terhadap gen_server yang diuji PropEr, dan kegagalannya menyusut ke pasangan perintah terkecil yang melanggar, yang biasanya langsung menunjuk klausul yang keliru.

Ringkasan#

  • Properti menyatakan kaidah umum dan PropEr mengujinya dengan ratusan masukan acak beserta penyusutan saat gagal.
  • ?FORALL menyatakan properti, tipe generator menjadi bagian desainnya, dan quickcheck/2 mengatur jumlah kasus.
  • ct_doctest mengeksekusi contoh sesi shell pada atribut -doc dan membandingkan keluarannya persis, dengan format prompt 1>, 2>, dan seterusnya.
  • Keduanya mengangkat klaim dokumen dan sifat umum menjadi uji yang dieksekusi nyata.

Referensi#