Pengujian transisi keadaan membantu ketika perilaku sistem bergantung pada keadaan objek saat ini. Pengguna, pesanan, tiket, pembayaran, langganan, dan sesi semuanya memiliki status dan transisi yang diizinkan.
Kapan menggunakannya
- →Ada status eksplisit seperti dibuat, aktif, diblokir, dibatalkan, dan selesai.
- →Beberapa transisi dilarang.
- →Perilaku dan tindakan yang tersedia bergantung pada keadaan saat ini.
- →Bug sering kali melibatkan pelanggaran siklus hidup, bukan bidang individual.
Apa yang harus didokumentasikan
- →Status objek
- →Transisi yang diizinkan
- →Peristiwa yang memicu transisi
- →Batasan: siapa yang dapat memulai transisi, dalam kondisi apa, dan apa yang terjadi setelahnya
Contoh
Misalkan laporan bug berstatus Terbuka, Sedang Berlangsung, Siap Diuji, Dibuka Kembali, dan Ditutup. Pengujian transisi status memeriksa apakah penutupan langsung diperbolehkan, siapa yang boleh membukanya kembali, dan apakah siklus perbaikan lainnya mempertahankan logika yang benar.
Apa yang harus diperiksa QA
- →Transisi terlarang: sistem harus memblokirnya atau menjelaskannya dengan tepat.
- →Efek samping transisi: notifikasi, audit, perubahan akses, dan pencatatan.
- →Transisi berulang dan gerakan mundur.
- →Status setelah kegagalan: apa yang terjadi jika suatu operasi dihentikan di tengah jalan?
Jika suatu objek memiliki siklus hidup, menggambar diagram keadaan sederhana biasanya berguna. Ini dengan cepat mengungkapkan titik-titik buta yang sulit dilihat dalam daftar kasus.
Contoh siklus hidup pesanan dengan kontrak eksplisit
Ini adalah model fiktif untuk satu pesanan, bukan perilaku setiap toko atau pembayaran API. Draft adalah pesanan yang belum terkirim, Placed telah dikirimkan, Paid telah dibayar, Shipped telah dikirim, dan Cancelled adalah terminal. Hanya lima transisi di bawah ini yang diperbolehkan. Pembatalan setelah pembayaran, pengembalian uang, izin, kesalahan transportasi, dan permintaan bersamaan berada di luar model kecil ini dan memerlukan aturan tambahan.
Draft --submit--> Placed --pay--> Paid --ship--> Shipped
Draft --cancel--> Cancelled
Placed --cancel--> CancelledAlternatif teks: submit memindahkan Draft ke Placed; pay memindahkan Placed ke Paid; ship memindahkan Paid ke Shipped. cancel memindahkan Draft atau Placed ke Cancelled. Shipped dan Cancelled tidak memiliki transisi keluar. Semua kombinasi keadaan/peristiwa lainnya ditolak tanpa mengubah keadaan atau menimbulkan efek samping bisnis.
Tabel pengujian peristiwa tunggal yang lengkap
Reset pesanan ke keadaan pada kolom pertama sebelum setiap baris dan gunakan kunci request baru. Jumlah tersebut adalah penghitung model untuk transisi bisnis yang tersimpan; bukan jumlah universal log, notifikasi, atau tagihan eksternal. Transisi yang diterima menambah satu; penolakan menambah nol.
Keadaan awal Peristiwa Hasil Keadaan akhir Efek baru
Draft submit diterima Placed 1
Draft pay ditolak Draft 0
Draft ship ditolak Draft 0
Draft cancel diterima Cancelled 1
Placed submit ditolak Placed 0
Placed pay diterima Paid 1
Placed ship ditolak Placed 0
Placed cancel diterima Cancelled 1
Paid submit ditolak Paid 0
Paid pay ditolak Paid 0
Paid ship diterima Shipped 1
Paid cancel ditolak Paid 0
Shipped submit ditolak Shipped 0
Shipped pay ditolak Shipped 0
Shipped ship ditolak Shipped 0
Shipped cancel ditolak Shipped 0
Cancelled submit ditolak Cancelled 0
Cancelled pay ditolak Cancelled 0
Cancelled ship ditolak Cancelled 0
Cancelled cancel ditolak Cancelled 0Percobaan ulang adalah kontrak terpisah, bukan keadaan tambahan
Untuk model ini, permintaan yang berhasil menyimpan kejadian dan hasilnya di bawah kunci yang dicakup dalam pesanan ini. Mengulangi kunci dan peristiwa yang sama akan mengembalikan hasil yang disimpan tanpa efek samping baru. Menggunakan kembali kunci yang berhasil untuk peristiwa berbeda akan mengembalikan konflik kunci. Permintaan yang ditolak tidak disimpan. Aturan yang sengaja disederhanakan ini bukan merupakan pernyataan tentang kode status HTTP atau penyedia pembayaran sebenarnya.
Urutan Respons yang diharapkan Keadaan saat ini Total efek yang tersimpan
submit / s1 diterima, Placed Placed 1
pay / p1 diterima, Paid Paid 2
pay / p1 lagi diterima, Paid (hasil yang tersimpan) Paid 2
ship / p1 key-conflict, Paid Paid 2
pay / p2 ditolak, Paid Paid 2
ship / s2 diterima, Shipped Shipped 3Hasil yang disimpan adalah cuplikan permintaan asli, belum tentu status pesanan saat ini setelah transisi selanjutnya. API asli harus menentukan cakupan kunci, retensi, perbandingan muatan, pemesanan, dan persistensi atom. Model pengajaran hanya memori tidak dapat menunjukkan perlindungan terhadap biaya duplikat secara bersamaan.
Jalankan model referensi secara lokal
Simpan sebagai order_state_model.py dan jalankan python3 order_state_model.py. Kode memeriksa seluruh 20 kombinasi keadaan/peristiwa, lalu urutan retry. Keluaran yang diharapkan: Transitions: 5 accepted, 15 rejected; replay checks: passed. Pemeriksaan ini memverifikasi model pembelajaran tersebut; uji aplikasi secara terpisah terhadap aturan yang sama.
STATES = ("Draft", "Placed", "Paid", "Shipped", "Cancelled")
EVENTS = ("submit", "pay", "ship", "cancel")
TRANSITIONS = {
("Draft", "submit"): "Placed", ("Draft", "cancel"): "Cancelled",
("Placed", "pay"): "Paid", ("Placed", "cancel"): "Cancelled",
("Paid", "ship"): "Shipped",
}
class Order:
def __init__(self, state="Draft"):
self.state, self.effects, self.saved = state, 0, {}
def apply(self, event, key):
if key in self.saved:
old_event, old_result = self.saved[key]
return old_result if event == old_event else ("key-conflict", self.state)
target = TRANSITIONS.get((self.state, event))
if target is None:
return ("rejected", self.state)
self.state = target
self.effects += 1
result = ("accepted", target)
self.saved[key] = (event, result)
return result
accepted = 0
for state in STATES:
for event in EVENTS:
order = Order(state)
target = TRANSITIONS.get((state, event))
expected = ("accepted", target) if target else ("rejected", state)
assert order.apply(event, "unique") == expected
assert order.state == (target or state)
assert order.effects == int(target is not None)
accepted += target is not None
order = Order()
assert order.apply("submit", "s1") == ("accepted", "Placed")
assert order.apply("pay", "p1") == ("accepted", "Paid")
assert order.apply("pay", "p1") == ("accepted", "Paid")
assert order.effects == 2
assert order.apply("ship", "p1") == ("key-conflict", "Paid")
assert order.apply("pay", "p2") == ("rejected", "Paid")
assert order.effects == 2
assert order.apply("ship", "s2") == ("accepted", "Shipped")
assert order.effects == 3
print(f"Transitions: {accepted} accepted, {20-accepted} rejected; replay checks: passed")Untuk sistem nyata, perluas cakupan urutan: batas waktu antara pembayaran dan persistensi, panggilan balik duplikat, versi lama, dan pembatalan yang berpacu dengan pembayaran. Tentukan apa yang harus terjadi sebelum menulis pernyataan. Lima transisi yang tercakup tidak membuktikan semua jalur multi-langkah atau pemulihan kegagalan berhasil.
Lanjutkan belajar
Gunakan tabel keputusan untuk kondisi kondisi penjaga transisi