Skip to content

فصل ۸ — رویهٔ اصلی: تست و تحویل مداوم

تست و تحویل مداوم دومین رویه از سه رویهٔ اصلی زیرساخت به‌عنوان کد است که شامل تعریف همه‌چیز به‌عنوان کد و ساخت قطعات کوچک هم می‌شود. تست سنگ‌بنای مهندسی نرم‌افزار چابک است. برنامه‌نویسی افراطی (XP) بر نوشتن تست‌ها ابتدا با TDD و یکپارچه‌سازی مکرر کد با CI تأکید دارد.¹ CD این را گسترش می‌دهد تا آمادگی کامل تولید کد را همان‌طور که توسعه‌دهندگان روی آن کار می‌کنند تست کند، نه اینکه تا پایان کار روی یک انتشار منتظر بمانند.²

اگر تمرکز قوی روی تست برای کد برنامه نتایج خوبی ایجاد می‌کند، منطقی است انتظار داشته باشیم برای کد زیرساخت هم مفید باشد. این فصل راهبردهای تست و تحویل زیرساخت را بررسی می‌کند. به‌شدت از رویکردهای مهندسی چابک برای کیفیت، از جمله TDD، CI و CD بهره می‌برد. این رویه‌ها همه کیفیت را با جاسازی تست در فرآیند نوشتن کد در سیستم می‌سازند، نه اینکه آن را برای بعد بگذارند.

این فصل روی چالش‌ها و رویکردهای بنیادی تست زیرساخت متمرکز است. فصل بعد (فصل ۹) با راهنمایی مشخص برای تست کد پشتهٔ زیرساخت بر این فصل بنا می‌گذارد، در حالی که فصل ۱۱ تست کد پیکربندی سرور را بحث می‌کند (به «تست کد سرور» در صفحه ۱۷۶ مراجعه کنید).


¹ به «Continuous Integration» اثر Martin Fowler مراجعه کنید.

² کتاب Continuous Delivery اثر Jez Humble و David Farley (Addison-Wesley) اصول و رویه‌های CD را تعریف کرد و آن را از عبارتی مبهم در مانیفست چابک به رویهٔ گسترده در میان تیم‌های تحویل نرم‌افزار ارتقا داد.

چرا کد زیرساخت را به‌طور مداوم تست کنیم؟

تست تغییرات زیرساخت قطعاً ایدهٔ خوبی است. اما نیاز به ساخت و نگه‌داری مجموعه‌ای از کد اتوماسیون تست ممکن است به‌اندازهٔ آن واضح نباشد. اغلب ساخت زیرساخت را فعالیتی یک‌باره می‌بینیم: بساز، تست کن، استفاده کن. چرا برای چیزی که یک‌بار می‌سازید تلاش ایجاد مجموعهٔ تست خودکار صرف کنید؟

ایجاد مجموعهٔ تست خودکار کار سختی است، به‌ویژه وقتی کار لازم برای پیاده‌سازی ابزارها و سرویس‌های تحویل و تست — سرورهای CI، خط لوله‌ها، اجراکننده‌های تست، داربست تست و انواع ابزارهای اسکن و اعتبارسنجی — را در نظر بگیرید. هنگام شروع با زیرساخت به‌عنوان کد، ساخت همهٔ این‌ها ممکن است بیشتر از ساخت سیستم‌هایی که روی آن‌ها اجرا می‌کنید به نظر برسد.

در «استفاده از زیرساخت به‌عنوان کد برای بهینه‌سازی تغییر» در صفحه ۴، منطق پیاده‌سازی سیستم‌های تحویل تغییر به زیرساخت را توضیح دادم. خلاصه: پس از ساخت، تغییرات بسیار بیشتری در زیرساخت خواهید داد از آنچه انتظار دارید. وقتی هر سیستم غیرپیش‌پاافتاده زنده می‌شود، باید وصله، ارتقا، رفع اشکال و بهبود دهید.

مزیت کلیدی CD حذف تمایز کلاسیک عصر آهن بین فازهای «ساخت» و «اجرا» در چرخهٔ عمر سیستم است.³³ سیستم‌های تحویل، از جمله تست خودکار و ارتقای کد، را هم‌زمان با خود سیستم طراحی و پیاده کنید. از این سیستم برای ساخت تدریجی زیرساخت و بهبود تدریجی آن در طول عمر عملیاتی استفاده کنید. «زنده شدن» تقریباً رویدادی دلخواه است، تغییری در اینکه چه کسی از سیستم استفاده می‌کند، نه در نحوهٔ مدیریت سیستم.

تست مداوم به چه معناست

یکی از سنگ‌بنای مهندسی چابک، تست هنگام کار — ساخت کیفیت درون کار — است. هرچه زودتر بفهمید هر خط کدی که می‌نویسید برای تولید آماده است، سریع‌تر کار می‌کنید و زودتر ارزش تحویل می‌دهید. یافتن سریع‌تر مشکلات یعنی زمان کمتری برای بررسی و رفع و بازنویسی کد. رفع مداوم مشکلات از انباشت بدهی فنی جلوگیری می‌کند.

بیشتر افراد اهمیت بازخورد سریع را می‌فهمند. اما آنچه تیم‌های واقعاً با عملکرد بالا را متمایز می‌کند، چقدر تهاجمی به دنبال بازخورد واقعاً مداوم می‌روند.

رویکردهای سنتی شامل تست پس از پیاده‌سازی کامل قابلیت سیستم توسط تیم است. روش‌های timebox این را جلوتر می‌برند. تیم در طول توسعه به‌صورت دوره‌ای تست می‌کند، مثلاً در پایان اسپرینت. تیم‌های پیرو Lean یا Kanban هر داستان را هنگام تکمیل تست می‌کنند.⁴

تست واقعاً مداوم حتی مکررتر از این است. افراد هنگام کدنویسی تست می‌نویسند و اجرا می‌کنند، حتی قبل از پایان داستان. مکرراً کد را به سیستم ساخت متمرکز خودکار push می‌کنند — ایده‌آل حداقل یک‌بار در روز.⁵ افراد باید به‌محض push کد بازخورد بگیرند تا با کمترین وقفه در جریان کار پاسخ دهند. حلقه‌های بازخورد تنگ جوهر تست مداوم است.

تست فوری و تست نهایی

راه دیگر فکر کردن به این، طبقه‌بندی هر فعالیت تست به‌عنوان فوری یا نهایی است. تست فوری هنگام push کد رخ می‌دهد. تست نهایی پس از تأخیر، شاید پس از بررسی دستی یا شاید طبق برنامه.

ایده‌آل این است که تست واقعاً فوری باشد، همان‌طور که کد می‌نویسید. فعالیت‌های اعتبارسنجی در ویرایشگر شما اجرا می‌شوند، مانند برجسته‌سازی نحو یا اجرای تست‌های واحد. Language Server Protocol (LSP) استانداردی برای یکپارچه‌سازی بررسی نحو در IDEها تعریف می‌کند که پیاده‌سازی‌هایی برای زبان‌های مختلف دارد.

افرادی که خط فرمان را به‌عنوان محیط توسعه ترجیح می‌دهند می‌توانند از ابزاری مثل inotifywait یا entr برای اجرای بررسی‌ها در ترمینال هنگام تغییر کد استفاده کنند.

نمونهٔ دیگر اعتبارسنجی فوری برنامه‌نویسی زوجی است که اساساً بازبینی کدی است که هنگام کار رخ می‌دهد. زوج‌برنامه‌نویسی بازخورد بسیار سریع‌تری از بازبینی‌هایی که پس از پایان کار روی داستان یا ویژگی و وقتی کس دیگری وقت بازبینی پیدا می‌کند می‌دهد.

