اختبارات قشارية

ما هو أكثر إزعاجًا من "الاختبار الأحمر"؟ الاختبار إما أخضر أو ​​أحمر ، وليس من الواضح لماذا. في مؤتمر Heisenbug 2017 في موسكو ، تحدث أندريه سولنتسيف (Codeborne) عن سبب ظهورها وكيفية تقليل عددها. الأمثلة في تقريره هي التي تشعر بالألم مباشرة في الجلد عندما تصطدم بهم. والنصائح مفيدة - ومن المفيد التعرف على كل من المختبرين والمطورين. هناك شيء غير متوقع: يمكنك معرفة كيف يمكنك أحيانًا اكتشاف مشكلة إذا انفصلت عن الشاشة ولعب مكعبات مع ابنتك.

ونتيجة لذلك ، أعرب الجمهور عن تقديره للتقرير ، وقررنا ليس فقط نشر الفيديو ، ولكن أيضًا عمل نسخة نصية من التقرير لحبر.



في رأيي ، الاختبارات غير المستقرة هي الموضوع الأكثر صلة في عالم الأتمتة. لأن السؤال "ما الذي يجري في العالم ، كيف حالك مع الأتمتة؟" كل الجواب: "لا يوجد استقرار! اختباراتنا تنخفض بشكل دوري. "

لقد أجريت اختبارًا في مكانك ، فهو أخضر ، ويومان أخضران آخران ، ثم سقطت فجأة على جنكينز. حاولت تكرارها ، ابدأها ، وهي خضراء مرة أخرى. وفي النهاية ، أنت لا تعرف أبدًا: هل هو خطأ أم أنه مجرد اختبار جلوكان؟ وفي كل مرة تحتاج إلى الفهم.

غالبًا ، بعد إطلاق الاختبارات ليلًا على Jenkins ، يرى المختبر أولاً "انخفض 30 اختبارًا ، تحتاج إلى الدراسة" ، ولكن الجميع يعرف ما سيحدث بعد ذلك ...



لقد خمنت ، بالطبع ، ما هي الكلمة غير اللائقة المقنعة: "سأعيد التشغيل". مثل ، "اليوم لا يوجد تردد في الفهم ..." هكذا يحدث عادة ، وهي كارثة حقيقية.

لا توجد إحصائيات دقيقة ، لكني سمعت في كثير من الأحيان من أشخاص مختلفين أن لديهم حوالي 30 ٪ من الاختبارات - غير مستقر. تقريبًا ، يطلقون ألفًا ، 300 منها حمراء بشكل دوري ، ثم يتفقدون بأيديهم ما إذا كانوا قد سقطوا بالفعل.

نشرت Google مقالة بضع سنوات: تقول أن لديهم 1.5 ٪ من الاختبارات غير المستقرة ، وتخبر كيف يقاتلون من أجل تقليل عددهم. يمكنني التباهي بالقول إن مشروعي في Codeborne يبلغ الآن 0.1٪. ولكن في الواقع ، كل هذا سيء ، حتى 0.1٪. لماذا؟

خذ 1.5٪ ، هذا الرقم يبدو صغيراً ، لكن ماذا يعني عملياً؟ لنفترض أن هناك ألف اختبار في المشروع. قد يعني هذا أن 15 اختبارًا سقطت في بناء واحد ، ثم 12 اختبارًا ، ثم 18.

وحتى جزء واحد من المليون (0.1٪) لا يزال سيئًا. لنفترض أن لدينا 1000 اختبار ، ثم 0.1٪ تعني أن بناءًا واحدًا من أصل عشرة يقع مع 1-2 اختبارات حمراء. هذه هي الصورة الحقيقية من جنكينز لدينا ، وتبين: مع تشغيل واحد ، سقط اختبار قشاري ، وآخر بدأ آخر.



اتضح أنه ليس لدينا يوم بدون بناء أحمر. نظرًا لوجود الكثير من اللون الأخضر ، يبدو أن كل شيء على ما يرام ، ولكن يحق للعميل أن يسألنا: "يا شباب ، نحن ندفع لك المال ، وتزودنا دائمًا باللون الأحمر!" ماذا تفعل؟ "

سأكون غير راضٍ عن مكان العميل ، وشرح "بشكل عام ، هذا أمر طبيعي في الصناعة ، كل شيء أحمر للجميع" ليس جيدًا ، أليس كذلك؟ لذلك ، في رأيي ، هذه مشكلة ملحة للغاية ، دعونا نفهم معًا كيفية التعامل معها.

الخطة هي:

  1. مجموعتي من الاختبارات غير المستقرة (من ممارستي ، حالات حقيقية تمامًا ، قصص بوليسية معقدة ومثيرة للاهتمام)
  2. أسباب عدم الاستقرار (استغرق بعضها سنوات للبحث)
  3. كيف تتعامل معهم؟ (نأمل أن يكون هذا هو الجزء الأكثر فائدة)

لذا ، دعنا نبدأ بمجموعتي ، التي أقدرها كثيرًا: لقد كلفني العديد من ساعات الليل من الحياة وتصحيح الأخطاء. لنبدأ بمثال بسيط.

مثال 1: كلاسيكي


للبذور - نص السيلينيوم الكلاسيكي:

driver.navigate().to("https://www.google.com/"); driver.findElement(By.name("q")).sendKeys("selenide"); driver.findElement(By.name("btnK")).click(); assertEquals(9, driver.findElements(By.cssSelector("#ires .g")).size()); 

  1. نفتح WebDriver ؛
  2. أوجد العنصر q ، قُد في الكلمة للبحث هناك ؛
  3. ابحث عن عنصر "زر" وانقر ؛
  4. تأكد من أن الإجابة هي تسع نتائج.

السؤال: أي خط يمكن أن ينكسر هنا؟

هذا صحيح ، كلنا نعلم جيدًا أن أي! يمكن كسر أي خط لأسباب مختلفة تمامًا:

السطر الأول هو الإنترنت البطيء ، وتعطلت الخدمة ، ولم يهيئ المشرفون شيئًا ما.

السطر الثاني - لم يكن للعنصر وقت لتقديمه إذا تم رسمه ديناميكيًا.
ما يمكن أن ينكسر في السطر الثالث؟ هنا لم يكن متوقعًا بالنسبة لي: لقد كتبت هذا الاختبار للمؤتمر ، وأدارته محليًا ، وسقط على السطر الثالث مع هذا الخطأ:



هذا يعني أن العنصر في هذه المرحلة غير قابل للنقر. قد يبدو نموذج Google الأساسي البسيط. اتضح أن السر هو أننا ضربنا الكلمة في السطر الثاني ، وبينما كنا ندخلها ، عثرت Google بالفعل على النتائج الأولى ، وأظهرت النتائج القليلة الأولى في مثل هذه النافذة المنبثقة ، وأغلقوا الزر التالي. وهذا لا يحدث في جميع المتصفحات وليس دائمًا. حدث هذا لي مع هذا النص مرة واحدة من أصل خمسة.

قد يقع السطر الرابع ، على سبيل المثال ، لأن هذا العنصر يتم رسمه ديناميكيًا ولم يكن لديه وقت للرسم.

في هذا المثال ، أود أن أقول ، من تجربتي ، 90٪ من الاختبارات غير المستقرة تستند إلى نفس الأسباب:

  • سرعة طلب Ajax: في بعض الأحيان تعمل بشكل أبطأ وأحيانًا أسرع ؛
  • ترتيب طلبات Ajax ؛
  • شبيبة السرعة.

