Testcontainers؛ آزمایش WAR واقعی در Docker با Azul Payara Micro

چرا مهم است

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

قبل از خواندن، این‌ها را بدان۵ مفهوم
runtime

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

بیشتر: runtime

Mock

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

در حال نگارش: Mock

Integration Test

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

در حال نگارش: Integration Test

Wait Strategy

قاعده‌ای برای تشخیص آماده‌شدن یک سرویس پیش از آغاز آزمایش؛ مثلاً مشاهده پیام مشخصی در log یا دریافت پاسخ از شبکه.

در حال نگارش: Wait Strategy

Singleton Pattern

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

در حال نگارش: Singleton Pattern

یک فیلد خالی librarianID در فرم امانت، به‌جای null به‌صورت رشته خالی ذخیره می‌شد و بررسی ذخیره‌شدن اطلاعات را در صفحه‌ای دیگر به‌هم می‌زد. این bug حاصل تعامل اتصال مقدار در JSF با callback مربوط به @PrePersist بود؛ بنابراین آزمایش EntityManager با Mock آن را آشکار نمی‌کرد. مشکل زمانی ظاهر می‌شد که مرورگر واقعی، فرم واقعی را به برنامه deploy‌شده می‌فرستاد.

مطلب با این مثال، کاربرد Integration Test با Testcontainers را توضیح می‌دهد: اجرای همان WAR ساخته‌شده با Maven روی یک نمونه واقعی Azul Payara Micro و آزمایش آن از بیرون. Testcontainers در طول اجرای آزمایش، container موقت می‌سازد و پس از پایان آن را حذف می‌کند. آزمایش از طریق شبکه و port واقعی با برنامه ارتباط می‌گیرد. Ryuk نیز برای پاک‌سازی container‌ها حتی در صورت توقف JVM به کار می‌رود، هرچند ممکن است در بعضی محیط‌های CI غیرفعال باشد.

در پروژه نمونه، کلاس PayaraMicroContainer از GenericContainer ارث می‌برد، docker image مربوط به payara/micro را با نسخه مورد استفاده پروژه دریافت می‌کند و WAR تازه ساخته‌شده را داخل آن کپی می‌کند. Wait Strategy بر پیام آماده‌شدن خود Payara تکیه دارد. ویژگی‌های payara.version و war.path را Maven Failsafe فراهم می‌کند؛ نبودن آن‌ها باعث خطای صریح می‌شود. همچنین war.path به خروجی مرحله package اشاره دارد تا نسخه قدیمی WAR آزمایش نشود.

نکته دیگر، استفاده از Singleton Pattern برای container مشترک است. پروژه آن را در static initializer کلاس پایه AbstractContainerIT راه‌اندازی می‌کند. دلیل این انتخاب، چرخه عمر افزونه JUnit 5 است: اگر فیلد static دارای @Container در کلاس پایه مشترک قرار بگیرد، پایان اولین کلاس آزمایش می‌تواند container را متوقف کند و کلاس‌های بعدی به container خاموش متصل شوند.

این روش مشکلات بسته‌بندی، dependency مفقود و رفتار مخصوص runtime را آشکار می‌کند، اما هزینه دارد: نیاز به محیط اجرای container،؜ راه‌اندازی در مقیاس ثانیه و احتمال شکست‌های ناشی از شبکه یا دیر آماده‌شدن. همچنین نگاه آن بیرونی است؛ پاسخ HTTP و تعامل مرورگر را می‌آزماید، اما دسترسی مستقیم به وضعیت مدیریت‌شده داخل container نمی‌دهد. پروژه برای همین محدودیت، مسیر Arquillian را هم حفظ کرده است.

نکته کلیدی

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

خواندن مقاله اصلی (در زبانه‌ی جدید باز می‌شود)
برچسب‌ها (۴)