ساخت CI و خط لولهٔ CD باید بلافاصله هر بار که کسی تغییری به پایگاه کد push می‌کند اجرا شوند. اجرای فوری روی هر تغییر نه‌تنها بازخورد را سریع‌تر می‌دهد، بلکه دامنهٔ تغییر کوچک برای هر اجرا را تضمین می‌کند. اگر خط لوله فقط به‌صورت دوره‌ای اجرا شود، ممکن است چند تغییر از چند نفر را شامل شود. اگر هر یک از تست‌ها شکست بخورد، سخت‌تر است بفهمید کدام تغییر مشکل را ایجاد کرده، یعنی افراد بیشتری باید درگیر شوند و وقت صرف پیدا و رفع آن کنند.


⁴ برای توضیح داستان‌های چابک به سایت Mountain Goat Software مراجعه کنید.

⁵ تحقیق Accelerate منتشرشده در گزارش سالانهٔ State of DevOps می‌یابد تیم‌هایی که همه حداقل روزانه کد را merge می‌کنند معمولاً مؤثرتر از کسانی هستند که کمتر این کار را می‌کنند. در مؤثرترین تیم‌هایی که دیده‌ام، توسعه‌دهندگان چندین‌بار در روز کد push می‌کنند، گاهی تقریباً هر ساعت.

چه چیزهایی را با زیرساخت تست کنیم؟

جوهر CI تست هر تغییری است که کسی به‌محض امکان انجام می‌دهد. جوهر CD بیشینه‌سازی دامنهٔ آن تست است. همان‌طور که Jez Humble می‌گوید: «همهٔ این را با اطمینان از اینکه کد همیشه در وضعیت قابل‌استقرار است به دست می‌آوریم.»⁶

تضمین کیفیت دربارهٔ مدیریت ریسک‌های اعمال کد به سیستم شماست. آیا کد هنگام اعمال می‌شکند؟ آیا زیرساخت درست ایجاد می‌کند؟ آیا زیرساخت همان‌طور که باید کار می‌کند؟ آیا معیارهای عملیاتی کارایی، قابلیت اطمینان و امنیت را برآورده می‌کند؟ آیا با قوانین تنظیم‌گری و حاکمیت سازگار است؟

CD دربارهٔ گسترش دامنهٔ ریسک‌هایی است که بلافاصله هنگام push تغییر به پایگاه کد تست می‌شوند، نه انتظار برای تست نهایی روزها، هفته‌ها یا حتی ماه‌ها بعد. پس در هر push، خط لوله کد را به محیط‌های تست واقع‌گرایانه اعمال و تست‌های جامع اجرا می‌کند. ایده‌آل این است که پس از عبور کد از مراحل خودکار خط لوله، کاملاً به‌عنوان آمادهٔ تولید اثبات شده باشد.

تیم‌ها باید ریسک‌های ناشی از تغییر کد زیرساخت را شناسایی و فرآیند تکرارپذیری برای تست هر تغییر مشخص در برابر آن ریسک‌ها بسازند. این فرآیند به شکل مجموعه‌های تست خودکار و تست‌های دستی است. مجموعهٔ تست مجموعه‌ای از تست‌های خودکار است که به‌عنوان گروه اجرا می‌شوند.

وقتی افراد به تست خودکار فکر می‌کنند، معمولاً به تست‌های عملکردی مثل تست‌های واحد و تست‌های سفر مبتنی بر UI فکر می‌کنند. اما دامنهٔ ریسک گسترده‌تر از نقص‌های عملکردی است، پس دامنهٔ اعتبارسنجی هم گسترده‌تر است. محدودیت‌ها و نیازمندی‌های فراتر از صرفاً عملکردی اغلب نیازمندی‌های غیرعملکردی (NFR) یا نیازمندی‌های بین‌عملکردی (CFR) نامیده می‌شوند.⁷ نمونه‌هایی از چیزهایی که ممکن است بخواهید اعتبارسنجی کنید، چه خودکار و چه دستی:

کیفیت کد آیا کد خوانا و قابل‌نگهداری است؟ آیا استانداردهای تیم برای قالب و ساختار کد را رعایت می‌کند؟ بسته به ابزارها و زبان‌هایی که استفاده می‌کنید، برخی ابزارها می‌توانند کد را برای خطاهای نحو و رعایت قوانین قالب‌بندی اسکن و تحلیل پیچیدگی اجرا کنند. بسته به قدمت و محبوبیت، زبان‌های زیرساخت ممکن است ابزارهای زیادی (یا هیچ!) نداشته باشند. روش‌های بررسی دستی شامل فرآیندهای بازبینی کد با دروازه، جلسات نمایش کد و برنامه‌نویسی زوجی است.

عملکرد آیا آنچه باید انجام می‌دهد؟ در نهایت، عملکرد با استقرار برنامه‌ها روی زیرساخت و بررسی اجرای صحیح آن‌ها تست می‌شود. این به‌طور غیرمستقیم صحت زیرساخت را تست می‌کند، اما اغلب می‌توانید مشکلات را قبل از استقرار برنامه‌ها بگیرید. نمونه‌ای برای زیرساخت مسیریابی شبکه است. آیا می‌توان از اینترنت عمومی به سرورهای وب اتصال HTTPS برقرار کرد؟ شاید بتوانید این را با زیرمجموعه‌ای از کل زیرساخت تست کنید.

امنیت می‌توانید امنیت را در سطوح مختلف تست کنید، از اسکن کد تا تست واحد تا تست یکپارچه‌سازی و نظارت تولید. ابزارهای خاص تست امنیت مانند اسکنرهای آسیب‌پذیری وجود دارد. همچنین مفید است تست‌های امنیتی را در مجموعه‌های تست استاندارد بنویسید. مثلاً تست‌های واحد می‌توانند دربارهٔ پورت‌های باز، مدیریت حساب کاربری یا مجوزهای دسترسی ادعا کنند.

انطباق سیستم‌ها ممکن است نیاز به رعایت قوانین، مقررات، استانداردهای صنعتی، تعهدات قراردادی یا سیاست‌های سازمانی داشته باشند. اطمینان و اثبات انطباق می‌تواند برای تیم‌های زیرساخت و عملیات وقت‌گیر باشد. تست خودکار می‌تواند در این بسیار مفید باشد، هم برای گرفتن سریع تخلفات و هم برای ارائهٔ شواهد به ممیزان. مانند امنیت، می‌توانید این را در سطوح مختلف اعتبارسنجی انجام دهید، از سطح کد تا تست تولید. برای نگاه گسترده‌تر به «حاکمیت در گردش‌کار مبتنی بر خط لوله» در صفحه ۳۵۲ مراجعه کنید.

کارایی ابزارهای خودکار می‌توانند سرعت تکمیل اقدامات خاص را تست کنند. تست سرعت اتصال شبکه از نقطهٔ A به B می‌تواند مشکلات پیکربندی شبکه یا پلتفرم ابری را قبل از استقرار برنامه نشان دهد. یافتن مشکلات کارایی روی زیرمجموعه‌ای از سیستم نمونهٔ دیگری از بازخورد سریع‌تر است.

مقیاس‌پذیری تست‌های خودکار می‌توانند اثبات کنند مقیاس‌دهی درست کار می‌کند؛ مثلاً بررسی اینکه خوشهٔ خودمقیاس‌شونده گره اضافه می‌کند وقتی باید. تست‌ها همچنین می‌توانند بررسی کنند آیا مقیاس‌دهی نتایج مورد انتظار را می‌دهد. مثلاً شاید افزودن گره به خوشه ظرفیت را بهبود ندهد به‌خاطر گلوگاهی جای دیگر در سیستم. اجرای مکرر این تست‌ها یعنی سریع می‌فهمید اگر تغییری در زیرساخت مقیاس‌دهی را بشکند.

