اشکال زدایی تست های Flaky در CI

اشکال زدایی خطاهای تست CI می تواند ناامید کننده باشد. گاهی اوقات بازآفرینی شرایط شکست به صورت محلی غیرممکن است. ما اخیراً با مشکلی مواجه شدیم که برخی از آزمایشات ما در CI در محل کار من پوسته پوسته می شوند. تجربه اشکال زدایی آن باعث شد که بخواهم یک پست کوچک در مورد فرآیند بنویسم. امیدوارم بتواند به دیگران در مواجهه با این نوع مشکلات کمک کند 💪
زمینه
در شغل من، ما از Minitest به عنوان چارچوب آزمایشی برای برنامه Ruby on Rails خود استفاده می کنیم. ما اقدامات ساده GitHub برای اجرای یکپارچه سازی مداوم (CI) داریم. ما متوجه شدیم که تعدادی از تستها گهگاه در اجرای CI برای Minitest شکست میخوردند. فراوانی خرابی ها تقریباً 2/3 پاس به شکست به نظر می رسید. هر آزمون مردودی چند ویژگی مشترک داشت. همه آنها آزمایشی برای کنترلرهای API ما بودند، همه آنها برای a بودند #create
اقدام، و همه آنها از این الگوی کلی پیروی می کردند:
test "create new resource" do
assert_difference('Resource.count') do
post(
'route',
params: {
resource: { **attrs },
access_token: access_token
},
as: :json
)
end
end
هدف در اینجا این است که وقتی کاربر با ویژگیهای معتبر و نشانه دسترسی به آن مسیر پست میکند، یک منبع جدید در پایگاه داده آزمایشی باقی میماند. این assert_difference('Resource.count')
این را با جستجوی اینکه آیا تعداد 1 افزایش می یابد، بررسی می کند.
پیام شکست یک پیام ساده بود "Resource.count" didn't change by 1
.
روند
اصلیترین کاری که من سعی میکنم هنگام اشکالزدایی انجام دهم، ایجاد مجدد موقعیتی است که باعث ایجاد باگ میشود. اگر بتوانم شرایطی را تضمین کنم که به طور مداوم باگ ایجاد می کند، می توانم سر و صدا را کاهش دهم و روی آنچه که اشتباه می شود تمرکز کنم. گاهی اوقات این ساده است، اما در مورد مشکلات در CI، اغلب اینطور نیست.
ایده 1: شماره دانه آزمون CI ناموفق را بکشید
فریم ورکهای تست معمولا مکانیزمی را برای اجرای تستها به صورت تصادفی ارائه میدهند و Minitest نیز از این قاعده مستثنی نیست. این مفید است زیرا مهم است که آزمایشات عاری از عوارض جانبی باشند. اگر TestA فقط زمانی که بعد از TestB اجرا میشود تأیید میشود – این یک مشکل است و TestA چیزی را که ادعا میکند آزمایش میکند آزمایش نمیکند. اجراهای تصادفی به بررسی این نوع موقعیتها کمک میکنند. در کنار یک اجرای تصادفی، Minitest یک شناسه “seed” برای آن اجرای آزمایشی تولید می کند. این به توسعه دهندگان این امکان را می دهد تا در صورت تمایل مجدداً آزمایش ها را به ترتیب خاص اجرا کنند.
اولین ایده ما این بود که خروجی گزارش مربوط به اکشن GitHub شکست خورده را بررسی کنیم. این لاگ ها مقدار seed مورد استفاده در اجرای آزمایشی را نشان می دهند. اگر آن شناسه seed را به صورت محلی به Minitest منتقل کنیم، آیا تست ها در محیط توسعه دهنده ما شکست خواهند خورد؟
آنها همچنان در حال توسعه بودند … به ایده بعدی!
ایده 2: اکشن GitHub را مجبور کنید که هر بار با یک دانه خراب اجرا شود
بنابراین ما تأیید کردیم که این اشکال فقط در محیط CI وجود دارد. همکار من این ایده را داشت که برای اقدام آزمایشی GitHub ما، یک عدد seed ناموفق را بگیرد و آن را به دستور Minitest در گردش کار اضافه کند:run: bundle exec rails test --seed=${FAILING_SEED_NUMBER}
سپس میتوانیم بررسی کنیم که آیا آزمون در CI شکست میخورد و نه فقط گاهی اوقات. پس از چند بار اجرا، ما احساس اطمینان کردیم که آزمون به طور مداوم در CI ناموفق است. این ناخوشایند است که قادر به ایجاد مجدد مشکل به صورت محلی نیستیم. اما حداقل ما یک محیط ثابت برای شکست ها داشتیم. اکنون می توانیم بررسی موارد شکست خورده را آغاز کنیم.
اشکال زدایی چاپ
متأسفانه ما نمیتوانیم یک جلسه اشکالزدایی را برای آزمایشهایی که در CI اجرا میشوند وصل کنیم. بنابراین مجبور شدیم به دوست قدیمی خود بازگردیم: اشکال زدایی چاپ. چندتا اسپم زدیم p
و puts
اظهارات در موارد تست شکست خورده ما. مقادیر متغیرهای محلی مختلف چه بود؟ چه بود 'Resource.count'
عودت؟ متغیرهای نمونه پیکربندی شده در ما چه بودند setup
روش بازگشت؟ و بالاخره چه کرد response
شبیه؟
ما به سرعت متوجه شدیم که این به دلیل عدم تطابق در خروجی و ادعا با شکست مواجه نشده است. در عوض به دلیل خطای مطرح شده در طول آزمایش ناموفق بود:
ActiveRecord::RecordNotUnique (duplicate key value violates unique constraint). Key already exists.
پس از بررسی کد، به روش FactoryBot در تست خود مشکوک شدیم #setup
:
def setup
# access_token and user configuration
@resource = create(:resource, attr1: "attr1", attr2: "attr2", id: '1')
end
یک شناسه به صراحت در کارخانه تعیین شد. این شناسه برای اظهارات در موارد آزمایشی متفاوت برای کنترلر استفاده می شد. پس از کمی تحقیق، یافتن نمونه هایی از مشکل بودن این نوع چیزها چندان دشوار نبود (نگاه کنید به: اینجا). با اختصاص مستقیم یک شناسه، افزایش طبیعی کلیدهای اصلی را می توان حذف کرد. بنابراین تصمیم گرفتیم آن آرگومان id را حذف کنیم و ادعاهایی را که به شناسه متکی بودند برای جستجو تغییر دهیم @resource.id
بجای. ما این تغییرات را برای هر مورد آزمایشی که از این الگو استفاده می کرد، ایجاد کردیم.
سپس کد را با فرمان تست CI که هنوز تنظیم شده است برای اجرای دانه شکست خورده به سمت بالا فشار دادیم و آن را قبول کردیم! سپس به اجرای اکشن GitHub بدون یک دانه مشخص بازگشتیم تا به ترتیب تصادفی به حالت اجرا برگردد. پس از چندین اجرا موفق متوالی، ما مطمئن بودیم که مشکل را حل کرده ایم!
نتیجه
من تاکنون چند بار از طریق شغلم و در پروژه های منبع باز با این نوع مسائل CI مواجه شده ام. زمانی که نتوانید شکست های توسعه را بازسازی کنید، می تواند مشکل باشد. اما کمی صبر و یک فرآیند اشکال زدایی سازمان یافته می تواند مشکل را سریع حل کند
بیشتر خواندن:
من از این دو مقاله آزمایشی ورقه ورقه توسط جیسون سوئت بهره زیادی گرفتم: