Temukan pengujian berbasis properti dengan pustaka Hypothesis Python. Melangkah lebih jauh dari pengujian berbasis contoh untuk menemukan kasus ekstrem dan membangun perangkat lunak yang lebih kuat dan andal.
Melampaui Unit Test: Pendalaman Uji Berbasis Properti dengan Hypothesis Python
Di dunia pengembangan perangkat lunak, pengujian adalah fondasi kualitas. Selama beberapa dekade, paradigma dominan adalah pengujian berbasis contoh. Kami dengan cermat membuat input, mendefinisikan output yang diharapkan, dan menulis pernyataan untuk memverifikasi bahwa kode kami berperilaku seperti yang direncanakan. Pendekatan ini, yang ditemukan dalam framework seperti unittest
dan pytest
, sangat kuat dan penting. Tetapi bagaimana jika saya memberi tahu Anda ada pendekatan pelengkap yang dapat mengungkap bug yang bahkan tidak pernah Anda pikirkan untuk dicari?
Selamat datang di dunia pengujian berbasis properti, sebuah paradigma yang mengalihkan fokus dari pengujian contoh-contoh spesifik ke verifikasi properti umum dari kode Anda. Dan dalam ekosistem Python, juara tak terbantahkan dari pendekatan ini adalah pustaka yang disebut Hypothesis.
Panduan komprehensif ini akan membawa Anda dari pemula lengkap menjadi praktisi pengujian berbasis properti dengan Hypothesis yang percaya diri. Kita akan menjelajahi konsep-konsep inti, menyelami contoh-contoh praktis, dan mempelajari cara mengintegrasikan alat canggih ini ke dalam alur kerja pengembangan harian Anda untuk membangun perangkat lunak yang lebih kuat, andal, dan tahan bug.
Apa itu Pengujian Berbasis Properti? Pergeseran Pola Pikir
Untuk memahami Hypothesis, pertama-tama kita perlu memahami gagasan mendasar dari pengujian berbasis properti. Mari kita bandingkan dengan pengujian berbasis contoh tradisional yang kita semua kenal.
Pengujian Berbasis Contoh: Jalan yang Familiar
Bayangkan Anda telah menulis fungsi pengurutan khusus, my_sort()
. Dengan pengujian berbasis contoh, proses berpikir Anda adalah:
- "Mari kita uji dengan daftar sederhana yang terurut." ->
assert my_sort([1, 2, 3]) == [1, 2, 3]
- "Bagaimana dengan daftar terurut terbalik?" ->
assert my_sort([3, 2, 1]) == [1, 2, 3]
- "Bagaimana dengan daftar kosong?" ->
assert my_sort([]) == []
- "Daftar dengan duplikat?" ->
assert my_sort([5, 1, 5, 2]) == [1, 2, 5, 5]
- "Dan daftar dengan angka negatif?" ->
assert my_sort([-1, -5, 0]) == [-5, -1, 0]
Ini efektif, tetapi memiliki keterbatasan mendasar: Anda hanya menguji kasus-kasus yang dapat Anda pikirkan. Pengujian Anda hanya sebaik imajinasi Anda. Anda mungkin melewatkan kasus ekstrem yang melibatkan angka yang sangat besar, ketidakakuratan floating-point, karakter unicode tertentu, atau kombinasi data kompleks yang mengarah pada perilaku tak terduga.
Pengujian Berbasis Properti: Berpikir dalam Invarian
Pengujian berbasis properti membalikkan naskah. Alih-alih memberikan contoh spesifik, Anda mendefinisikan properti, atau invarian, dari fungsi Anda—aturan yang harus berlaku untuk setiap input yang valid. Untuk fungsi my_sort()
kami, properti ini mungkin:
- Output diurutkan: Untuk setiap daftar angka, setiap elemen dalam daftar output kurang dari atau sama dengan elemen yang mengikutinya.
- Output berisi elemen yang sama dengan input: Daftar yang diurutkan hanyalah permutasi dari daftar asli; tidak ada elemen yang ditambahkan atau hilang.
- Fungsi bersifat idempoten: Mengurutkan daftar yang sudah diurutkan seharusnya tidak mengubahnya. Yaitu,
my_sort(my_sort(some_list)) == my_sort(some_list)
.
Dengan pendekatan ini, Anda tidak menulis data pengujian. Anda menulis aturannya. Anda kemudian membiarkan sebuah framework, seperti Hypothesis, menghasilkan ratusan atau ribuan input acak, beragam, dan seringkali licik untuk mencoba membuktikan bahwa properti Anda salah. Jika ia menemukan input yang melanggar properti, ia telah menemukan bug.
Memperkenalkan Hypothesis: Generator Data Uji Otomatis Anda
Hypothesis adalah pustaka pengujian berbasis properti utama untuk Python. Ia mengambil properti yang Anda definisikan dan melakukan kerja keras menghasilkan data pengujian untuk menantangnya. Ini bukan hanya generator data acak; ini adalah alat yang cerdas dan kuat yang dirancang untuk menemukan bug secara efisien.
Fitur Utama Hypothesis
- Pembuatan Kasus Uji Otomatis: Anda mendefinisikan *bentuk* data yang Anda butuhkan (misalnya, "daftar bilangan bulat," "string yang hanya berisi huruf," "datetime di masa depan"), dan Hypothesis menghasilkan berbagai macam contoh yang sesuai dengan bentuk tersebut.
- Penyusutan Cerdas: Ini adalah fitur ajaib. Ketika Hypothesis menemukan kasus uji yang gagal (misalnya, daftar 50 bilangan kompleks yang membuat fungsi pengurutan Anda crash), ia tidak hanya melaporkan daftar besar itu. Ia secara cerdas dan otomatis menyederhanakan input untuk menemukan contoh terkecil yang mungkin yang masih menyebabkan kegagalan. Alih-alih daftar 50 elemen, ia mungkin melaporkan bahwa kegagalan terjadi hanya dengan
[inf, nan]
. Ini membuat debugging sangat cepat dan efisien. - Integrasi yang Mulus: Hypothesis terintegrasi dengan sempurna dengan framework pengujian populer seperti
pytest
danunittest
. Anda dapat menambahkan pengujian berbasis properti di samping pengujian berbasis contoh yang ada tanpa mengubah alur kerja Anda. - Pustaka Strategi yang Kaya: Ia dilengkapi dengan koleksi "strategi" bawaan yang luas untuk menghasilkan segala sesuatu mulai dari bilangan bulat dan string sederhana hingga struktur data bersarang yang kompleks, datetime yang sadar zona waktu, dan bahkan array NumPy.
- Pengujian Stateful: Untuk sistem yang lebih kompleks, Hypothesis dapat menguji urutan tindakan untuk menemukan bug dalam transisi status, sesuatu yang sangat sulit dengan pengujian berbasis contoh.
Memulai: Uji Hypothesis Pertama Anda
Mari kita mulai bekerja. Cara terbaik untuk memahami Hypothesis adalah dengan melihatnya beraksi.
Instalasi
Pertama, Anda perlu menginstal Hypothesis dan runner pengujian pilihan Anda (kami akan menggunakan pytest
). Semudah:
pip install pytest hypothesis
Contoh Sederhana: Fungsi Nilai Absolut
Mari kita pertimbangkan fungsi sederhana yang seharusnya menghitung nilai absolut dari sebuah angka. Implementasi yang sedikit buggy mungkin terlihat seperti ini:
# dalam file bernama `my_math.py` def custom_abs(x): """Implementasi khusus dari fungsi nilai absolut.""" if x < 0: return -x return x
Sekarang, mari kita tulis file pengujian, test_my_math.py
. Pertama, pendekatan pytest
tradisional:
# test_my_math.py (Berbasis Contoh) def test_abs_positive(): assert custom_abs(5) == 5 def test_abs_negative(): assert custom_abs(-5) == 5 def test_abs_zero(): assert custom_abs(0) == 0
Pengujian ini lulus. Fungsi kami terlihat benar berdasarkan contoh-contoh ini. Tetapi sekarang, mari kita tulis pengujian berbasis properti dengan Hypothesis. Apa properti inti dari fungsi nilai absolut? Hasilnya seharusnya tidak pernah negatif.
# test_my_math.py (Berbasis Properti dengan Hypothesis) from hypothesis import given from hypothesis import strategies as st from my_math import custom_abs @given(st.integers()) def test_abs_property_is_non_negative(x): """Properti: Nilai absolut dari setiap bilangan bulat selalu >= 0.""" assert custom_abs(x) >= 0
Mari kita uraikan ini:
from hypothesis import given, strategies as st
: Kami mengimpor komponen yang diperlukan.given
adalah dekorator yang mengubah fungsi pengujian reguler menjadi pengujian berbasis properti.strategies
adalah modul tempat kita menemukan generator data kita.@given(st.integers())
: Ini adalah inti dari pengujian. Dekorator@given
memberi tahu Hypothesis untuk menjalankan fungsi pengujian ini beberapa kali. Untuk setiap kali dijalankan, ia akan menghasilkan nilai menggunakan strategi yang disediakan,st.integers()
, dan meneruskannya sebagai argumenx
ke fungsi pengujian kita.assert custom_abs(x) >= 0
: Ini adalah properti kita. Kami menegaskan bahwa untuk bilangan bulat apa punx
yang dibayangkan Hypothesis, hasil fungsi kita harus lebih besar dari atau sama dengan nol.
Ketika Anda menjalankan ini dengan pytest
, kemungkinan besar akan lulus untuk banyak nilai. Hypothesis akan mencoba 0, -1, 1, angka positif besar, angka negatif besar, dan banyak lagi. Fungsi sederhana kami menangani semua ini dengan benar. Sekarang, mari kita coba strategi yang berbeda untuk melihat apakah kita dapat menemukan kelemahan.
# Mari kita uji dengan angka floating point @given(st.floats()) def test_abs_floats_property(x): assert custom_abs(x) >= 0
Jika Anda menjalankan ini, Hypothesis akan dengan cepat menemukan kasus yang gagal!
Falsifying example: test_abs_floats_property(x=nan) ... assert custom_abs(nan) >= 0 AssertionError: assert nan >= 0
Hypothesis menemukan bahwa fungsi kita, ketika diberikan float('nan')
(Not a Number), mengembalikan nan
. Pernyataan nan >= 0
salah. Kita baru saja menemukan bug halus yang kemungkinan besar tidak akan terpikirkan untuk kita uji secara manual. Kita dapat memperbaiki fungsi kita untuk menangani kasus ini, mungkin dengan menaikkan ValueError
atau mengembalikan nilai tertentu.
Bahkan lebih baik, bagaimana jika bugnya adalah dengan float yang sangat spesifik? Penciut Hypothesis akan mengambil angka yang besar dan kompleks yang gagal dan menguranginya ke versi sesederhana mungkin yang masih memicu bug.
Kekuatan Strategi: Membuat Data Uji Anda
Strategi adalah jantung dari Hypothesis. Mereka adalah resep untuk menghasilkan data. Pustaka ini menyertakan sejumlah besar strategi bawaan, dan Anda dapat menggabungkan dan menyesuaikannya untuk menghasilkan hampir semua struktur data yang dapat Anda bayangkan.
Strategi Bawaan Umum
- Numerik:
st.integers(min_value=0, max_value=1000)
: Menghasilkan bilangan bulat, opsional dalam rentang tertentu.st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False)
: Menghasilkan float, dengan kontrol terperinci atas nilai-nilai khusus.st.fractions()
,st.decimals()
- Teks:
st.text(min_size=1, max_size=50)
: Menghasilkan string unicode dengan panjang tertentu.st.text(alphabet='abcdef0123456789')
: Menghasilkan string dari set karakter tertentu (misalnya, untuk kode hex).st.characters()
: Menghasilkan karakter individual.
- Koleksi:
st.lists(st.integers(), min_size=1)
: Menghasilkan daftar di mana setiap elemen adalah bilangan bulat. Perhatikan bagaimana kita meneruskan strategi lain sebagai argumen! Ini disebut komposisi.st.tuples(st.text(), st.booleans())
: Menghasilkan tupel dengan struktur tetap.st.sets(st.integers())
st.dictionaries(keys=st.text(), values=st.integers())
: Menghasilkan kamus dengan tipe kunci dan nilai yang ditentukan.
- Temporal:
st.dates()
,st.times()
,st.datetimes()
,st.timedeltas()
. Ini dapat dibuat sadar zona waktu.
- Lain-lain:
st.booleans()
: MenghasilkanTrue
atauFalse
.st.just('constant_value')
: Selalu menghasilkan nilai tunggal yang sama. Berguna untuk menyusun strategi kompleks.st.one_of(st.integers(), st.text())
: Menghasilkan nilai dari salah satu strategi yang disediakan.st.none()
: Hanya menghasilkanNone
.
Menggabungkan dan Mentransformasikan Strategi
Kekuatan sebenarnya dari Hypothesis berasal dari kemampuannya untuk membangun strategi kompleks dari strategi yang lebih sederhana.
Menggunakan .map()
Metode .map()
memungkinkan Anda mengambil nilai dari satu strategi dan mengubahnya menjadi sesuatu yang lain. Ini sangat cocok untuk membuat objek dari kelas khusus Anda.
# Kelas data sederhana from dataclasses import dataclass @dataclass class User: user_id: int username: str # Strategi untuk menghasilkan objek User user_strategy = st.builds( User, user_id=st.integers(min_value=1), username=st.text(min_size=3, alphabet='abcdefghijklmnopqrstuvwxyz') ) @given(user=user_strategy) def test_user_creation(user): assert isinstance(user, User) assert user.user_id > 0 assert user.username.isalpha()
Menggunakan .filter()
dan assume()
Terkadang Anda perlu menolak nilai-nilai yang dihasilkan tertentu. Misalnya, Anda mungkin membutuhkan daftar bilangan bulat di mana jumlahnya bukan nol. Anda dapat menggunakan .filter()
:
st.lists(st.integers()).filter(lambda x: sum(x) != 0)
Namun, menggunakan .filter()
bisa jadi tidak efisien. Jika kondisinya sering salah, Hypothesis mungkin menghabiskan banyak waktu untuk mencoba menghasilkan contoh yang valid. Pendekatan yang lebih baik seringkali adalah menggunakan assume()
di dalam fungsi pengujian Anda:
from hypothesis import assume @given(st.lists(st.integers())) def test_something_with_non_zero_sum_list(numbers): assume(sum(numbers) != 0) # ... logika pengujian Anda di sini ...
assume()
memberi tahu Hypothesis: "Jika kondisi ini tidak terpenuhi, buang saja contoh ini dan coba yang baru." Ini adalah cara yang lebih langsung dan seringkali lebih berperforma untuk membatasi data pengujian Anda.
Menggunakan st.composite()
Untuk pembuatan data yang benar-benar kompleks di mana satu nilai yang dihasilkan bergantung pada yang lain, st.composite()
adalah alat yang Anda butuhkan. Ini memungkinkan Anda untuk menulis fungsi yang mengambil fungsi draw
khusus sebagai argumen, yang dapat Anda gunakan untuk menarik nilai dari strategi lain langkah demi langkah.
Contoh klasik adalah menghasilkan daftar dan indeks yang valid ke dalam daftar itu.
@st.composite def list_and_index(draw): # Pertama, gambar daftar yang tidak kosong my_list = draw(st.lists(st.integers(), min_size=1)) # Kemudian, gambar indeks yang dijamin valid untuk daftar itu index = draw(st.integers(min_value=0, max_value=len(my_list) - 1)) return (my_list, index) @given(data=list_and_index()) def test_list_access(data): my_list, index = data # Akses ini dijamin aman karena bagaimana kita membangun strateginya element = my_list[index] assert element is not None # Pernyataan sederhana
Hypothesis dalam Aksi: Skenario Dunia Nyata
Mari kita terapkan konsep-konsep ini ke masalah yang lebih realistis yang dihadapi pengembang perangkat lunak setiap hari.
Skenario 1: Menguji Fungsi Serialisasi Data
Bayangkan sebuah fungsi yang menserialisasikan profil pengguna (kamus) menjadi string yang aman untuk URL dan yang lain yang mendeserialisasikannya. Properti utama adalah bahwa proses tersebut harus dapat dibalik dengan sempurna.
import json import base64 def serialize_profile(data: dict) -> str: """Menserialisasikan kamus ke string base64 yang aman untuk URL.""" json_string = json.dumps(data) return base64.urlsafe_b64encode(json_string.encode('utf-8')).decode('utf-8') def deserialize_profile(encoded_str: str) -> dict: """Mendeserialisasikan string kembali menjadi kamus.""" json_string = base64.urlsafe_b64decode(encoded_str.encode('utf-8')).decode('utf-8') return json.loads(json_string) # Sekarang untuk pengujian # Kita membutuhkan strategi yang menghasilkan kamus yang kompatibel dengan JSON json_dictionaries = st.dictionaries( keys=st.text(), values=st.recursive(st.none() | st.booleans() | st.floats(allow_nan=False) | st.text(), lambda children: st.lists(children) | st.dictionaries(st.text(), children), max_leaves=10) ) @given(profile=json_dictionaries) def test_serialization_roundtrip(profile): """Properti: Mendeserialisasikan profil yang dikodekan harus mengembalikan profil asli.""" encoded = serialize_profile(profile) decoded = deserialize_profile(encoded) assert profile == decoded
Pengujian tunggal ini akan menghantam fungsi-fungsi kita dengan berbagai macam data: kamus kosong, kamus dengan daftar bersarang, kamus dengan karakter unicode, kamus dengan kunci aneh, dan banyak lagi. Ini jauh lebih teliti daripada menulis beberapa contoh manual.
Skenario 2: Menguji Algoritma Pengurutan
Mari kita tinjau kembali contoh pengurutan kita. Berikut adalah cara Anda akan menguji properti yang kita definisikan sebelumnya.
from collections import Counter def my_buggy_sort(numbers): # Mari kita perkenalkan bug halus: ia menghilangkan duplikat return sorted(list(set(numbers))) @given(st.lists(st.integers())) def test_sorting_properties(numbers): sorted_list = my_buggy_sort(numbers) # Properti 1: Output diurutkan for i in range(len(sorted_list) - 1): assert sorted_list[i] <= sorted_list[i+1] # Properti 2: Elemennya sama (ini akan menemukan bug) assert Counter(numbers) == Counter(sorted_list) # Properti 3: Fungsi bersifat idempoten assert my_buggy_sort(sorted_list) == sorted_list
Ketika Anda menjalankan pengujian ini, Hypothesis akan dengan cepat menemukan contoh yang gagal untuk Properti 2, seperti numbers=[0, 0]
. Fungsi kita mengembalikan [0]
, dan Counter([0, 0])
tidak sama dengan Counter([0])
. Penciut akan memastikan contoh yang gagal sesederhana mungkin, membuat penyebab bug segera jelas.
Skenario 3: Pengujian Stateful
Untuk objek dengan status internal yang berubah seiring waktu (seperti koneksi database, keranjang belanja, atau cache), menemukan bug bisa sangat sulit. Urutan operasi tertentu mungkin diperlukan untuk memicu kesalahan. Hypothesis menyediakan `RuleBasedStateMachine` untuk tujuan ini.
Bayangkan API sederhana untuk penyimpanan nilai-kunci dalam memori:
class SimpleKeyValueStore: def __init__(self): self._data = {} def set(self, key, value): self._data[key] = value def get(self, key): return self._data.get(key) def delete(self, key): if key in self._data: del self._data[key] def size(self): return len(self._data)
Kita dapat memodelkan perilakunya dan mengujinya dengan mesin status:
from hypothesis.stateful import RuleBasedStateMachine, rule, Bundle class KeyValueStoreMachine(RuleBasedStateMachine): def __init__(self): super().__init__() self.model = {} self.sut = SimpleKeyValueStore() # Bundle() digunakan untuk meneruskan data antar aturan keys = Bundle('keys') @rule(target=keys, key=st.text(), value=st.integers()) def set_key(self, key, value): self.model[key] = value self.sut.set(key, value) return key @rule(key=keys) def delete_key(self, key): del self.model[key] self.sut.delete(key) @rule(key=st.text()) def get_key(self, key): model_val = self.model.get(key) sut_val = self.sut.get(key) assert model_val == sut_val @rule() def check_size(self): assert len(self.model) == self.sut.size() # Untuk menjalankan pengujian, Anda cukup membuat subclass dari mesin dan unittest.TestCase # Di pytest, Anda cukup menetapkan pengujian ke kelas mesin TestKeyValueStore = KeyValueStoreMachine.TestCaseHypothesis sekarang akan menjalankan urutan acak operasi `set_key`, `delete_key`, `get_key`, dan `check_size`, tanpa henti mencoba menemukan urutan yang menyebabkan salah satu pernyataan gagal. Ia akan memeriksa apakah mendapatkan kunci yang dihapus berperilaku dengan benar, jika ukurannya konsisten setelah beberapa set dan penghapusan, dan banyak skenario lain yang mungkin tidak terpikirkan untuk Anda uji secara manual.
Praktik Terbaik dan Tips Tingkat Lanjut
- Basis Data Contoh: Hypothesis itu pintar. Ketika ia menemukan bug, ia menyimpan contoh yang gagal di direktori lokal (
.hypothesis/
). Saat berikutnya Anda menjalankan pengujian Anda, ia akan memutar ulang contoh yang gagal itu terlebih dahulu, memberi Anda umpan balik langsung bahwa bug masih ada. Setelah Anda memperbaikinya, contoh tersebut tidak lagi diputar ulang. - Mengontrol Eksekusi Pengujian dengan
@settings
: Anda dapat mengontrol banyak aspek dari eksekusi pengujian menggunakan dekorator@settings
. Anda dapat meningkatkan jumlah contoh, menetapkan batas waktu berapa lama satu contoh dapat berjalan (untuk menangkap loop tak terbatas), dan mematikan pemeriksaan kesehatan tertentu.@settings(max_examples=500, deadline=1000) # Jalankan 500 contoh, batas waktu 1 detik @given(...) ...
- Mereproduksi Kegagalan: Setiap eksekusi Hypothesis mencetak nilai seed (misalnya,
@reproduce_failure('version', 'seed')
). Jika server CI menemukan bug yang tidak dapat Anda reproduksi secara lokal, Anda dapat menggunakan dekorator ini dengan seed yang disediakan untuk memaksa Hypothesis menjalankan urutan contoh yang persis sama. - Berintegrasi dengan CI/CD: Hypothesis sangat cocok untuk pipeline integrasi berkelanjutan apa pun. Kemampuannya untuk menemukan bug yang tidak jelas sebelum mencapai produksi menjadikannya jaring pengaman yang tak ternilai harganya.
Pergeseran Pola Pikir: Berpikir dalam Properti
Mengadopsi Hypothesis lebih dari sekadar mempelajari pustaka baru; ini tentang merangkul cara berpikir baru tentang kebenaran kode Anda. Alih-alih bertanya, "Input apa yang harus saya uji?", Anda mulai bertanya, "Apa kebenaran universal tentang kode ini?"Berikut adalah beberapa pertanyaan untuk memandu Anda saat mencoba mengidentifikasi properti:
- Apakah ada operasi terbalik? (misalnya, serialisasi/deserialisasi, enkripsi/dekripsi, kompresi/dekompresi). Propertinya adalah bahwa melakukan operasi dan kebalikannya harus menghasilkan input asli.
- Apakah operasi bersifat idempoten? (misalnya,
abs(abs(x)) == abs(x)
). Menerapkan fungsi lebih dari sekali harus menghasilkan hasil yang sama dengan menerapkannya sekali. - Apakah ada cara yang berbeda dan lebih sederhana untuk menghitung hasil yang sama? Anda dapat menguji bahwa fungsi Anda yang kompleks dan dioptimalkan menghasilkan output yang sama dengan versi sederhana yang jelas benar (misalnya, menguji pengurutan mewah Anda terhadap
sorted()
bawaan Python). - Apa yang seharusnya selalu benar tentang output? (misalnya, output dari fungsi `find_prime_factors` hanya boleh berisi bilangan prima, dan produknya harus sama dengan input).
- Bagaimana keadaan berubah? (Untuk pengujian stateful) Invarian apa yang harus dipertahankan setelah operasi yang valid? (misalnya, Jumlah item dalam keranjang belanja tidak boleh negatif).
Kesimpulan: Tingkat Kepercayaan Baru
Pengujian berbasis properti dengan Hypothesis tidak menggantikan pengujian berbasis contoh. Anda masih membutuhkan pengujian spesifik yang ditulis tangan untuk logika bisnis yang kritis dan persyaratan yang dipahami dengan baik (misalnya, "Pengguna dari negara X harus melihat harga Y").
Apa yang disediakan Hypothesis adalah cara otomatis yang kuat untuk menjelajahi perilaku kode Anda dan menjaga terhadap kasus ekstrem yang tak terduga. Ia bertindak sebagai mitra yang tak kenal lelah, menghasilkan ribuan pengujian yang lebih beragam dan licik daripada yang dapat ditulis oleh manusia secara realistis. Dengan mendefinisikan properti fundamental dari kode Anda, Anda membuat spesifikasi yang kuat yang dapat diuji oleh Hypothesis, memberi Anda tingkat kepercayaan baru pada perangkat lunak Anda.
Saat berikutnya Anda menulis fungsi, luangkan waktu sejenak untuk berpikir di luar contoh. Tanyakan pada diri Anda, "Apa aturannya? Apa yang harus selalu benar?" Kemudian, biarkan Hypothesis melakukan kerja keras mencoba melanggarnya. Anda akan terkejut dengan apa yang ditemukannya, dan kode Anda akan menjadi lebih baik karenanya.