دسترس‌پذیری به‌طور مشابه، تست خودکار می‌تواند اثبات کند سیستم شما در برابر قطعی‌های بالقوه در دسترس بود. تست‌ها می‌توانند منابع را نابود کنند، مثل گره‌های خوشه، و تأیید کنند خوشه خودکار جایگزین می‌کند. همچنین می‌توانید تست کنید سناریوهایی که خودکار حل نمی‌شوند با ظرافت مدیریت می‌شوند؛ مثلاً نمایش صفحهٔ خطا و اجتناب از فساد داده.

قابلیت بهره‌برداری می‌توانید هر نیازمندی عملیاتی دیگر سیستم را خودکار تست کنید. تیم‌ها می‌توانند نظارت (تزریق خطا و اثبات تشخیص و گزارش)، لاگ‌گیری و فعالیت‌های نگهداری خودکار را تست کنند.

هر یک از این انواع اعتبارسنجی را می‌توان در بیش از یک سطح دامنه اعمال کرد، از پیکربندی سرور تا کد پشته تا سیستم کاملاً یکپارچه. در «تست تدریجی» در صفحه ۱۱۵ بحث می‌کنم. اما ابتدا می‌خواهم به چیزهایی که تست زیرساخت را به‌ویژه دشوار می‌کند بپردازم.


⁶ برای اطلاعات بیشتر دربارهٔ الگوهای CD به وب‌سایت Jez Humble مراجعه کنید.

⁷ همکارم Sarah Taraporewalla اصطلاح CFR را ابداع کرد تا تأکید کند افراد نباید این‌ها را جدا از کار توسعه بدانند، بلکه به همهٔ کارها اعمال می‌شوند. به وب‌سایت او مراجعه کنید.

چالش‌های تست کد زیرساخت

بیشتر تیم‌هایی که با زیرساخت به‌عنوان کد کار می‌کنند برای پیاده‌سازی همان سطح تست و تحویل خودکار برای کد زیرساخت که برای کد برنامه دارند مشکل دارند. و بسیاری از تیم‌های بدون پیشینهٔ مهندسی نرم‌افزار چابک حتی دشوارتر می‌یابند.

فرض زیرساخت به‌عنوان کد این است که می‌توانیم رویه‌های مهندسی نرم‌افزار مانند تست چابک را به زیرساخت اعمال کنیم. اما تفاوت‌های قابل‌توجهی بین کد زیرساخت و کد برنامه وجود دارد. پس باید برخی تکنیک‌ها و ذهنیت‌ها را از تست برنامه تطبیق دهیم تا برای زیرساخت عملی شوند.

موارد زیر چند چالشی است که از تفاوت‌های بین کد زیرساخت و کد برنامه ناشی می‌شود.

چالش: تست‌های کد اعلانی اغلب ارزش کمی دارند

همان‌طور که در فصل ۴ («زبان‌های اعلانی زیرساخت» در صفحه ۴۱) ذکر شد، بسیاری از ابزارهای زیرساخت از زبان‌های اعلانی به‌جای امری استفاده می‌کنند. کد اعلانی معمولاً وضعیت مطلوب برخی زیرساخت را اعلام می‌کند، مانند این کد که زیرشبکهٔ شبکه را تعریف می‌کند:

مثال ۸-۱.

subnet:
  name: private_A
  address_range: 192.168.0.0/16

تست برای این فقط کد را بازمی‌گوید:

assert:
  subnet("private_A").exists
assert:
  subnet("private_A").address_range is("192.168.0.0/16")

مجموعه‌ای از تست‌های سطح پایین کد اعلانی می‌تواند تمرین دفترداری شود. هر بار کد زیرساخت را تغییر می‌دهید، تست را مطابق آن تغییر می‌دهید. این تست‌ها چه ارزشی دارند؟ خوب، تست دربارهٔ مدیریت ریسک است، پس ببینیم تست قبلی چه ریسک‌هایی را می‌تواند آشکار کند:

  • کد زیرساخت هرگز اعمال نشده.
  • کد زیرساخت اعمال شده، اما ابزار آن را درست اعمال نکرده بدون برگرداندن خطا.
  • کسی کد زیرساخت را تغییر داده اما فراموش کرده تست را مطابق آن تغییر دهد.

ریسک اول ممکن است واقعی باشد، اما برای هر اعلان واحد تست لازم نیست. با فرض کدی که چند کار روی سرور انجام می‌دهد، یک تست برای آشکار کردن اینکه به هر دلیلی کد اعمال نشده کافی است.

ریسک دوم در نهایت محافظت در برابر باگ ابزاری است که استفاده می‌کنید. توسعه‌دهندگان ابزار باید آن باگ را رفع کنند یا تیم شما به ابزار قابل‌اطمینان‌تر برود. تیم‌هایی دیده‌ام که چنین تست‌هایی را وقتی باگ خاصی پیدا کرده‌اند و می‌خواستند در برابر آن محافظت کنند استفاده کرده‌اند. تست برای این برای پوشش مشکل شناخته‌شده قابل‌قبول است، اما پوشش دادن کل کد با تست‌های جزئی فقط در صورت باگ ابزار اتلاف است.

ریسک آخر منطق دایره‌ای است. حذف تست ریسکی را که پوشش می‌دهد حذف می‌کند و کار تیم را هم کم می‌کند.

تست‌های اعلانی

قالب Given, When, Then برای نوشتن تست مفید است.⁸ تست اعلانی بخش «When» را حذف می‌کند و قالبی شبیه «Given یک چیز، Then این ویژگی‌ها را دارد» دارد. تست‌های نوشته‌شده به این شکل پیشنهاد می‌کنند کدی که تست می‌کنید نتایج متغیر ایجاد نمی‌کند. تست‌های اعلانی، مانند کد اعلانی، جایگاهی در بسیاری پایگاه‌های کد زیرساخت دارند، اما آگاه باشید بسیاری از ابزارها و رویه‌های تست کد پویا ممکن است مناسب نباشند.

برخی موقعیت‌ها هست که تست کد اعلانی مفید است. دو موردی که به ذهن می‌رسد: وقتی کد اعلانی می‌تواند نتایج مختلف ایجاد کند، و وقتی چند اعلان را ترکیب می‌کنید.

تست کد اعلانی متغیر

مثال قبلی کد اعلانی ساده است — مقادیر سخت‌کد شده‌اند، پس نتیجهٔ اعمال کد روشن است. متغیرها امکان ایجاد نتایج مختلف را معرفی می‌کنند که ممکن است ریسک‌هایی ایجاد کند که تست را مفیدتر کند. متغیرها همیشه تنوعی که نیاز به تست دارد ایجاد نمی‌کنند. اگر متغیرهای ساده به مثال قبلی اضافه کنیم چه؟

subnet:
  name: ${MY_APP}-${MY_ENVIRONMENT}
  address_range: ${SUBNET_IP_RANGE}

ریسک زیادی در این کد نیست که ابزار اعمال‌کننده مدیریت نکند. اگر کسی متغیرها را به مقادیر نامعتبر تنظیم کند، ابزار باید با خطا شکست بخورد.

کد وقتی ریسکی‌تر می‌شود که نتایج ممکن بیشتری دارد. بیایید کد شرطی به مثال اضافه کنیم:

subnet:
  name: ${MY_APP}-${MY_ENVIRONMENT}
  address_range: get_networking_subrange(
    get_vpc(${MY_ENVIRONMENT}),
    data_centers.howmany,
    data_centers.howmany++
  )

این کد منطقی دارد که شاید ارزش تست داشته باشد. دو تابع get_networking_subrange و get_vpc را فراخوانی می‌کند که هر کدام ممکن است شکست بخورند یا نتیجه‌ای برگردانند که با دیگری به شکل غیرمنتظره‌ای تعامل کند.

نتیجهٔ اعمال این کد بر اساس ورودی‌ها و زمینه متغیر است که نوشتن تست را ارزشمند می‌کند.

