Beqaror test aslida nima
Beqaror deb kodga tegilmagan holda goho yiqiladigan testni aytishadi. Odatda aybdor test deb hisoblanadi va unga retry qoʻyiladi. Xulosa notoʻgʻri: koʻpchilik hollarda test haqiqiy poygani ushlagan va uni takrorlay olmagan.
Asinxron kod bitta tartibda bajarilmaydi. Bir vaqtning oʻzida bir nechta kolbek tayyor boʻlganda, kimdir qaysi biri birinchi ketishini hal qilishi kerak — buni ijro muhiti operatsion tizim bilan birga hal qiladi. Variantlar mingta, sizni bittasi buzadi. Sizning mashinangizda u hech qachon chiqmaydi, tunda yigʻish serverida esa bir marta chiqadi va qaytmaydi.
Bu prodda qancha turadi
Retray bilan oʻchirilgan poyga yoʻqolmaydi — u ishlab chiqarishga koʻchadi, u yerda jadvalni sizning noutbukingiz emas, yuklama tanlaydi. Va u yerda bu pul turadi.
Birinchi qator eng qimmat va eng koʻp uchraydigani. Toʻlov tizimlarida amallarning idempotentligi aynan shuning uchun bor, va oddiy test aynan shuni oʻtkazib yuboradi: tashlab ketilgan soʻrov test ketganidan keyin yetib keladi.
Nega retry(3) yechim emas
Retray «nega yiqildi» savolini «necha marta qayta ishga tushirsa oʻtadi» savoliga almashtiradi. Bu qizil yigʻish hech kimni ajablantirmay qoʻygan kungacha ishlaydi — oʻsha kundan boshlab testlar toʻplami hech narsani himoya qilmaydi.
- Retray beqarorlikni emas, xatolar sinfini yashiradi: har qanday haqiqiy poyga xuddi shunday koʻrinadi va xuddi shunday «tuzatiladi».
- Yigʻish vaqti oʻsadi, ishonch tushadi: jamoa logni oʻqishdan oldin qayta ishga tushiradi.
- Xato kodda qoladi va kamdan-kam jadvalni tez-tez uchraydiganga aylantiradigan yuklamani kutadi.
Jadvalni operatsion tizimdan tortib olish
Deterministik simulyatsiya gʻoyasi oddiy: agar noaniqlik siz boshqarmaydigan narsadan kelsa — uni oʻzingizga oling. Yurish davomida setTimeout, setInterval, setImmediate, Date, performance.now va Math.random almashtiriladi. Kutishning barcha yoʻllari bitta virtual taymerlar navbatiga tushadi va «keyin nima boʻladi» savolining yagona egasi paydo boʻladi.
Bir virtual lahzada bir nechta kolbek tayyor boʻlsa, tartib siddan tanlanadi va yozib qoʻyiladi. Bir sid — istalgan mashinada bayt-baytigacha bir xil yurish. Qadamlar orasida invariantlar qayta tekshiriladi, shuning uchun buzilish uni keltirib chiqargan jadvalda ushlanadi.
Bir yon taʼsirni ortiqcha baholab boʻlmaydi: virtual vaqt bepul. Olti soatlik chekinishli qayta urinish siyosati mikrosoniyalarda tekshiriladi, osilgan tizim esa sekinidan farqlanadi — chunki simulyator uygʻotadigan hech kim qolmaganini biladi.
Bu kodda qanday koʻrinadi
Buning uchun ochiq kodli vosita yozdik — unflake. Test oddiy koʻrinadi, faqat tanasi boshqariladigan dunyoda bajariladi, shart esa har bir rejalashtirish qadamidan keyin qayta tekshiriladi.
import { check } from 'unflake';
import { it } from 'vitest';
it('ulanish ikki marta berilmaydi', async () => {
await check('bitta ulanish — bitta egasi', async (sim) => {
const pool = createPool(sim, { size: 2 });
const held = new Map<string, number>();
// Test oxirida emas, har bir qadamdan keyin qayta tekshiriladi.
sim.invariant('ulanish ikkovida emas', () =>
[...held.values()].every((n) => n <= 1),
);
await sim.parallel(4, async () => {
const conn = await pool.acquire();
held.set(conn, (held.get(conn) ?? 0) + 1);
await sim.io('query', { latency: [1, 6] });
held.set(conn, (held.get(conn) ?? 0) - 1);
pool.release(conn);
});
}, { runs: 200 });
});Ikki yuz jadvaldan biri invariantni buzsa, yiqilish buzishda davom etadigan eng kichik jadvalgacha siqiladi va vaqt chizigʻi bilan chop etiladi: kim nimani ushlab turgan, kim nimani kutgan va qaysi lahzada uygʻotadigan hech kim qolmagan. U sid bilan emas, yozib olingan reja bilan qayta ijro etiladi — yaʼni begona mashinada ham.
Tanlab olish va isbotlash — har xil narsa
Ikki yuz tasodifiy jadval — bu fazzing: toza natija «topilmadi» degani, «boʻlmaydi» degani emas. Yetarlicha kichik test uchun koʻproq narsa olish mumkin — qaror daraxtini toʻliq sanab chiqish. Bu farq amalda nimani anglatishi model tekshiruvi haqidagi materialda koʻrib chiqilgan.
const report = await explore('bitta ulanish — bitta egasi', body);
// { ok: true, schedules: 24, exhaustive: true }exhaustive: true — bu makon toʻliq aylanib chiqilgani, va shunda oʻtish «buzilish mavjud emas» deb oʻqiladi. Chegara halol: daraxt koʻpaytma tarzida oʻsadi, keng latency: [1, 25] har bir kiritish-chiqarishda yigirma besh tarmoq beradi, toʻliq sanash esa kichik testlar nasibasi boʻlib qoladi.
Begona kodga qaratganda nima chiqdi
Hech narsa topmaydigan vosita shubha uygʻotadi — shuning uchun uni begona paketlarga qaratdik va natijani toʻliq eʼlon qildik: yettita mashhur kutubxonada 19 ta hujjatlashtirilgan shartnoma (p-limit, p-queue, async-mutex, async-sema, generic-pool, p-retry, bottleneck), har biriga yuzlab jadval.
Birorta shartnoma buzilmadi. Bu halol sarlavha va uni jimgina aytmay qoʻyish oʻrniga ovoz chiqarib aytish kerak. Bu kutilgan natija ham: bu paketlar haftasiga millionlab yuklanadi, ularning asosiy kafolatlarini har bir foydalanuvchi har kuni ishlatadi. Birinchi kechadayoq p-limit chegarasini «buzadigan» vosita oʻz kalibrlanishi haqida gapiradi, p-limit haqida emas.
Bitta topilma haqiqiydek koʻrindi: bottleneck minTime: 10 da ikkita vazifani 9 ms farq bilan ishga tushirardi. U simulyatordan tashqarida ham takrorlandi — lekin farq toʻrt yuz jadvalning hammasida aynan 1 ms edi, yaʼni bu rejalashtirish nuqsoni emas, ishga tushish narxi. Bunday hisobot yuborish — mayntenerning kechasini bir millisekund uchun olib qoʻyish. Yubormadik va sababini yozib qoʻydik.
Usul nimani qila olmaydi
Kafolatlarini oshirib koʻrsatadigan test vositasi yoʻqligidan yomonroq. Chegaralar bular.
- Faqat oʻzi boshqargan narsani koʻradi. Haqiqiy soket, fayl yoki nativ drayver rejalashtiruvchiga koʻrinmaydi, bajariladigan narsa qolmagan yurish esa oʻzaro blokirovkaga oʻxshaydi. Haqiqiy kiritish-chiqarishni simulyator orqali oʻtkazish kerak.
- Perebor makonini qisqartirmaydi. Vosita qaysi amallar umumiy holatga tegishini bilmaydi, shuning uchun ikkita tartib ekvivalentligini isbotlab, birini tashlab keta olmaydi. Rustda buni loom uddalaydi — chunki test ostidagi kod uning oʻz primitivlarini ishlatadi.
- Parallellik haqida emas. Node bir oqimli, simulyator ham: vorkerlar, jarayonlar va nativ modullar ichidagi poygalar qamrovdan tashqarida.
- Mikrovazifalar tartibiga tegilmaydi. Promislarning hal boʻlishi allaqachon deterministik; uni qayta tartiblash — dvigatel yarata olmaydigan poygalarni oʻylab topish, yolgʻon signal esa oʻtkazib yuborilgan xatodan qimmatroq.
Bir soatda qanday boshlash mumkin
- Yigʻishlar tarixidan retray qoʻyilgan yoki kodga tegilmay yiqilgan testni toping.
- U aslida nimani tekshirayotganini invariant sifatida ifodalang: «bitta ulanish — bitta egasi», «yechib olish bir marta boʻladi».
- Test tanasini haqiqiy taymerlar va tarmoq emas, simulyator orqali kutadigan qilib qayta yozing.
- Ikki yuz jadvalni yuriting. Yiqilsa — sizda eng kichik jadval va qayta ijro bor; yiqilmasa — retrayni olib tashlashga asos bor.
- Yurishni CIda qoldiring: tuzatilgan poyga endi jimgina qaytmaydi.
Agar gap pul sanaydigan tizim haqida ketayotgan boʻlsa, testlardan emas, amallar arxitekturasidan boshlash kerak: tafsilotlar toʻlovlar idempotentligi tahlilida va fintech ishlab chiqish sahifasida. Qolgan muhandislik tahlillari «Muhandislik» boʻlimida.
Ko‘p so‘raladigan savollar
Bu fake-timers kabi taymerlarni almashtirishdan nimasi bilan farq qiladi?
Taymerlarni almashtirish vaqtni boshqaradi, lekin bir nechta tayyor kolbekdan qaysi biri birinchi ketishini hal qilmaydi. Poygalar aynan shu tanlovda yashaydi. Deterministik simulyatsiya vaqtni ham, tartibni ham boshqaradi va qilingan tanlovni yozib qoʻyadi.
Bu bizning oddiy testlarimizni almashtiradimi?
Yoʻq. Bu konkurent kod uchun alohida qatlam: pullar, navbatlar, qayta urinishlar, tranzaksiyalar — bir nechta vazifa umumiy holatga tegadigan hamma joy. Yunit va integratsion testlar oʻz oʻrnida qoladi.
Nechta jadval yuritish kerak?
Ikki yuz — maʼqul standart: vaqt virtual boʻlgani uchun yurish arzon. Kichik testlar uchun toʻliq sanash mantiqan toʻgʻri — u «topilmadi»ni «mavjud emas»ga aylantiradi.
Biz TypeScriptda yozmaymiz. Nima qilish kerak?
Yondashuv tilga bogʻliq emas: Rustda loom va shuttle, taqsimlangan tizimlarda Jepsen, maʼlumotlar bazalarida oʻz simulyatorlari. Qoida bitta: noaniqlik manbasini oʻzingizniki qiling va qilingan tanlovni yozib boring.
Manbalar
Maqoladagi da’volarni tekshirish mumkin: quyida qayta hikoya emas, birlamchi manbalar.
- 01FoundationDB: Testing — Toʻgʻriligi deterministik simulyatsiyaga tayanadigan maʼlumotlar bazasi
- 02TigerBeetle: VOPR — Xuddi shu yondashuv moliyaviy registr uchun
- 03loom (Rust) — Makonni qisqartirish bilan toʻliq sanash — JavaScriptda hali yoʻq
- 04unflake — Maqoladagi vosita: MIT, bogʻliqliklarsiz, ichida yettita paket auditi

Bu maqola, boshqalaridan farqli oʻlaroq, ism bilan imzolangan: uning ortida mijoz yoʻq. Gap ketayotgan kod toʻliq ochiq va oʻsha ismda chop etilgan — uni oʻqish, ishga tushirish va maqoladagi daʼvolarni soʻzga ishonmasdan tekshirish mumkin.