اگر از ده توسعهدهنده نرمافزار بپرسید که «تست قرارداد (contract test) چیست؟»، احتمالاً دوازده پاسخ متفاوت دریافت میکنید.
یکی ممکن است بگوید که این موضوع somehow به اعتبارسنجی اسکیما (schema validation) مربوط میشود.
دیگری ممکن است بگوید که به مشخصات API ربط دارد.
نفر دیگری هم ممکن است به شما بگوید که این موضوع به حرفه حقوقی مربوط است و با مهربانی از شما بخواهد که دیگر با سؤالهای نامربوط مزاحمش نشوید.
چند سال پیش، این میزان سردرگمی شاید دوستداشتنی به نظر میرسید.
اما در سال ۲۰۲۵، چارچوبها و ابزارهای جدید بیشماری برای جلب توجه تیمهای نرمافزاری در همهجا با یکدیگر رقابت میکنند.
وقت آن رسیده است که ریشه این موضوع را پیدا کنیم.
وقتی از contract testing صحبت میکنیم، واقعاً درباره چه چیزی حرف میزنیم؟
تعریف بازیگران اصلی: Schemaها در برابر Contractها
یک schema از یک نشانهگذاری عمومی برای تعریف انواع داده و مجموعه ورودیها و خروجیهایی استفاده میکند که یک سیستم واحد در یک مقطع زمانی از آنها پشتیبانی میکند؛ مانند یک تعریف OpenAPI (یا OpenAPI Definition – OAD).
برای مثال، در HTTP، اینها همان قوانین نحوی هستند که درخواستها و پاسخها باید از آنها پیروی کنند.
به بیان دیگر، یک schema صرفاً دادهای است که دادههای دیگر را توصیف میکند.
این schema هیچ کد واقعیای که به API توصیفشده مربوط باشد در خود ندارد.
یا به بیان دیگر، و به نقل از فیلسوف بزرگ Alfred Korzybski:
«نقشه، خودِ سرزمین نیست.»
اما یک contract تعریف میکند که دو سیستم چگونه میتوانند با یکدیگر ارتباط برقرار کنند؛ این کار را با توافق بر سر اینکه چه تعاملهایی میتوانند بین آنها ردوبدل شوند انجام میدهد و مثالهای مشخصی برای آزمودن رفتار مورد توافق ارائه میکند.
اگر بخواهم یک بخش از این تعریف را برجسته کنم، آن بخش «دو سیستم» است.
و اگر بخواهم بخش دومی را برجسته کنم، آن بخش «مثالهای مشخص» است.
محدودیتهای Schemaها
Schemaها انتزاعی هستند — contractها عینی هستند.آنها عینی هستند زیرا شامل مثالهای واقعی از تعامل بین دو سیستم میشوند، نه فقط اطلاعات انتزاعی درباره یک سیستم.
هر دو کاربرد دارند، اما استفاده از schemaها بهتنهایی میتواند به برخی چالشها منجر شود:
-
ابهام (Ambiguity):
Schemaها انتزاعی هستند و این موضوع میتواند به برداشتهای نادرست منجر شود.
برای مثال، در یک تعریف OpenAPI، ممکن است مشخص کنید که یک API میتواند وضعیت ۴۰۰، ۴۰۳ یا ۲۰۰ را بازگرداند، اما تنها با نگاهکردن به specification نمیتوانید با قطعیت بگویید که کدام مجموعه مشخص از ورودیها منجر به کدام کد وضعیت میشود.
این مسئله بهویژه برای schemaهای API که دارای فیلدهای اختیاری و انواع چندریختی (polymorphic types) هستند، مشکلساز است. -
انحراف API (API drift):
تغییرات غیرمنتظره یا ناخواسته در یک API میتوانند در صورتی که بهدرستی ردیابی نشوند و بهطور مداوم با schema همگام نگردند، باعث بروز مشکل شوند.
اگر schema همزمان نقش مستندات API را نیز ایفا کند، این موضوع میتواند منجر به مستنداتی شود که رفتار واقعی API را بهدرستی توصیف نمیکنند. -
پوشش تست (Test coverage):
بر اساس موارد بالا، بررسی اینکه آیا یک سیستم با یک schema سازگار است کار سادهای است، اما بسیار دشوار است که با اطمینان بگوییم آن سیستم کل تعریف API را بهطور کامل پیادهسازی کرده است.
سطوح پیچیدگی
امیدوارم تا اینجا متوجه شده باشید که بین اعتبارسنجی ساده schema و یک تست قرارداد کامل و بالغ تفاوت وجود دارد.
بیایید این تفاوت را در قالب سطوح پیچیدگی بررسی کنیم:
-
سطح ۱ — تست schema:
تأیید میکند که یک سیستم واحد در یک مقطع زمانی با یک schema (مانند OAD) سازگار است. -
سطح ۲ — تست قرارداد مبتنی بر schema (که به آن تست قرارداد دوطرفه یا bidirectional contract test نیز گفته میشود):
روشی از تست که بررسی میکند یک مصرفکننده پیامهایی ارسال میکند که با یک (زیرمجموعهای از یک) schema مشخص مطابقت دارند، و اینکه ارائهدهنده خروجیای تولید میکند که با همان schema سازگار است.
این تست ممکن است از طریق تحلیل ایستا، تولید کد، یا روشهای دیگر ایجاد شود. -
سطح ۳ — تست قرارداد مبتنی بر کد (Code-based contract test):
روشی برای تولید قراردادها و بررسی معتبر بودن آنها با استفاده از تستهای خودکار مبتنی بر کد؛ یعنی تستها باید کد واقعی اپلیکیشن را در هر دو سمت تعامل اجرا کنند.
انتخاب رویکرد مناسب
من پیشتر درباره مزایا و معایب نوع تست schema توصیفشده در سطح ۱ صحبت کردهام، بنابراین حالا بیایید به برخی از ملاحظات و بدهبستانهایی که در سطوح ۲ و ۳ باید در نظر بگیرید نگاه کنیم.
تست قرارداد مبتنی بر کد (مانند Pact) کد واقعی اپلیکیشن را اجرا میکند تا از انحراف پیادهسازی جلوگیری کند، مستندات شفاف «specification-by-example» ارائه میدهد که ابهام را از بین میبرد، و از تکامل سرویس پشتیبانی میکند (در Pact، این کار از طریق Pact Broker تسهیل میشود).
اما نقطهضعف آن این است که به زمان یادگیری بیشتر، نگهداری تستها، و درگیرشدن با مدیریت پیچیده دادههای تست از طریق provider stateها نیاز دارد.
تست قرارداد مبتنی بر schema مزایایی دارد، از جمله تجربه سادهتر برای توسعهدهنده، راهاندازی سریعتر، و گزینههای ابزارسازی گستردهتر.
با این حال، معایب قابل توجهی نیز دارد، از جمله:
-
Schemaها نمیتوانند معناشناسی (semantics) سطح اپلیکیشن را بهطور قابل اعتماد ثبت کنند.
-
آنها درباره اینکه کدام ورودیها منجر به کدام پاسخها میشوند ابهام ایجاد میکنند.
-
برای تکامل، همکاری، و اشتراکگذاری قابل اعتماد بین تیمها به فرایندهای اضافی نیاز دارند.
این چالشها مشابه همان مشکلاتی هستند که هنگام انجام تستهای schema در سطح ۱ با آنها مواجه میشویم.
با درک همزمان مزایا و محدودیتها، میتوانید درباره رویکرد مناسب برای contract testing تصمیمی آگاهانه بگیرید.