تصور کنید به‌جای فراخوانی این توابع، کد انتخاب زیرمجموعه‌ای از محدودهٔ آدرس را بخشی از این اعلان برای زیرشبکه نوشته‌اید. این مثال ترکیب کد اعلانی و امری است (همان‌طور که در «جداسازی کد اعلانی و امری» در صفحه ۴۶ بحث شد). تست‌های کد زیرشبکه باید موارد لبهٔ مختلف کد امری را شامل شوند — مثلاً اگر محدودهٔ والد کوچک‌تر از محدودهٔ مورد نیاز باشد چه می‌شود؟ اگر کد اعلانی به‌قدری پیچیده است که تست پیچیده می‌خواهد، نشانه‌ای است که باید بخشی از منطق را از اعلان‌ها بیرون بکشید و در کتابخانه‌ای به زبان رویه‌ای بنویسید. سپس می‌توانید تست‌های جدا و واضح برای آن تابع بنویسید و تست اعلان زیرشبکه را ساده کنید.

تست ترکیب کد اعلانی

موقعیت دیگر که تست ارزشمندتر است وقتی چند اعلان برای زیرساخت دارید که به ساختارهای پیچیده‌تر ترکیب می‌شوند. مثلاً ممکن است کدی داشته باشید که چند ساختار شبکه — بلوک آدرس، متعادل‌کنندهٔ بار، قوانین مسیریابی و دروازه — تعریف کند. هر قطعه کد احتمالاً به‌قدری ساده است که تست غیرضروری باشد. اما ترکیب آن‌ها نتیجه‌ای می‌دهد که ارزش تست دارد — اینکه کسی بتواند از نقطهٔ A به نقطهٔ B اتصال شبکه برقرار کند.

تست اینکه ابزار چیزهای اعلام‌شده در کد را ایجاد کرده معمولاً کمتر از تست اینکه آن‌ها نتایج مورد نظر شما را ممکن می‌کنند ارزش دارد.

چالش: تست کد زیرساخت کند است

برای تست کد زیرساخت باید آن را به زیرساخت مربوط اعمال کنید. و تأمین نمونهٔ زیرساخت اغلب کند است، به‌ویژه وقتی باید روی پلتفرم ابری ایجاد کنید. بیشتر تیم‌هایی که با پیاده‌سازی تست خودکار زیرساخت مشکل دارند می‌یابند زمان ایجاد زیرساخت تست مانعی برای بازخورد سریع است.

راه‌حل معمولاً ترکیبی از راهبردهاست:

تقسیم زیرساخت به قطعات قابل‌مدیریت‌تر شامل کردن قابلیت تست به‌عنوان عامل در طراحی ساختار سیستم مفید است، چون یکی از راه‌های کلیدی آسان‌نگهداری، گسترش و تکامل سیستم است. کوچک‌تر کردن قطعات یک تاکتیک است، چون قطعات کوچک‌تر معمولاً سریع‌تر تأمین و تست می‌شوند. نوشتن و نگه‌داری تست برای قطعات کوچک‌تر و با اتصال شل‌تر آسان‌تر است چون ساده‌ترند و سطح ریسک کمتری دارند. فصل ۱۵ این موضوع را عمیق‌تر بحث می‌کند.

روشن، به حداقل رساندن و جداسازی وابستگی‌ها هر عنصر سیستم ممکن است به بخش‌های دیگر سیستم، سرویس‌های پلتفرم و سرویس‌ها و سیستم‌های خارج از تیم، بخش یا سازمان شما وابسته باشد. این‌ها روی تست تأثیر می‌گذارند، به‌ویژه اگر باید به کس دیگری برای تأمین نمونه‌های پشتیبانی تست تکیه کنید. ممکن است کند، گران، غیرقابل‌اطمینان یا با دادهٔ تست ناسازگار باشند، به‌ویژه اگر کاربران دیگر آن‌ها را به اشتراک بگذارند. test double راه مفیدی برای جداسازی مؤلفه است تا بتوانید سریع تست کنید. ممکن است test double را به‌عنوان بخشی از راهبرد تست تدریجی استفاده کنید — ابتدا مؤلفه را با test double تست کنید و بعد یکپارچه با مؤلفه‌ها و سرویس‌های دیگر. بیشتر در «استفاده از fixtureهای تست برای مدیریت وابستگی‌ها» در صفحه ۱۳۷.

تست تدریجی معمولاً چند مجموعهٔ تست برای جنبه‌های مختلف سیستم دارید. می‌توانید تست‌های سریع‌تر را ابتدا اجرا کنید تا اگر شکست خوردند بازخورد سریع‌تر بگیرید و فقط پس از عبور آن‌ها تست‌های کندتر و دامنهٔ گسترده‌تر را اجرا کنید. در «تست تدریجی» در صفحه ۱۱۵ عمیق‌تر می‌روم.

انتخاب نمونه‌های زودگذر یا پایدار ممکن است هر بار که تست می‌کنید نمونه‌ای از زیرساخت ایجاد و نابود کنید (نمونهٔ زودگذر)، یا نمونه‌ای را بین اجراها روشن نگه دارید (نمونه‌های پایدار). استفاده از نمونه‌های زودگذر می‌تواند تست‌ها را به‌طور قابل‌توجهی کندتر کند، اما تمیزتر و نتایج سازگارتر می‌دهد. نگه‌داشتن نمونه‌های پایدار زمان اجرای تست را کم می‌کند، اما ممکن است تغییرات و ناسازگاری‌های انباشته‌شده بگذارد. راهبرد مناسب را برای هر مجموعهٔ تست انتخاب کنید و بر اساس عملکرد تصمیم را بازبینی کنید. نمونه‌های پیاده‌سازی نمونه‌های زودگذر و پایدار را در «الگو: پشتهٔ تست زودگذر» در صفحه ۱۴۳ ارائه می‌دهم.

تست‌های آنلاین و آفلاین برخی تست‌ها آنلاین اجرا می‌شوند و نیاز به تأمین زیرساخت روی پلتفرم ابری «واقعی» دارند. برخی می‌توانند آفلاین روی لپ‌تاپ یا عامل ساخت اجرا شوند. تست‌هایی که آفلاین اجرا می‌شوند شامل بررسی نحو کد و تست‌هایی در ماشین مجازی یا کانتینر است. ماهیت تست‌های مختلف را در نظر بگیرید و بدانید کدام‌ها کجا می‌توانند اجرا شوند. تست آفلاین معمولاً بسیار سریع‌تر است، پس تمایل دارید زودتر اجرا شوند. می‌توانید از test double برای شبیه‌سازی API ابری آفلاین برای برخی تست‌ها استفاده کنید. بیشتر در «مراحل تست آفلاین برای پشته‌ها» در صفحه ۱۳۱ و «مراحل تست آنلاین برای پشته‌ها» در صفحه ۱۳۴.

با هر یک از این راهبردها باید به‌طور منظم بسنجید چقدر خوب کار می‌کنند. اگر تست‌ها غیرقابل‌اطمینان‌اند، یا درست اجرا نمی‌شوند یا نتایج ناسازگار برمی‌گردانند، باید دلایل را بررسی و یا رفع کنید یا با چیز دیگری جایگزین کنید. اگر تست‌ها به‌ندرت شکست می‌خورند، یا همان تست‌ها تقریباً همیشه با هم شکست می‌خورند، شاید بتوانید آن‌ها را حذف کنید تا مجموعهٔ تست ساده شود. اگر بیشتر وقت صرف پیدا و رفع مشکلات ناشی از تست‌ها می‌کنید تا کدی که تست می‌کنید، راه‌های ساده‌سازی و بهبود را جستجو کنید.

چالش: وابستگی‌ها تست زیرساخت را پیچیده می‌کنند

زمان لازم برای راه‌اندازی زیرساخت دیگری که کد به آن وابسته است تست را کندتر می‌کند. تکنیک مفید برای این، جایگزینی وابستگی‌ها با test double است.

Mock، fake و stub همه انواع test double هستند. test double وابستگی مورد نیاز مؤلفه را جایگزین می‌کند تا بتوانید آن را جداگانه تست کنید. این اصطلاحات توسط افراد مختلف به روش‌های مختلف استفاده می‌شوند، اما تعاریف Gerard Meszaros در کتاب xUnit Test Patterns (Addison-Wesley) را مفید یافته‌ام.⁹

در زمینهٔ زیرساخت، ابزارهای فزاینده‌ای هستند که API فروشندگان ابری را mock می‌کنند.¹⁰ می‌توانید کد زیرساخت را روی ابر mock محلی اعمال کنید تا برخی جنبه‌های کد را تست کنید. این‌ها نمی‌گویند آیا ساختارهای شبکه درست کار می‌کنند، اما باید بگویند آیا تقریباً معتبرند.

اغلب مفیدتر است از test double برای مؤلفه‌های زیرساخت دیگر به‌جای خود پلتفرم زیرساخت استفاده کنید. فصل ۹ نمونه‌هایی از استفاده از test double و fixtureهای تست دیگر برای تست پشته‌های زیرساخت می‌دهد (به «استفاده از fixtureهای تست برای مدیریت وابستگی‌ها» در صفحه ۱۳۷ مراجعه کنید). فصل‌های بعدی بخش IV زیرساخت را به قطعات کوچک‌تر تقسیم و یکپارچه می‌کنند. fixtureهای تست ابزار کلیدی برای نگه‌داشتن مؤلفه‌ها با اتصال شل هستند.


⁸ برای توضیح نوشتن تست‌های Given, When, Then به پست Perryn Fowler مراجعه کنید.

⁹ bliki «Mocks Aren't Stubs» اثر Martin Fowler مرجع مفیدی برای test double است.

¹⁰ نمونه‌های ابزارها و کتابخانه‌های mock ابری شامل Localstack و moto است. Do Better As Code فهرست به‌روزی از این نوع ابزار نگه می‌دارد.

تست تدریجی

بیشتر سیستم‌های غیرپیش‌پاافتاده از چند مجموعهٔ تست برای اعتبارسنجی تغییرات استفاده می‌کنند. مجموعه‌های مختلف ممکن است چیزهای مختلف تست کنند (همان‌طور که در «چه چیزهایی را با زیرساخت تست کنیم؟» در صفحه ۱۰۸ فهرست شد). یک مجموعه ممکن است یک نگرانی را آفلاین تست کند، مثل بررسی آسیب‌پذیری‌های امنیتی با اسکن نحو کد. مجموعهٔ دیگر ممکن است بررسی‌های آنلاین برای همان نگرانی اجرا کند، مثلاً با کاوش نمونهٔ در حال اجرای پشتهٔ زیرساخت برای آسیب‌پذیری‌های امنیتی.

تست تدریجی شامل اجرای مجموعه‌های تست به‌صورت توالی است. توالی از تست‌های ساده‌تر که سریع‌تر روی دامنهٔ کوچک‌تر کد اجرا می‌شوند شروع می‌شود، سپس به تست‌های جامع‌تر روی مجموعهٔ گسترده‌تری از مؤلفه‌ها و سرویس‌های یکپارچه می‌رسد. مدل‌هایی مثل هرم تست و تست پنیر سوئیسی کمک می‌کنند فعالیت‌های اعتبارسنجی را در مجموعه‌های تست ساختار دهید.

اصل راهنما برای راهبرد بازخورد تدریجی، گرفتن بازخورد سریع و دقیق است. به‌عنوان قاعده، این یعنی ابتدا تست‌های سریع‌تر با دامنهٔ باریک‌تر و وابستگی‌های کمتر اجرا شوند و سپس تست‌هایی که تدریجاً مؤلفه‌ها و نقاط یکپارچه‌سازی بیشتری اضافه می‌کنند (شکل ۸-۱). این‌طور خطاهای کوچک سریع آشکار می‌شوند تا سریع رفع و دوباره تست شوند.

شکل ۸-۱. دامنه در مقابل سرعت تست تدریجی

وقتی تست با دامنهٔ گسترده شکست می‌خورد، سطح بزرگی از مؤلفه‌ها و وابستگی‌ها برای بررسی دارید. پس باید هر ناحیهٔ بالقوه را در زودترین نقطه، با کوچک‌ترین دامنه‌ای که می‌توانید پیدا کنید.

هدف دیگر راهبرد تست، نگه‌داشتن مجموعهٔ تست کلی قابل‌مدیریت است. از تکرار تست در سطوح مختلف خودداری کنید. مثلاً ممکن است تست کنید کد پیکربندی سرور برنامه مجوزهای درست پوشهٔ لاگ را تنظیم می‌کند. این تست در مرحلهٔ زودتری که صریحاً پیکربندی سرور را تست می‌کند اجرا می‌شود. نباید تستی داشته باشید که مجوزهای فایل را در مرحله‌ای که کل پشتهٔ زیرساخت در ابر تأمین شده تست می‌کند بررسی کند.

هرم تست

هرم تست مدل شناخته‌شده‌ای برای تست نرم‌افزار است.¹¹ ایدهٔ کلیدی هرم تست این است که تست‌های بیشتری در لایه‌های پایین‌تر — مراحل زودتر در پیشرفت — و تست‌های کمتر در مراحل بعدی داشته باشید (شکل ۸-۲).

هرم برای توسعهٔ نرم‌افزار برنامه طراحی شد. لایهٔ پایین هرم از تست‌های واحد تشکیل شده، هر کدام قطعهٔ کوچکی از کد را تست می‌کند و بسیار سریع اجرا می‌شود.¹² لایهٔ میانی تست‌های یکپارچه‌سازی است که هر کدام مجموعه‌ای از مؤلفه‌های کنار هم را پوشش می‌دهد. مراحل بالاتر تست‌های سفر هستند که از رابط کاربری هدایت می‌شوند و برنامه را به‌عنوان کل تست می‌کنند.

شکل ۸-۲. هرم تست کلاسیک

تست‌های سطوح بالاتر هرم همان دامنه‌ای را که در سطوح پایین‌تر پوشش داده شده پوشش می‌دهند. یعنی می‌توانند کمتر جامع باشند — فقط عملکردی که از یکپارچه‌سازی مؤلفه‌ها ناشی می‌شود را تست کنند، نه اثبات رفتار مؤلفه‌های سطح پایین.

هرم تست با پایگاه‌های کد زیرساخت اعلانی کمتر ارزشمند است. بیشتر کد پشتهٔ اعلانی سطح پایین (به «زبان‌های زیرساخت سطح پایین» در صفحه ۵۴ مراجعه کنید) نوشته‌شده برای ابزارهایی مثل Terraform و CloudFormation برای تست واحد بیش از حد بزرگ است و به پلتفرم زیرساخت وابسته است. ماژول‌های اعلانی (به «استفادهٔ مجدد از کد اعلانی با ماژول‌ها» در صفحه ۲۷۲ مراجعه کنید) تست مفید دشوار دارند، هم به‌خاطر ارزش کمتر تست کد اعلانی (به «چالش: تست‌های کد اعلانی اغلب ارزش کمی دارند» در صفحه ۱۱۰ مراجعه کنید) و هم چون معمولاً بدون زیرساخت چیز مفیدی برای تست نیست.

این یعنی اگرچه تقریباً حتماً تست‌های زیرساخت سطح پایین دارید، شاید به‌اندازهٔ آنچه مدل هرم پیشنهاد می‌کند نباشند. پس مجموعهٔ تست زیرساخت برای زیرساخت اعلانی ممکن است بیشتر شبیه الماس شود، همان‌طور که در شکل ۸-۳ نشان داده شده.

شکل ۸-۳. الماس تست زیرساخت

