لحظهای که تستهای قرارداد Specmatic را روی کپی «تمامشده» Zerodha با استک MERN خود اجرا کردم، این ابزار پنج نقص واقعی را شناسایی کرد که بررسیهای دستی من هرگز متوجه آنها نشده بودند. از میان ۱۷۸ مورد تست تولید شده، این مجموعه باعث شکست API به روشهایی شد که مستقیماً کاربران واقعی را تحت تأثیر قرار میداد: انواع داده نامعتبر، کرش کردن هنگام استفاده از اعتبارنامههای بدشکل، فساد خاموش دادهها، ثبتنامهای غیرایدمپوتنت (non-idempotent) و یک تغییر قرارداد که باعث توقف فرآیند CI شد.
چگونه باگها از تست دستی گریختند
این پروژه ترکیبی از بکاند Node.js، فرانتاند React، ذخیرهسازی MongoDB و Razorpay برای پرداختها بود. من جریانهای ثبتنام و پرداخت را به صورت دستی بررسی کردم و همه چیز درست به نظر میرسید. با این حال، تست دستی فقط «مسیر خوشبینانه» (happy path) را آزمایش میکند: یعنی تأیید میکند که کد وقتی کاربران مراحل تعیینشده را دنبال میکنند، درست عمل میکند. اما ثابت نمیکند که سرویس میتواند در برابر درخواستهای بدشکل یا رفتارهای غیرمنتظره کلاینت دوام بیاورد.
وقتی Specmatic را روی کد موجود نشانه رفتم، «قرارداد» (contract) — که توصیفی صریح از شکل درخواست و پاسخ هر اندپوینت است — به عنوان منبع حقیقت عمل کرد. سپس این ابزار یک ماتریس عظیم از سناریوهای مثبت و منفی را به صورت خودکار تولید کرد که بسیاری از آنها هرگز به ذهن یک تستکننده انسانی نمیرسید.
پنج نقص کشفشده
- شکافهای اعتبارسنجی ورودی – اندپوینت
/newOrderاعداد اعشاری و رشتهها را برای فیلدquantityمیپذیرفت، در حالی که قرارداد مستلزم عدد صحیح (integer) بود. تستهای تولید شده که انواع داده اشتباه ارسال میکردند، باعث عملکرد نادرست API شدند. - خطاهای مدیریتنشده ورود – ارسال اعتبارنامههای بدشکل به مسیر احراز هویت، به دلیل نبود بررسیهای نوع داده (type checks) در کد، باعث بروز استثنا در زمان اجرا (runtime exception) شد.
- فساد خاموش پرداخت – اندپوینت
/verify-paymentمقادیر بولین (boolean) را برایamountمیپذیرفت. وقتی یک مقدارtrueعبور میکرد، پایگاه داده یک پرداخت موفق با مقدار صفر ثبت میکرد که باعث تورم بیخبر ارقام درآمد میشد. - فقدان ایدمپوتنت بودن (Idempotency) – اجرای مجدد جریان ثبتنام در مجموعه تست با شکست مواجه شد، زیرا اندپوینت به جای مدیریت صحیح کاربر تکراری، سعی میکرد کاربر موجود را دوباره ایجاد کند.
- شناسایی تغییر قرارداد که باعث شکست میشد – من عمداً یک نوع داده را در قرارداد تغییر دادم. خط لوله CI بلافاصله این تغییر را رد کرد و از انتشار نسخهای که باعث شکست سیستم میشد، جلوگیری نمود.
چرا تست قرارداد برای خط لولههای CI اهمیت دارد
- تست منفی در مقیاس بالا – اکثر موارد از میان ۱۷۸ مورد، ورودیهای حالتهای خاص (edge-case) بودند. نوشتن دستی آنها بسیار زمانبر بود.
- ایمنی برای کلاینتهای شخص ثالث – قراردادها تعریف میکنند که یک سرویس به مصرفکنندگان خارجی چه وعدهای میدهد. اگر پیادهسازی تغییر کند، تست قرارداد شکست میخورد و از اپلیکیشنهای پاییندستی محافظت میکند.
- حلقه بازخورد سریع – دروازه CI مانع از اعمال یک تغییر مخرب قبل از ادغام (merge) شد و تیم را از یک بازگشت به حالت قبل (rollback) پرهزینه نجات داد.
- بهبود کیفیت کد – اضافه کردن یک اندپوینت actuator و ایدمپوتنت کردن جریان ثبتنام، گامهای لازم برای تستپذیر کردن قرارداد بودند که به نوبه خود باعث مقاومتر شدن سرویس شد.
موازنهای که توسعهدهندگان باید در نظر بگیرند
تست قرارداد هزینههای نگهداری را افزایش میدهد. مشخصات (specification) باید با کد همگام بماند و فرآیند تولید تست میتواند زمان بیلد (build) را طولانیتر کند. تیمها باید تصمیم بگیرند که آیا ایمنی اضافی، این تلاش بیشتر را توجیه میکند یا خیر، بهویژه برای پروژههای کوچکتر که تست دستی کافی به نظر میرسد.
آنچه باید در آینده زیر نظر داشت
- پذیرش گستردهتر در CI – با ادغام بیشتر مجموعههای قرارداد در خط لولههای تیمها، ابزارها احتمالاً سریعتر و قابلتنظیمتر خواهند شد.
- فرمتهای استاندارد قرارداد – مشخصات نوظهور میتواند اشتراکگذاری قراردادها بین سرویسها و تیمها را آسانتر کند.
- خودکارسازی بهروزرسانی مشخصات – ابزارهایی که قراردادها را از تغییرات کد استخراج میکنند، ممکن است بار نگهداری دستی را کاهش دهند.
اگر تصور میکنید API شما به دلیل روان بودن رابط کاربری (UI) مستحکم است، اجرای یک تست قرارداد میتواند نقصهای پنهانی را آشکار کند که بررسیهای دستی از آنها غافل میمانند. افزودن یک قرارداد قابل اجرا به خط لوله CI، عبارت «خوب به نظر میرسد» را به «امنیت اثباتشده» تبدیل میکند.
Repository: https://github.com/priya3054/zerodha-specmatic