لحسن الحظ ، هناك علاج لهذه الأسباب! سيلينيد يحل هذه المشاكل. كيف تقرر؟ نعيد كتابة اختبار Google على Selenide - يبدو كل شيء تقريبًا ، يتم استخدام علامات $ فقط:

 @Test public void userCanLogin() { open(“http://localhost:8080/login”); $(By.name(“username”).setValue(“john”); $(“#submit”).click(); $(“.menu”).shouldHave(text(“Hello, John!”)); } 

يمر هذا الاختبار دائما. نظرًا لحقيقة أن أساليب setValue () ، فانقر على () و shouldHave () تتسم بالذكاء: إذا لم يكن هناك وقت للطلاء عليه ، فينتظرون قليلاً ويحاولون مرة أخرى (وهذا ما يسمى "التوقعات الذكية").

إذا نظرت بمزيد من التفصيل ، فإن كل هذه الطرق * يجب أن تكون ذكية:



يمكنهم الانتظار إذا لزم الأمر. بشكل افتراضي ، ينتظرون ما يصل إلى 4 ثوانٍ ، وهذه المهلة ، بالطبع ، قابلة للتكوين ، يمكنك تحديد أي وقت آخر. على سبيل المثال ، مثل هذا: mvn -Dselenide.timeout = 8000.

مثال 2: nbob


لذا ، يتم حل 90 ٪ من مشاكل الاختبارات غير المستقرة باستخدام Selenide. لكن 10٪ من الحالات الأكثر تعقيدًا تبقى لأسباب معقدة ومربكة. أريد أن أتحدث اليوم عنهم بالتحديد لأنها "منطقة رمادية". دعني أعطيك مثالاً واحداً: اختبار غير مستقر ، صادفته على الفور في مشروع جديد. للوهلة الأولى ، لا يمكن أن يحدث هذا ببساطة ، ولكن هذا شيء مثير للاهتمام.

اختبرنا تطبيق لوحة المفاتيح لتسجيل الدخول في الأكشاك. أراد الاختبار تسجيل الدخول باسم المستخدم "bob" ، أي إدخال ثلاثة أحرف في حقل "تسجيل الدخول": bob. للقيام بذلك ، تم استخدام الأزرار الموجودة على الشاشة. كقاعدة ، نجح هذا ، ولكن في بعض الأحيان تعطل الاختبار ، وظلت القيمة "nbob" في حقل "تسجيل الدخول":



بطبيعة الحال ، أنت تكافح من أجل البحث عن طريق الكود حيث يمكننا كتابة "nbob" - ولكن هذا المشروع ليس كله على الإطلاق (لا في قاعدة البيانات ، ولا في الكود ، ولا حتى في ملفات Excel). كيف هذا ممكن؟

ننظر إلى الشفرة بمزيد من التفصيل - يبدو أن كل شيء بسيط ولا توجد ألغاز:

 @Test public void loginKiosk() { open(“http://localhost:9000/kiosk”); $(“body”).click(); $(By.name(“username”)).sendKeys(“bob”); $(“#login”).click(); } 

بدأنا في إجراء المزيد من النقاش ، والتقدم خطوة بخطوة ، وبهذه الطريقة تمكنا من فهم: يظهر هذا الخطأ أحيانًا بعد السطر $ ("body"). انقر (). أي في هذه الخطوة ، تظهر "n" في حقل "تسجيل الدخول" ، ثم تتم إضافة "bob" في الخطوات اللاحقة. من خمن بالفعل من أين تأتي "n"؟

حدث أن الحرف N كان في منتصف الشاشة ، ووظيفة click () على الأقل في Chrome تعمل على هذا النحو: إنها تحسب الإحداثيات المركزية لعنصر وتنقر عليه. نظرًا لأن الجسم عنصر كبير ، فقد نقرت في منتصف الشاشة بأكملها.



وهذا لم يقع دائمًا. من يعرف لماذا؟ في الواقع ، أنا نفسي لا أعرف بالكامل. ربما يرجع ذلك إلى حقيقة أن نافذة المتصفح تفتح طوال الوقت بأحجام مختلفة ، وهذا لا يقع دائمًا في الحرف N.

ربما لديك سؤال: لماذا حقق شخص ما $ ("body"). انقر ()؟ لا أعرف أيضًا حتى النهاية ، لكنني أفترض إزالة التركيز من الميدان. هناك مثل هذه المشكلة في السيلينيوم التي تنقر فوق () ، لكن إلغاء النقر () ليس كذلك. إذا كان هناك تركيز في المجال ، فلا يمكن إزالته من هناك ، يمكنك فقط النقر على أي عنصر آخر. وبما أنه لم تكن هناك عناصر أخرى معقولة ، نقروا على الجسم ، وحصلوا على مثل هذا التأثير.

ومن هنا المعنوي: لا تدخل أي شيء يدخل في <body>. وبعبارة أخرى ، لا تحتاج إلى القيام بأي حركات إضافية في حالة من الذعر. في الواقع ، يحدث هذا غالبًا: نظرًا لأنني أتعامل مع Selenide ، غالبًا ما أتلقى شكاوى "شيء ما لا يعمل" ، ثم اتضح أنه في مكان ما في طرق الإعداد كان هناك 15 سطرًا إضافيًا لا يقدم أي شيء مفيد ويتدخل . لا حاجة إلى إثارة الجلبة وإدخالها على أي حال في اختبارات مثل "فجأة ستكون أكثر موثوقية".

نتيجة لذلك ، قمنا بتوسيع قائمة أسباب الاختبارات غير المستقرة:

  • سرعة طلب أياكس ؛
  • ترتيب طلبات Ajax ؛
  • شبيبة السرعة.
  • حجم نافذة المتصفح ؛
  • الغرور!

وفي نفس الوقت ، توصيتي هي: لا تقم بتشغيل الاختبارات بأقصى قدر (أي لا تفتح المتصفح في نافذة كاملة). كقاعدة عامة ، يقوم الجميع بذلك ، وفي Selenide كان ذلك افتراضيًا (أو لا يزال). بدلاً من ذلك ، أنصحك بتشغيل متصفح دائمًا بدقة شاشة محددة بدقة ، لأنه يتم استبعاد هذا العامل العشوائي. أنصحك بتعيين الحد الأدنى للحجم الذي يدعمه تطبيقك وفقًا للمواصفات.

مثال 3: حسابات فانتوم


مثال مثير للاهتمام في أن كل شيء يمكن أن يتزامن فقط على الفور.

كان هناك اختبار فحص أنه يجب أن يكون هناك 5 حسابات على هذه الشاشة.



كقاعدة ، كانت خضراء ، ولكن في بعض الأحيان لم يكن واضحًا تحت أي ظروف سقطت وقالت أنه لم يكن هناك خمسة ، ولكن ست تهم على الشاشة.

لقد بدأت في البحث من أين تأتي الفاتورة الإضافية. غير مفهومة على الإطلاق. نشأ السؤال: ربما يكون لدينا اختبار آخر ، والذي خلال الاختبار يخلق حسابًا جديدًا؟ اتضح أن نعم ، هناك مثل LoansTest. وبينه وبين اختبارات الحسابات المتساقطة (التي تتوقع خمسة حسابات) قد يكون هناك مليون من بعض الاختبارات الأخرى.

نحن نحاول أن نفهم كيف يكون الأمر كذلك: ألا يجب على LoansTest ، الذي ينشئ الحساب ، أن يحذفه في النهاية؟ نحن ننظر إلى الكود الخاص به - نعم ، يجب ، في النهاية هناك وظيفة بعد ذلك. ثم ، من الناحية النظرية ، يجب أن يكون كل شيء على ما يرام ، ما هي المشكلة؟

ربما يزيله الاختبار ، لكنه يبقى مخبأًا في مكان ما؟ نحن ننظر إلى رمز الإنتاج الذي يحمّل الحسابات - فهو يحتوي بالفعل على التعليق التوضيحيCacheFor ، ويخزن الحسابات مؤقتًا لمدة خمس دقائق.

السؤال الذي يطرح نفسه: ولكن ألا ينبغي للاختبار مسح ذاكرة التخزين المؤقت هذه؟ سيكون من المنطقي ألا يمكن أن يكون هناك دعامة كهذه؟ نحن ننظر إلى الكود الخاص به - نعم ، يقوم بالفعل بمسح ذاكرة التخزين المؤقت قبل كل اختبار. ما الأمر؟ لقد فقدت بالفعل ، لأن الفرضيات قد انتهت: تم حذف الكائن ، ومحو ذاكرة التخزين المؤقت ، وعصا الأشجار ، وماذا يمكن أن يكون مشكلة أخرى؟ ثم بدأ في تسلق الرمز ، واستغرق الأمر بعض الوقت ، وربما حتى بضعة أيام. حتى نظرت أخيرًا إلى هذه الفئة والطبقة الفائقة ، ووجدت شيئًا واحدًا مريبًا هناك:



شخص ما لاحظه بالفعل ، أليس كذلك؟ هذا صحيح: في الطفل وفي الفصل الأصل هناك طريقة بنفس الاسم ، ولا تسمى سوبر.

وفي جافا ، من السهل جدًا القيام بذلك: تضغط على Alt + Enter أو Ctrl + Insert في IntelliJ IDEA أو Eclipse ، وتقوم افتراضيًا بإنشاء طريقة setUp () ، ولا تلاحظ أنها تلغي الطريقة في الطبقة الفائقة. أي أنه لم يتم استدعاء ذاكرة التخزين المؤقت. عندما رأيت هذا ، شعرت بالغضب الشديد. إنه لمن دواعي سروري الآن.

ومن هنا المعنوي:

  1. في الاختبارات ، من المهم جدًا مراقبة الرمز النظيف. إذا كان الجميع في كود الإنتاج منتبهًا لذلك ، فإنهم يجرون مراجعة الكود ، ثم في الاختبارات - ليس دائمًا.
  2. إذا تم التحقق من رمز الإنتاج عن طريق الاختبارات ، فمن سيختبر الاختبارات؟ لذلك ، من المهم بشكل خاص استخدام الشيكات في IDE.

بعد هذا الحادث ، وجدت في IDEA مثل هذا الفحص ، والذي تم إيقاف تشغيله افتراضيًا ، والذي يتحقق: إذا تم تجاوز الطريقة في مكان ما ، ولكن لا يوجد تعليق توضيحي @ Overrid ، فهذا يشير إلى هذا على أنه خطأ. الآن أقوم دائمًا بتحديد هذا المربع بشكل هستيري.

دعونا نلخص مرة أخرى: كيف حدث هذا ، لماذا لم يفشل الاختبار دائمًا؟ أولاً ، اعتمدت على ترتيب هذين الاختبارين ؛ يتم تشغيلهما دائمًا بترتيب عشوائي. اعتمد اختبار آخر على مقدار الوقت الذي يمر بينهما. يتم تخزين الحسابات في ذاكرة التخزين المؤقت لمدة خمس دقائق ، إذا مرت أكثر ، كان الاختبار أخضر ، وإذا كان أقل ، سقط ، ونادراً ما حدث هذا.

نقوم بتوسيع قائمة أسباب عدم استقرار الاختبارات:

  • سرعة طلب أياكس ؛
  • ترتيب طلبات Ajax ؛
  • شبيبة السرعة.
  • حجم نافذة المتصفح ؛
  • ذاكرة التخزين المؤقت للتطبيق ؛
  • البيانات من الاختبارات السابقة ؛
  • الوقت.

مثال 4: زمن جافا


كان هناك اختبار يعمل على جميع أجهزة الكمبيوتر الخاصة بنا وعلى Jenkins لدينا ، ولكن في بعض الأحيان تعطل على أحد عملاء Jenkins. ننظر إلى الاختبار ، نفهم لماذا. اتضح أنه كان يتراجع ، لأنه عند التحقق من "تاريخ الدفع يجب أن يكون الآن أو في الماضي" ، تبين أنه "في المستقبل".

 assert payment.time <= new Date(); 

ننظر إلى الكود فجأة ، وفي ظل بعض الظروف ، هل يمكننا تحديد موعد في المستقبل؟ لا يمكننا: في المكان الوحيد الذي تتم فيه تهيئة وقت الدفع ، يتم استخدام التاريخ () الجديد ، وهذا هو الوقت الحالي دائمًا (في الحالات القصوى ، قد يكون في الماضي إذا كان الاختبار بطيئًا جدًا). كيف هذا ممكن حتى؟ ضربوا رؤوسهم لفترة طويلة ، لم يتمكنوا من الفهم.

وبمجرد أن نظروا في سجل التطبيق. ومن هنا الأخلاقي الأول - من المفيد للغاية عند فحص الاختبارات للنظر في سجل التطبيق نفسه. ارفع يديك ، من يفعل ذلك. بشكل عام ، وليس الأغلبية ، للأسف. وهناك معلومات مفيدة: على سبيل المثال ، سجل الطلبات ، تم تنفيذ عنوان URL كذا وكذا في مثل هذا الوقت ، أعطى إجابة كذا وكذا.



هل هناك شيء مريب هنا ، لاحظ؟ ننظر إلى الوقت: تمت معالجة هذا الطلب ناقص ثلاث ثوانٍ. كيف يكون هذا؟ قاتلوا لفترة طويلة ، لا يمكن أن يفهموا. أخيرًا ، عندما نفدنا من النظرية ، اتخذنا قرارًا غبيًا: كتب جنكينز نصًا بسيطًا يسجل الوقت الحالي في دورة مرة واحدة في الثانية. أطلقها. في اليوم التالي ، عندما سقط هذا الاختبار غير المستقر مرة في الليل ، بدأوا في مشاهدة مقتطف من هذا الملف في الوقت الذي سقط فيه:



لذا: 34 ثانية ، 35 ، 36 ، 37 ، 35 ، 39 ... من الرائع أننا وجدناها ، ولكن كيف يمكن ذلك؟ انتهت النظريات مرة أخرى ، خدش رؤوسهم يومين آخرين. هذا هو الحال عندما ماتريكس يمزح معك ، أليس كذلك؟

حتى أخيرًا ضربتني فكرة واحدة ... واتضح ذلك. لدى Linux خدمة مزامنة الوقت التي تعمل على خادم مركزي وتسأل "كم مللي ثانية الآن؟" وتبين أنه تم إطلاق خدمتين مختلفتين في جنكينز هذه. بدأ الاختبار في التعطل عندما تم تحديث Ubuntu على هذا الخادم.

هناك ، تم تكوين خدمة ntp مسبقًا ، والتي وصلت إلى خادم مصرفي خاص واستغرق وقتًا من هناك. ومع الإصدار الجديد من Ubuntu ، تم تضمين خدمة جديدة خفيفة الوزن بشكل افتراضي ، على سبيل المثال ، systemd-timesyncd. وعمل كلاهما. لم يلاحظ أحد ذلك. لسبب ما ، أصدر الخادم المصرفي المركزي وبعض خادم Ubuntu المركزي استجابة بفارق 3 ثوان. بطبيعة الحال ، تدخلت هاتان الخدمتان مع بعضهما البعض. في مكان ما بعمق في وثائق Ubuntu ، تقول ، بالطبع ، لا تسمح بهذا الموقف ... حسنًا ، شكرًا على المعلومات :)

بالمناسبة ، تعلمت في الوقت نفسه فارقًا واحدًا مثيرًا للاهتمام لجاوا ، والذي لم يكن يعرفه قبل ذلك ، على الرغم من سنوات خبرتي العديدة. واحدة من أكثر الطرق الأساسية في Java تسمى System.currentTimeMillis () ، والتي يتم عادة المساعدة فيها على استدعاء شيء ما ، كتب العديد من هذه الرموز:

 long start = System.currentTimeMillis(); // ... long end = System.currentTimeMillis(); log.info("Loaded in {} ms", end-start); 

مثل هذا الكود موجود في Apache Commons ، مكتبات الجوافة. بمعنى ، إذا كنت بحاجة إلى اكتشاف عدد المللي ثانية التي يستغرقها استدعاء شيء ما ، فعادة ما يفعلون ذلك. وربما سمع الكثيرون أنه لا ينبغي القيام بذلك. سمعت أيضا ، ولكن لم أكن أعرف لماذا ، وكسالى جدا لفهمها. اعتقدت أن السؤال كان بالضبط لأن System.nanoTime () ظهر في بعض إصدارات Java - إنه أكثر دقة ، فهو ينتج نانو ثانية أكثر دقة مليون مرة. وبما أن مكالماتي تستمر ، كقاعدة ، لمدة ثانية أو نصف ثانية ، فإن هذه الدقة ليست مهمة بالنسبة لي ، واستمرت في استخدام System.currentTimeMillis () ، الذي رأيناه في السجل حيث كان -3 ثوان. لذا ، في الحقيقة ، الطريقة الصحيحة هي هذه ، والآن اكتشفت لماذا:

 long start = System.nanoTime(); // ... long end = System.nanoTime(); log.info("Loaded in {} ms", (end-start)/1000000); 

في الواقع ، هذا مكتوب في توثيق الأساليب ، لكنني لم أقرأه أبدًا. لقد اعتقدت طيلة حياتي أن System.currentTimeMillis () و System.nanoTime () هما نفس الشيء ، فقط بفارق مليون مرة. ولكن اتضح أن هذه أشياء مختلفة جوهريًا.

System.currentTimeMillis () يعرض التاريخ الحالي الفعلي - عدد المللي ثانية التي أصبحت الآن منذ 1 يناير 1970. و System.nanoTime () هو نوع من العدادات المجردة التي لا ترتبط بالوقت الحقيقي: نعم ، من المضمون أن تنمو كل نانو ثانية لكل وحدة ، ولكنها غير مرتبطة بالوقت الحالي ، بل يمكن أن تكون سلبية. في بداية JVM ، يتم اختيار نقطة زمنية بشكل عشوائي بطريقة ما ، وتبدأ في النمو. كانت مفاجأة بالنسبة لي. لك أيضا؟ حسنًا ، لم يصل سدىًا.

مثال 5: لعنة الزر الأخضر


هنا ، يملأ اختبارنا نموذجًا معينًا ، وينقر على زر التأكيد الأخضر ، وأحيانًا لا يذهب إلى أبعد من ذلك. لماذا لا تذهب غير مفهومة.



نحن نقود في أربعة أصفار ونعلق ، لا تذهب إلى الصفحة التالية. يحدث النقر دون أخطاء. نظرت إلى كل شيء: طلبات Ajax والانتظار والمهلة وسجلات التطبيق وذاكرة التخزين المؤقت - لم أجد أي شيء. لم تظهر مكتبة مسجل الفيديو التي كتبها سيرجي بيروغوف بعد. يسمح بإضافة تعليق توضيحي واحد إلى الرمز ، تسجيل الفيديو. ثم تمكنت من تصوير مقطع فيديو لهذا الاختبار ، ومشاهدته بحركة بطيئة ، وهذا يوضح في النهاية الموقف الذي لم أستطع حله لعدة أشهر قبل الفيديو.



حظر شريط التقدم الزر لجزء من الثانية ، وعملت النقرة تمامًا في تلك اللحظة وضربت شريط التقدم هذا. أي أن شريط التقدم تم النقر عليه واختفى! ولن يكون مرئيًا في أي لقطة شاشة ، في أي سجل ، فلن تعرف أبدًا ما حدث.

من حيث المبدأ ، هذا هو إلى حد ما خطأ في التطبيق: ظهر شريط التقدم لأن التطبيق يزحف حقًا خارج حافة الشاشة ، وإذا قمت بالتمرير ، فقد تبين أن الكثير من البيانات المفيدة. لكن المستخدمين لم يشتكوا من ذلك ، لأن كل شيء يتناسب مع الشاشة الكبيرة ، ولا يتناسب فقط مع الشاشة الصغيرة.

المثال 6: لماذا يتجمد Chrome؟


التحقيق البوليسي لمدة عامين هو حالة حقيقية للغاية. الوضع هو: كانت اختباراتنا في كثير من الأحيان متقشرة وسقطت ، وفي آثار المكدس كان من الواضح أن Chrome يتجمد: ليس اختبارنا ، وهو Chrome. في السجلات كان من الواضح "Build يعمل 36 ساعة ..." بدأوا في إزالة مقالب الخيط وآثار المكدس - يظهرون أن كل شيء على ما يرام في الاختبارات ، وتوقف المكالمة إلى Chromedriver ، وكقاعدة عامة ، في وقت الإغلاق (نسمي طريقة الإغلاق ، وهذه الطريقة لا تفعل شيئا ، وتوقف 36 ساعة). إذا كان مثيرًا للاهتمام ، فقد بدا تتبع المكدس كما يلي:



حاولنا أن نفعل كل ما لا يتبادر إلى الذهن إلا:

  • قم بتكوين المهلة لفتح / إغلاق المتصفح (إذا لم تتمكن من فتح / إغلاق المتصفح في 15 ثانية ، حاول مرة أخرى بعد 15 ثانية ، حتى ثلاث محاولات). افتح وأغلق المتصفح في سلسلة محادثات منفصلة. النتيجة: تم تعليق المحاولات الثلاث بنفس الطريقة.
  • اقتل عمليات Chrome القديمة. لقد أنشأوا وظيفة منفصلة في Jenkins 'kill-chrome' ، على سبيل المثال ، مثل هذا ، يمكنك "قتل" جميع العمليات التي مضى عليها أكثر من ساعة:

    killall - لحام من 1h chromedriver
    killall - مجلد من الكروم ساعة واحدة

    هذا على الأقل حرر الذاكرة ، لكنه لم يعط إجابة على السؤال "ماذا يحدث؟". في الواقع ، هذا الشيء أخرنا لحظة اتخاذ القرار.
  • تمكين سجلات تطبيق التصحيح.
  • تمكين سجلات تصحيح أخطاء WebDriver.
  • أعد فتح المتصفح بعد كل 20 اختبارًا. قد يبدو الأمر سخيفًا ، لكن الفكرة كانت: "ماذا لو تجمد Chrome لأنه متعب؟" حسنا ، تسرب للذاكرة أو أي شيء آخر.

كانت نتيجة المحاولة الأخيرة غير متوقعة تمامًا: بدأت المشكلة تتكرر أكثر! وكنا نأمل أن يساعد ذلك في استقرار Chrome حتى يعمل بشكل أفضل. هذا عادة ما يكون الوجبات الجاهزة للدماغ. ولكن في الواقع ، عندما تبدأ المشكلة في التكرار في كثير من الأحيان ، لا ينبغي للمرء أن يكون حزينًا ، ولكن ابتهج! هذا يجعل من الممكن دراستها بشكل أفضل. إذا بدأت في التكرار أكثر ، فيجب على المرء التمسك به: "نعم ، نعم ، الآن سأضيف شيئًا آخر ، وسجلات ، ونقاط توقف ..."

نحن نحاول تكرار المشكلة: نكتب دورة من 1 إلى 1000 ، في الدورة نفتح المتصفح ببساطة ، ونغلق الصفحة الأولى في تطبيقنا. كتبنا مثل هذه الدورة ، و ... بنغو! النتيجة: بدأت المشكلة تتكرر بثبات (على الرغم من كل 80 تكرارًا تقريبًا)! رائع! صحيح أن هذا الإنجاز لم يعط أي شيء لفترة طويلة. لقد بدأتها ، وانتظرت التكرار 80 ، تعطل Chrome ... ثم ماذا تفعل؟ أنت تنظر إلى آثار المكدس ، والمقالب ، والسجلات - ليس هناك شيء مفيد هناك. قد تساعد أدوات المطورين في Chrome ، ولكن حتى سبتمبر 2017 لم تعمل هذه الأدوات مع Selenium (كانت المنافذ في صراع: أنت تشغل Chrome من Selenium ولا تفتح DevTools). لفترة طويلة ، لم أستطع التفكير في ما يجب فعله.

وهنا في هذه القصة تبدأ لحظة رائعة. مرة واحدة ، بعد عدد لا حصر له من المحاولات ، أجريت هذه الاختبارات مرة أخرى ، إنها معلقة مرة أخرى في تكرار مثل 56 ، أعتقد "دعنا نحفر شيئًا آخر" (على الرغم من أنني لا أعرف مكانًا آخر لوضع نقطة التوقف أو ماذا إضافة سجل). في هذه اللحظة ، تعرض ابنتي أن تلعب مكعبات ، لكن اختباري معلقة هنا فقط. أقول ، "انتظر" ، قالت لي: "ماذا ، أنت لا تفهم ، لقد حصلت على ب هنا وهناك!"

ماذا أفعل ، غادرت الكمبيوتر للأسف ، ذهبت للعب مكعبات ... وفجأة ، بعد حوالي 20 دقيقة ، ألقيت نظرة خاطفة على الشاشة ، وأرى صورة غير متوقعة تمامًا:



ماذا يحدث: هناك عد تنازلي ، بعد عدد الدقائق التي تنتهي فيها الجلسة ، وأنا أبني برجًا من المكعبات ، وتبقى اثنتان ، واحدة ... تنتهي الجلسة ، ويستمر الاختبار ، ويمتد حتى النهاية ويسقط (لم يعد هناك أي عنصر ، انتهت الجلسة).

ماذا يحدث: لم يتجمد Chrome حقًا ، كما اعتقدنا طوال هذا الوقت ، إنه ينتظر شيئًا طوال هذا الوقت. عندما انتهت الجلسة انتظرت. ما كان Chrome بالضبط يتوقعه - من غير المفهوم تمامًا لفهم ذلك ، كان عليّ أن أجرف كل الشفرة باستخدام طريقة البحث الثنائية: التخلص من نصف JavaScript و HTML ، وحاول تكرار 80 تكرارًا مرة أخرى - لم يتم تعليقها ، أوه ، هذا يعني أنه في مكان ما هناك ... بشكل عام ، فهمنا تجريبيًا أن المشكلة هنا:

 var timeout = setTimeout(sessionWatcher); 

JavaScript — , , . , JavaScript- , : , <script> . , , , , . JavaScript — jQuery, $, function , :

 var timeout; $(function() { timeout = setTimeout(...); }); 

هذا هو ABC لبرمجة الويب ، ربما يعرف الجميع. ولم يكن لدينا ذلك ، فقد أخطأنا. عندما قمت بتغيير هذا وكرر التجربة لألف تكرار ، لم تعد معلقة.

صحيح ، أنا لا أعرف كل الإجابات بشكل كامل: على سبيل المثال ، لا أعرف لماذا لم يتم تعليقها دائمًا ، ولكن نادرًا جدًا في بعض الأحيان. ربما هذا خطأ Chrome ، اللعنة. نعم ، لقد استغرق الأمر حقًا عامين للبحث في هذه المشكلة.

, flaky- , , , . , — , , ( ). , . , : ?

Chrome flaky-: -, , , .

UI- : , . click(), , . , , : click() , . - , ? :)

. , , , . , , , , Docker.

, - , . :

  • Ajax-;
  • Ajax-;
  • JS;
  • ;
  • ;
  • ;
  • ;
  • ;
  • UI-;
  • ( ).

flaky- , . , -, : , .

. «» . , flaky- , ID, flaky- . .

, : , , .

, flaky- usability, -, flaky- . .

, , … , , , flaky- security-, . , !

, , flaky-:

  • ;
  • Selenide;
  • ;
  • .

— . flaky- , unit- , UI-? , ( ), , flaky.

Selenide .

. (, / ). , « ?». , , . , .

, . , , , , . : « », , (10 , 20 , — , — ). , flaky - .

, flaky- :

  1. ;
  2. ;
  3. فيديو

« » , , , : - . «», «» , : flaky-, , flaky. , , . . , Jenkins pipeline, Jenkins :

 finally { stage("Reports") { junit 'build/test-results/**/*.xml' artifacts = 'build./reports/**/*,build/test-results/**/*,logs/**/*' archiveArtifacts artifacts: artifacts } } 

finally , . : - - . Jenkins , . , Jenkins , . , .

(Selenide , ). flaky-. , Video Recorder , : video — , !

, Docker: TestContainers ( Heisenbug ). Rule , — Docker , , . .

 @Rule public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer() .withRecordingMode(RECORD_ALL, new File("build")) .withDesiredCapabilities(chrome()); 

.

. , , , , , . flaky- .

, ! , :) . , « , , », , .

— , , , , - . — , … ! :) flaky- — : !

, : 6-7 Heisenbug . , , . (, , ) .

Source: https://habr.com/ru/post/ar416757/


All Articles