هرم ممکن است با پایگاه کدی که بیشتر از کتابخانه‌های پویا (به «ایجاد پویای عناصر پشته با کتابخانه‌ها» در صفحه ۲۷۳ مراجعه کنید) نوشته‌شده به زبان‌های امری (به «زبان‌های زیرساخت امری قابل‌برنامه‌ریزی» در صفحه ۴۳ مراجعه کنید) مرتبط‌تر باشد. این پایگاه‌های کد مؤلفه‌های کوچک‌تر بیشتری دارند که نتایج متغیر تولید می‌کنند، پس چیز بیشتری برای تست وجود دارد.

مدل تست پنیر سوئیسی

راه دیگر سازمان‌دهی تست‌های تدریجی، مدل پنیر سوئیسی است. این مفهوم مدیریت ریسک از خارج صنعت نرم‌افزار می‌آید. ایده این است که لایهٔ داده‌شدهٔ تست ممکن است سوراخ داشته باشد، مانند یک برش پنیر سوئیسی، که نقص یا ریسک را از دست بدهد. اما وقتی چند لایه را ترکیب می‌کنید، بیشتر شبیه بلوک پنیر سوئیسی می‌شود که هیچ سوراخی تمام راه را نمی‌زند.

نکتهٔ استفاده از مدل پنیر سوئیسی در تفکر دربارهٔ تست زیرساخت این است که روی جایی که هر ریسک مشخص را بگیرید تمرکز کنید (شکل ۸-۴). هنوز می‌خواهید مشکلات را در زودترین لایه‌ای که امکان‌پذیر است بگیرید، اما مهم این است که جایی در مدل کلی تست شود.

شکل ۸-۴. مدل تست پنیر سوئیسی

نکتهٔ کلیدی تست بر اساس ریسک است، نه بر اساس جا دادن در فرمول.


¹¹ «The Practical Test Pyramid» اثر Ham Vocke مرجع جامعی است.

¹² برای تعریف تست‌های واحد به ExtremeProgramming.org مراجعه کنید. تعریف UnitTest در bliki Martin Fowler چند دیدگاه دربارهٔ تست واحد بحث می‌کند.

خط‌ لوله‌های تحویل زیرساخت

خط لولهٔ CD پیاده‌سازی تست تدریجی را با تحویل کد در محیط‌ها در مسیر به تولید ترکیب می‌کند.¹³ فصل ۱۹ جزئیات نحوهٔ بسته‌بندی، یکپارچه‌سازی و اعمال کد به محیط‌ها توسط خط لوله‌ها را بررسی می‌کند. این بخش نحوهٔ طراحی خط لوله برای تست تدریجی را توضیح می‌دهد.

وقتی کسی تغییر کدی را به مخزن کنترل منبع push می‌کند، تیم از سیستم متمرکزی برای پیشبرد تغییر از سری مراحل برای تست و تحویل تغییر استفاده می‌کند. این فرآیند خودکار است، اگرچه افراد ممکن است برای راه‌اندازی یا تأیید فعالیت‌ها درگیر شوند.

خط لوله فرآیندهای مربوط به بسته‌بندی، ارتقا و اعمال کد و تست را خودکار می‌کند. انسان‌ها ممکن است تغییرات را بازبینی کنند و حتی تست اکتشافی روی محیط‌ها انجام دهند. اما نباید دستورات را دستی برای استقرار و اعمال تغییرات اجرا کنند. همچنین نباید گزینه‌های پیکربندی را انتخاب کنند یا تصمیمات دیگر در لحظه بگیرند. این اقدامات باید به‌عنوان کد تعریف و توسط سیستم اجرا شوند.

خودکارسازی فرآیند تضمین می‌کند هر بار و برای هر مرحله به‌طور سازگار انجام شود. این قابلیت اطمینان تست‌ها را بهبود می‌دهد و سازگاری بین نمونه‌های زیرساخت ایجاد می‌کند.


¹³ Sam Newman مفهوم خط لوله‌های ساخت را در چند پست وبلاگ از ۲۰۰۵ توصیف کرد که در پست ۲۰۰۹ «A Brief and Incomplete History of Build Pipelines» خلاصه کرد. کتاب Continuous Delivery اثر Jez Humble و Dave Farley (ارجاع‌شده در این فصل) خط لوله‌ها را محبوب کرد. Jez الگوی خط لولهٔ استقرار را در وب‌سایتش مستند کرده است.

هر تغییر باید از ابتدای خط لوله push شود. اگر خطایی در مرحلهٔ «پایین‌دست» (بعدی) خط لوله پیدا کردید، آن را در همان مرحله رفع نکنید و از بقیهٔ خط لوله ادامه دهید. به‌جای آن کد را در مخزن رفع کنید و تغییر جدید را از ابتدای خط لوله push کنید، همان‌طور که در شکل ۸-۵ نشان داده شده. این رویه تضمین می‌کند هر تغییر به‌طور کامل تست شود.

شکل ۸-۵. اجرای جدید خط لوله برای اصلاح شکست

در شکل، یک تغییر با موفقیت از خط لوله عبور می‌کند. تغییر دوم در میانهٔ خط لوله شکست می‌خورد. رفعی انجام و در اجرای سوم خط لوله تا تولید push می‌شود.

مراحل خط لوله

هر مرحلهٔ خط لوله ممکن است کارهای مختلف انجام دهد و به روش‌های مختلف راه‌اندازی شود. برخی ویژگی‌های مرحلهٔ مشخص خط لوله:

راه‌انداز (Trigger) رویدادی که باعث شروع مرحله می‌شود. ممکن است خودکار هنگام push تغییر به مخزن کد اجرا شود، یا پس از اجرای موفق مرحلهٔ قبل در خط لوله. یا کسی مرحله را دستی راه‌اندازی کند، مثلاً وقتی تست‌کننده یا مدیر انتشار تصمیم می‌گیرد تغییر کد را به محیط مشخص اعمال کند.

فعالیت (Activity) آنچه هنگام اجرای مرحله رخ می‌دهد. چند اقدام ممکن است برای یک مرحله اجرا شود. مثلاً مرحله‌ای ممکن است کد را برای تأمین پشتهٔ زیرساخت اعمال کند، تست اجرا کند و سپس پشته را نابود کند.

تأیید (Approval) نحوهٔ علامت‌گذاری مرحله به‌عنوان قبول یا رد. سیستم می‌تواند خودکار مرحله را قبول (اغلب «سبز») علامت بزند وقتی دستورات بدون خطا اجرا شوند و همهٔ تست‌های خودکار عبور کنند. یا انسان باید مرحله را تأیید کند. مثلاً تست‌کننده پس از تست اکتشافی روی تغییر مرحله را تأیید کند. همچنین می‌توانید از مراحل تأیید دستی برای امضای حاکمیت استفاده کنید.

خروجی (Output) artifactها یا مواد دیگر تولیدشده توسط مرحله. خروجی‌های معمول شامل بستهٔ کد زیرساخت یا گزارش تست است.

دامنهٔ مؤلفه‌های تست‌شده در یک مرحله

در راهبرد تست تدریجی، مراحل زودتر مؤلفه‌های فردی را اعتبارسنجی می‌کنند، در حالی که مراحل بعدی مؤلفه‌ها را یکپارچه و با هم تست می‌کنند. شکل ۸-۶ نمونه‌ای از تست تدریجی مؤلفه‌هایی که به سرور وب در حال اجرا به‌عنوان بخشی از پشتهٔ بزرگ‌تر منجر می‌شوند نشان می‌دهد.

شکل ۸-۶. یکپارچه‌سازی و تست تدریجی مؤلفه‌ها

یک مرحله ممکن است تست برای چند مؤلفه اجرا کند، مثل مجموعه‌ای از تست‌های واحد. یا مؤلفه‌های مختلف هر کدام مرحلهٔ تست جداگانه داشته باشند. فصل ۱۷ راهبردهای مختلف برای زمان یکپارچه‌سازی مؤلفه‌های مختلف را در زمینهٔ پشته‌های زیرساخت شرح می‌دهد (به «یکپارچه‌سازی پروژه‌ها» در صفحه ۳۲۶ مراجعه کنید).

