مقدمه
تست کردن یک شیوه رایج برای اطمینان حاصل کردن از اینکه منطق کد به راحتی در طول توسعه و بازسازی خراب نمیشود. اجرای تست به عنوان بخشی از زیرساخت CI ضروری است، به ویژه با توجه به اینکه یک کدپایه بزرگ توسط بسیاری از مهندسین توسعه داده شده است. با این حال، هر چه تستها بیشتر میشوند، مدت زمان اجرای آنها بیشتر میشود. در زمینه توسعه iOS، زمان اجرای مجموعه تست کلی میتواند به شدت تحت تاثیر عدد بیشتر تستهای نوشته شده قرار بگیرد. اجرای پایپلاین CI قبل از ادغام، هزینه بیشتری به ما خواهد داشت. بنابراین، کاهش زمان اجرای تست یک اپیک بلندمدت است که برای ساخت زیرساخت CI خوب، باید با آن روبرو شویم.
علاوه بر تقسیم تست ها به زیرمجموعه ها و اجرای هر یک از آنها در یک کار CI، میتوانیم از قابلیت تست موازی Xcode برای دستیابی به موازیسازی در یک کار CI تنها استفاده کنیم. با این حال، به دلیل پیاده سازی های متناهی، محدودیت هایی وجود دارد که از کارایی تست موازی جلوگیری می کند. یک محدودیت که متوجه شدیم این است که تست های هم کلاس Swift روی یک شبیه ساز اجرا می شوند. در این پست، به بررسی این محدودیت به تفصیل می پردازیم و راه حلی برای غلبه بر آن معرفی می کنیم.
زمینه
تست موازی Xcode
قابلیت تست موازی با انتشار Xcode 10 عرضه شد. این پشتیبانی به ما امکان تنظیم آسان را برای تنظیم تست فراهم می کند:
- نیازی به نگرانی در مورد چگونگی تقسیم یک مجموعه تست داده نمی شود. تعداد کارگران (یعنی اجراکننده های موازی / نمونه ها) قابل پیکربندی است. می توانیم این مقدار را در xcodebuildCLI از طریق گزینه -parallel-testing-worker-count گذاشت. Xcode از کلون کردن مراقبت می کند و به موقع شبیه سازها را راه اندازی می کند.
با این حال، منطق توزیع در زیر ساخت عملکرد سیاه جعبه است. در واقع نمی دانیم چگونه تست ها به هر کارگر یا شبیه ساز اختصاص داده می شوند و به چه ترتیبی.
مهم است که حتی بدون پشتیبانی از تست موازی Xcode ، می توانیم با اجرای زیرمجموعه هایی از تست ها در فرآیند های فرزند مختلف بهبودهای مشابهی را داشته باشیم. اما نیاز به تلاش بیشتر برای ارسال تست ها به هر فرآیند فرزند به روشی کارآمد و برخورد مناسب با خروجی هر فرآیند تست وجود دارد.
تراز زمان تست
به طور کلی، یک سیستم اجرای موازی اگر هر وظیفه موازی خود را در مدت زمانی تقریباً یکسان اجرا کند و در زمان تقریباً یکسانی به پایان برسد، در بهترین کارائی خود است.
اگر زمان صرف شده برای هر وظیفه موازی از هم متفاوت باشد، زمان بیشتری از زمان مورد انتظار برای اجرای تمام وظایف لازم خواهد شد. به عنوان مثال، در تصویر زیر، به سیستم سمت چپ ۱۳ دقیقه زمان لازم است تا ۳ وظیفه را به پایان برساند. در حالی که سیستم سمت راست تنها ۱۰.۵ دقیقه زمان لازم دارد تا همان ۳ وظیفه را به پایان برساند.
فرض کنید N کارگر وجود دارد. کارگر i وظایف خود را در t ثانیه / دقیقه انجام می دهد. در پلات سمت چپ، t1 = ۱۰ دقیقه، t2 = ۷ دقیقه، t3 = ۱۳ دقیقه است.
ما معیار عدم تعادل زمان تست را به عنوان تفاوت بین زمان پایان کمینه و زمان پایان بیشینه تعریف می کنیم:
max(ti) - min(ti)
برای مثال بالا، عدم تعادل زمان تست برابر است با ۱۳ دقیقه - ۷ دقیقه = ۶ دقیقه.
عوامل موثر در عدم تعادل زمان تست
چندین عامل باعث عدم تعادل زمان تست می شوند. دو مهمترین عامل مؤثر عبارتند از:
- تست ها در زمان اجرا متفاوت است.تست های هم کلاس روی یک شبیه ساز اجرا می شوند.
یک مثال از عامل اول این است که در پروژه ما، حدود ۵۰٪ از تست ها در بازه ۲۰-۴۰ ثانیه اجرا می شوند. برخی از تست ها کمتر از ۱۵ ثانیه زمان اجرا دارند در حالی که برخی دیگر تا ۲ دقیقه طول میکشند. گاهی اوقات قطعاً نمی توان از اجرای طولانی مدت تست ها جلوگیری کرد زیرا این تست ها معمولاً با چندین جریان تعامل دارند که قابل تقسیم نیستند. اگر اینگونه تست ها در آخر اجرا شوند، عدم تعادل زمان تست ممکن است افزایش یابد.
با این حال، این مسئله به طور کلی مهم نیست زیرا تست های با مدت زمان اجرای طولانی همیشه آخرین تست ها را اجرا نمی کنند.
درباره عامل دوم، هیچ سند رسمی از طرف Apple وجود ندارد که به صراحت این محدودیت را بیان کند. زمانی که Apple برای اولین بار پشتیبانی از تست موازی را در Xcode 10 معرفی کرد، فقط اشاره کردند که کلاس های تست بین فرایندهای اجرایی کارگر توزیع می شوند:
«توزیع تست موازی با توزیع کلاس های تست در هدف بین چندین فرایند اجرایی. از گزینه تست برای دیدن اینکه کلاس های تست شما چگونه موازی شده اند استفاده کنید. شما یک ورودی در لاگ برای هر فرآیند اجرایی که راه اندازی شده است می بینید، و در زیر هر فرآیند اجرایی لیست کلاس هایی که اجرا شده است را می بینید.»
به عنوان مثال، ما یک کلاس تست به نام JobFlowTests داریم که شامل پنج تست است و یک کلاس تست به نام TutorialTests که فقط یک تست واحد دارد.
کلاس نهایی JobFlowTests: BaseXCTestCase {بررسی فلان{...}بررسی فلان{...}بررسی فلان{...}بررسی فلان{...}بررسی فلان{...}}}...کلاس نهایی TutorialTests: BaseXCTestCase {بررسی فلان{...}}}...
زمان اجرای دو تست با دو شبیهساز در حال اجرا به صورت موازی، از منظر واقعی مانند تصویر سمت چپ است، اما ایده آل باید مانند تصویر سمت راست عمل کند.
غوطه ور شدن در تست موازی Xcode
رمزگشایی لاگ زمانبندی Xcode
همانطور که قبلاً اشاره شد، Xcode تست ها را به شبیه سازها / کارگرها بصورت مشخصی توزیع می کند. با این حال، با نگاه کردن به لاگ زمانبندی تولید شده هنگام اجرای تست ها، می توانیم بفهمیم چگونه تست موازی Xcode کار می کند.
هنگام اجرای تست های UI از طریق دستور xcodebuild:
$ xcodebuild-workspaceDriver/Driver.xcworkspace\-schemeDriver\-configurationDebug\-sdk'iphonesimulator'\-destination'platform=iOS Simulator,id=EEE06943-7D7B-4E76-A3E0-B9A5C1470DBE'\-derivedDataPath'./DerivedData'\-parallel-testing-enabledYES\-parallel-testing-worker-count2\-only-testing:DriverUITests/JobFlowTests\# 👈👈👈👈👈-only-testing:DriverUITests/TutorialTests\test-without-building
لاگ را می توان در پوشه *.xcresult در زیر مسیر DerivedData/Logs/Test یافت. به عنوان مثال:DerivedData/Logs/Test/Test-Driver-2019.11.04\_23-31-34-+0800.xcresult/1\_Test/Diagnostics/DriverUITests-144D9549-FD53-437B-BE97-8A288855E259/scheduling.log
2019-11-05 03:55:00 +0000: Received worker from worker provider: 0x7fe6a684c4e0 [0: Clone 1 of DaxIOS-XC10-1-iP7-1 (3D082B53-3159-4004-A798-EA5553C873C4)] 2019-11-05 03:55:13 +0000: Worker 0x7fe6a684c4e0 [4985: Clone 1 of DaxIOS-XC10-1-iP7-1 (3D082B53-3159-4004-A798-EA5553C873C4)] finished bootstrapping 2019-11-05 03:55:13 +0000: Parallelization enabled; test execution driven by the IDE 2019-11-05 03:55:13 +0000: Skipping test class discovery 2019-11-05 03:55:13 +0000: Executing tests {( # 👈👈👈👈👈 DriverUITests/JobFlowTests, DriverUITests/TutorialTests )}; skipping tests {( )} 2019-11-05 03:55:13 +0000: Load balancer requested an additional worker 2019-11-05 03:55:13 +0000: Dispatching tests {( # 👈👈👈👈👈 DriverUITests/JobFlowTests )} to worker: 0x7fe6a684c4e0 [4985: Clone 1 of DaxIOS-XC10-1-iP7-1 (3D082B53-3159-4004-A798-EA5553C873C4)] 2019-11-05 03:55:13 +0000: Received worker from worker provider: 0x7fe6a1582e40 [0: Clone 2 of DaxIOS-XC10-1-iP7-1 (F640C2F1-59A7-4448-B700-7381949B5D00)] 2019-11-05 03:55:39 +0000: Dispatching tests {( # 👈👈👈👈👈 DriverUITests/TutorialTests )} to worker: 0x7fe6a684c4e0 [4985: Clone 1 of DaxIOS-XC10-1-iP7-1 (3D082B53-3159-4004-A798-EA5553C873C4)] ...
نگاه به لاگ زمانبندی زیر، ما میدانیم که هنگامی که یک کلاس تست به یک کارگر / شبیهساز ارسال یا توزیع میشود، همه تست های آن کلاس در آن شبیه ساز اجرا خواهند شد.
2019-11-05 03:55:39 +0000: Dispatching tests {( DriverUITests/TutorialTests )} to worker: 0x7fe6a684c4e0 [4985: Clone 1 of DaxIOS-XC10-1-iP7-1 (3D082B53-3159-4004-A798-EA5553C873C4)]
حتی در صورتی که یک سوئیت تست را سفارشیسازی کنیم (با پیچیدن بعضی از متدها یا متغیرهای کلاس XCTestSuite)، تا زمانی که تست ها به یک کارگر خاصی ارسال شوند، سوئیت تست ساخته شده تنها پس از اجرای تست ها به کار میرود.
بنابراین، هر هوکی برای عبور از این محدودیت باید از زودتر انجام شود.
گذاشتن آرگومان -only-testing در دستور xcodebuild
حالا، تست ها را (به جای کلاس های تست) به آرگومان -only-testing منتقل می کنیم.
$ xcodebuild -workspace Driver/Driver.xcworkspace \ # ... -only-testing:DriverUITests/JobFlowTests/testJobIgnoreByTimer \ -only-testing:DriverUITests/JobFlowTests/testRecoverFlow \ -only-testing:DriverUITests/JobFlowTests/testJobIgnoreByDax \ -only-testing:DriverUITests/JobFlowTests/testHappyFlow \ -only-testing:DriverUITests/JobFlowTests/testForceClearBooking \ -only-testing:DriverUITests/TutorialTests/testOnboardingFlow