دامنهٔ وابستگی‌های استفاده‌شده برای یک مرحله

بسیاری از عناصر سیستم به سرویس‌های دیگر وابسته‌اند. پشتهٔ سرور برنامه ممکن است به سرویس مدیریت هویت برای احراز هویت کاربر متصل شود. برای تست تدریجی این، ابتدا مرحله‌ای اجرا می‌کنید که سرور برنامه را بدون سرویس مدیریت هویت تست کند، شاید با سرویس mock به‌جای آن. مرحلهٔ بعدی تست‌های اضافی روی سرور برنامه یکپارچه با نمونهٔ تست سرویس مدیریت هویت اجرا می‌کند و مرحلهٔ تولید با نمونهٔ تولید یکپارچه می‌شود (شکل ۸-۷).

شکل ۸-۷. یکپارچه‌سازی تدریجی با وابستگی‌ها

فقط مراحلی که ارزش اضافه می‌کنند

از ایجاد مراحل غیرضروری در خط لوله خودداری کنید، چون هر مرحله زمان و هزینه به فرآیند تحویل اضافه می‌کند. پس برای کامل بودن مراحل جداگانه برای هر مؤلفه و یکپارچه‌سازی نسازید. تست را به این شکل به مراحل تقسیم کنید فقط وقتی ارزش کافی برای سربار دارد. دلایلی که ممکن است شما را به این سوق دهد شامل سرعت، قابلیت اطمینان، هزینه و کنترل است.

عناصر پلتفرم مورد نیاز برای یک مرحله

سرویس‌های پلتفرم نوع خاصی از وابستگی برای سیستم شما هستند. سیستم شما در نهایت روی پلتفرم زیرساخت اجرا می‌شود، اما ممکن است بتوانید بخش‌هایی از آن را مفیداً آفلاین اجرا و تست کنید.

مثلاً کدی که ساختارهای شبکه را تعریف می‌کند برای تست معنادار باید آن ساختارها را روی پلتفرم ابری تأمین کند. اما ممکن است بتوانید کدی که بستهٔ سرور برنامه را نصب می‌کند را در ماشین مجازی محلی یا حتی در کانتینر تست کنید، به‌جای نیاز به راه‌اندازی ماشین مجازی روی پلتفرم ابری.

پس مراحل تست زودتر ممکن است برای برخی مؤلفه‌ها بدون استفاده از کل پلتفرم ابری اجرا شوند (شکل ۸-۸).

شکل ۸-۸. استفادهٔ تدریجی از عناصر پلتفرم

نرم‌افزار و سرویس‌های خط لولهٔ تحویل

برای ساخت خط لوله به نرم‌افزار یا سرویس میزبانی‌شده نیاز دارید. سیستم خط لوله باید چند کار انجام دهد:

  • راهی برای پیکربندی مراحل خط لوله بدهد.
  • مراحل را از اقدامات مختلف راه‌اندازی کند، از جمله رویدادهای خودکار و راه‌اندازهای دستی. ابزار باید روابط پیچیده‌تر مثل fan-in (یک مرحله با چند مرحلهٔ ورودی) و fan-out (یک مرحله با چند مرحلهٔ خروجی) را پشتیبانی کند.
  • هر اقدامی که برای مراحل نیاز دارید را پشتیبانی کند، از جمله اعمال کد زیرساخت و اجرای تست. باید بتوانید فعالیت‌های سفارشی بسازید نه مجموعهٔ ثابتی از فعالیت‌های پشتیبانی‌شده.
  • artifactها و خروجی‌های دیگر مراحل را مدیریت کند، از جمله انتقال از یک مرحله به بعدی.
  • به شما کمک کند نسخه‌ها و نمونه‌های مشخص کد، artifactها، خروجی‌ها و زیرساخت را ردیابی و همبسته کنید.

چند گزینه برای سیستم خط لوله وجود دارد:

سرور ساخت بسیاری از تیم‌ها از سرور ساختی مثل Jenkins، Team City، Bamboo یا GitHub Actions برای ساخت خط لوله استفاده می‌کنند. این‌ها اغلب «job-oriented» هستند نه «stream-oriented». طراحی اصلی ذاتاً نسخه‌های کد، artifactها و اجراها را در چند job همبسته نمی‌کند. بیشتر این محصولات پشتیبانی خط لوله را به‌عنوان لایه‌ای روی UI و پیکربندی اضافه کرده‌اند.

نرم‌افزار CD نرم‌افزار CD حول مفهوم خط لوله ساخته شده. هر مرحله را به‌عنوان بخشی از خط لوله تعریف می‌کنید و نسخه‌های کد و artifactها با خط لوله مرتبط‌اند تا بتوانید آن‌ها را به جلو و عقب ردیابی کنید. ابزارهای CD شامل GoCD،¹⁴ ConcourseCI،¹⁵ و BuildKite است.

سرویس‌های SaaS سرویس‌های میزبانی‌شدهٔ CI و CD شامل CircleCI، TravisCI، AppVeyor، Drone و BoxFuse است.

سرویس‌های پلتفرم ابری بیشتر فروشندگان ابری سرویس‌های CI و CD دارند، از جمله AWS CodeBuild (CI) و AWS CodePipeline (CD) و Azure Pipelines.

سرویس‌های مخزن کد منبع بسیاری از محصولات و فروشندگان مخزن کد منبع پشتیبانی CI اضافه کرده‌اند که می‌توانید برای ساخت خط لوله استفاده کنید. دو نمونهٔ برجسته GitHub Actions و GitLab CI و CD است.

محصولاتی که اینجا ذکر کردم همه با نرم‌افزار برنامه در ذهن طراحی شدند. می‌توانید بیشتر آن‌ها را برای ساخت خط لوله زیرساخت استفاده کنید، اگرچه ممکن است کار اضافی بخواهد.

چند محصول و سرویس برای زیرساخت به‌عنوان کد در حال ظهور است. این حوزه به‌سرعت در حال تغییر است، پس حدس می‌زنم آنچه دربارهٔ این ابزارها می‌گویم تا زمان خواندن شما قدیمی و ابزارهای جدیدتر را از قلم انداخته باشد. اما ارزش دارد ببینید الان چه وجود دارد، برای زمینهٔ ارزیابی ابزارها هنگام ظهور و تکامل:

  • Atlantis محصولی است که مدیریت pull request برای پروژه‌های Terraform و اجرای plan و apply برای یک نمونه را کمک می‌کند. تست اجرا نمی‌کند، اما می‌توانید برای ایجاد خط لولهٔ محدودی که بازبینی کد و تأیید تغییرات زیرساخت را مدیریت می‌کند استفاده کنید.
  • Terraform Cloud به‌سرعت در حال تکامل است. مختص Terraform است و قابلیت‌های بیشتری (مثل ثبت ماژول) نسبت به CI و خط لوله دارد. می‌توانید خط لولهٔ محدودی بسازید که plan و apply کد پروژه را به چند محیط اعمال کند. اما جز اعتبارسنجی سیاست با Sentinel خود HashiCorp تست اجرا نمی‌کند.
  • WeaveWorks محصولات و سرویس‌هایی برای مدیریت خوشه‌های Kubernetes می‌سازد. این‌ها شامل ابزارهایی برای مدیریت تحویل تغییرات به پیکربندی خوشه و همچنین برنامه‌ها با خط لوله‌های مبتنی بر شاخهٔ Git است، رویکردی که GitOps می‌نامد. حتی اگر از ابزارهای WeaveWorks استفاده نکنید، مدل در حال ظهوری است که ارزش نظارت دارد. کمی بیشتر در «GitOps» در صفحه ۳۵۱ به آن می‌پردازم.

¹⁴ برای افشای کامل، کارفرمای من ThoughtWorks، GoCD را ساخت. قبلاً محصول تجاری بود، اما اکنون کاملاً متن‌باز است.

¹⁵ با وجود نامش، ConcourseCI حول خط لوله‌ها طراحی شده نه jobهای CI.

تست در تولید

تست انتشارها و تغییرات قبل از اعمال به تولید تمرکز بزرگی در صنعت ماست. در یک مشتری، هشت گروه را شمردم که باید انتشارها را بازبینی و تأیید می‌کردند، جدا از تیم‌های فنی مختلفی که باید وظایفی برای نصب و پیکربندی بخش‌های مختلف سیستم انجام می‌دادند.¹⁶

با افزایش پیچیدگی و مقیاس سیستم‌ها، دامنهٔ ریسک‌هایی که عملاً می‌توانید خارج از تولید بررسی کنید کوچک می‌شود. این به معنای بی‌ارزش بودن تست قبل از تولید نیست. اما باور به اینکه تست پیش از انتشار می‌تواند ریسک‌های شما را به‌طور جامع پوشش دهد منجر به:

  • سرمایه‌گذاری بیش از حد در تست پیش از انتشار، خیلی فراتر از نقطهٔ بازدهی نزولی
  • سرمایه‌گذاری کم در تست در محیط تولید

عمیق‌تر دربارهٔ تست در تولید

برای بیشتر دربارهٔ تست در تولید، تماشای سخنرانی Charity Majors با عنوان «Yes, I Test in Production (And So Should You)» را توصیه می‌کنم که منبع کلیدی تفکر من در این موضوع است.

آنچه خارج از تولید قابل تکرار نیست

چند ویژگی محیط‌های تولید وجود دارد که واقع‌بینانه نمی‌توان خارج از تولید تکرار کرد:

داده سیستم تولید شما ممکن است مجموعه‌داده‌های بزرگ‌تری داشته باشد که نتوانید تکرار کنید و حتماً مقادیر و ترکیب‌های دادهٔ غیرمنتظره به‌لطف کاربران شما خواهد داشت.

کاربران به‌خاطر تعدادشان، کاربران شما در انجام کارهای عجیب از کارکنان تست شما خلاق‌ترند.

ترافیک اگر سیستم شما سطح غیرپیش‌پاافتاده‌ای از ترافیک دارد، نمی‌توانید تعداد و انواع فعالیت‌هایی که منظم تجربه می‌کند را تکرار کنید. تست خیس‌کردن یک هفته‌ای در مقایسه با یک سال اجرا در تولید پیش‌پاافتاده است.

همزمانی ابزارهای تست می‌توانند چند کاربر همزمان را شبیه‌سازی کنند، اما نمی‌توانند ترکیب‌های غیرمعمول کارهایی که کاربران شما همزمان انجام می‌دهند را تکرار کنند.

دو چالش ناشی از این ویژگی‌ها این است که ریسک‌هایی ایجاد می‌کنند که نمی‌توانید پیش‌بینی کنید و شرایطی که نمی‌توانید به‌اندازهٔ کافی خوب تکرار کنید تا جایی غیر از تولید تست کنید.

با اجرای تست در تولید، از شرایط موجود آنجا — مجموعه‌داده‌های طبیعی بزرگ و فعالیت همزمان غیرقابل‌پیش‌بینی — بهره می‌برید.

چرا جایی غیر از تولید تست کنیم؟

بدیهی است تست در تولید جایگزین تست تغییرات قبل از اعمال به تولید نیست. کمک می‌کند روشن باشید چه چیزهایی واقع‌بینانه می‌توانید (و باید!) از قبل تست کنید:

  • آیا کار می‌کند؟
  • آیا کد من اجرا می‌شود؟
  • آیا به روش‌هایی که می‌توانم پیش‌بینی کنم شکست می‌خورد؟
  • آیا به روش‌هایی که قبلاً شکست خورده شکست می‌خورد؟

تست تغییرات قبل از تولید unknownهای شناخته‌شده — چیزهایی که می‌دانید ممکن است اشتباه پیش بروند — را پوشش می‌دهد. تست تغییرات در تولید unknownهای ناشناخته — ریسک‌های غیرقابل‌پیش‌بینی‌تر — را پوشش می‌دهد.

مدیریت ریسک‌های تست در تولید

تست در تولید ریسک‌های جدید ایجاد می‌کند. چند چیز به مدیریت این ریسک‌ها کمک می‌کند:

نظارت نظارت مؤثر اطمینان می‌دهد می‌توانید مشکلات ناشی از تست‌ها را سریع تشخیص دهید تا متوقفشان کنید. این شامل تشخیص وقتی تست‌ها مشکل ایجاد می‌کنند است تا سریع متوقفشان کنید.

قابلیت مشاهده (Observability) قابلیت مشاهده دید به آنچه درون سیستم رخ می‌دهد در سطحی می‌دهد که به بررسی و رفع سریع مشکلات و همچنین بهبود کیفیت آنچه می‌توانید تست کنید کمک می‌کند.¹⁷

استقرار بدون قطعی توانایی استقرار و بازگشت سریع و یکپارچه ریسک خطاها را کاهش می‌دهد (به «تغییر زیرساخت زنده» در صفحه ۳۶۸ مراجعه کنید).

استقرار تدریجی اگر بتوانید نسخه‌های مختلف مؤلفه‌ها را همزمان اجرا کنید یا پیکربندی‌های مختلف برای مجموعه‌های مختلف کاربران داشته باشید، می‌توانید تغییرات را در شرایط تولید قبل از در معرض قرار دادن کاربران تست کنید (به «تغییر زیرساخت زنده» در صفحه ۳۶۸ مراجعه کنید).

مدیریت داده تست‌های تولید شما نباید تغییرات نامناسب به داده ایجاد کنند یا دادهٔ حساس را افشا کنند. می‌توانید رکوردهای دادهٔ تست، مثل کاربران و شماره‌های کارت اعتباری، نگه دارید که اقدامات دنیای واقعی را راه نیندازند.

مهندسی آشوب کاهش ریسک در محیط‌های تولید با تزریق عمدی انواع شناخته‌شدهٔ شکست برای اثبات کار درست سیستم‌های کاهش‌دهنده (به «مهندسی آشوب» در صفحه ۳۷۹ مراجعه کنید).

نظارت به‌عنوان تست

نظارت را می‌توان تست غیرفعال در تولید دید. تست واقعی نیست، چون اقدامی انجام نمی‌دهید و نتیجه را بررسی نمی‌کنید. در عوض فعالیت طبیعی کاربران را مشاهده و نتایج نامطلوب را می‌بینید.

نظارت باید بخشی از راهبرد تست باشد، چون بخشی از آمیختهٔ کارهایی است که برای مدیریت ریسک سیستم انجام می‌دهید.

جمع‌بندی

این فصل چالش‌ها و رویکردهای عمومی تست زیرساخت را بحث کرد. از ورود عمیق به موضوعات تست، کیفیت و مدیریت ریسک خودداری کردم. اگر در این حوزه‌ها تجربهٔ زیادی ندارید، این فصل شاید برای شروع کافی باشد. شما را تشویق می‌کنم بیشتر بخوانید، چون تست و QA بنیادی برای زیرساخت به‌عنوان کد است.


¹⁶ این گروه‌ها بودند: مدیریت تغییر، امنیت اطلاعات، مدیریت ریسک، مدیریت سرویس، مدیریت انتقال، تست یکپارچه‌سازی سیستم، پذیرش کاربر و هیئت حاکمیت فنی.

¹⁷ اگرچه اغلب با نظارت اشتباه گرفته می‌شود، قابلیت مشاهده دربارهٔ دادن راه‌هایی به افراد برای فهم آنچه درون سیستم می‌گذرد است. به «Introduction to Observability» از Honeycomb مراجعه کنید.

³ همان‌طور که در «از عصر آهن به عصر ابر» در صفحه ۲ توصیف شد.