الاختبارات التلقائية لواجهة المستخدم: كيف لا تفعل ذلك

مرحبا يا هابر. اسمي فيتالي كوتوف ، أعمل في قسم الاختبارات في Badoo. أكتب الكثير من الاختبارات التلقائية لواجهة المستخدم ، لكنني أعمل أكثر مع أولئك الذين كانوا يقومون بهذا منذ وقت ليس ببعيد ولم يتمكنوا بعد من السير على جميع المكابس.

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

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

دعنا نذهب!




المحتويات



محددات المواقع بدون سمات


لنبدأ بمثال بسيط. نظرًا لأننا نتحدث عن اختبارات واجهة المستخدم ، يلعب المحددون دورًا مهمًا فيها. محدد الموقع هو خط يتكون وفقًا لقاعدة معينة ويصف واحدًا أو أكثر من عناصر XML (خاصة HTML).

هناك عدة أنواع من محددات المواقع. على سبيل المثال ، يتم استخدام محددات css في أوراق الأنماط المتتالية. يتم استخدام محددات XPath للعمل مع مستندات XML. وهكذا دواليك.

يمكن العثور على قائمة كاملة بأنواع محدد المواقع التي تستخدمها السيلينيوم على seleniumhq.imtqy.com .

في اختبارات واجهة المستخدم ، يتم استخدام محددات المواقع لوصف العناصر التي يجب أن يتفاعل معها السائق.

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



اتضح مثل هذا المحدد:

/html/body/div[3]/div[1]/div[2]/div/div/div[2]/div[1]/a

يبدو أنه لا حرج في مثل هذا الموقع. بعد كل شيء ، يمكننا حفظه في بعض ثابت أو حقل من الفصل ، والذي سينقل باسمه جوهر العنصر:

 @FindBy(xpath = "/html/body/div[3]/div[1]/div[2]/div/div/div[2]/div[1]/a") public WebElement createAccountButton; 

ولف نص الخطأ المقابل في حالة عدم العثور على العنصر:

 public void waitForCreateAccountButton() { By by = By.xpath(this.createAccountButton); WebDriverWait wait = new WebDriverWait(driver, timeoutInSeconds); wait .withMessage(“Cannot find Create Account button.”) .until( ExpectedConditions.presenceOfElementLocated(by) ); } 

هذا النهج له ميزة إضافية: ليست هناك حاجة لتعلم XPath.

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

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

//a[@rel=”createAccount”]

من السهل إدراك هذا الموقع في الكود ، ولن ينكسر إلا إذا اختفى "rel".

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

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

تحقق من عدم وجود عنصر مفقود


لكل مستخدم Badoo ملفه الشخصي. يحتوي على معلومات حول المستخدم: (الاسم ، العمر ، الصور) ومعلومات حول من يريد المستخدم الدردشة معه. بالإضافة إلى ذلك ، من الممكن الإشارة إلى اهتماماتك.

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

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



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

مهمتنا هي كتابة اختبار آلي للتحقق من هذه الحالة.

نكتب النص التالي تقريبًا:

  • افتح الملف الشخصي
  • قائمة اهتمامات مفتوحة
  • انقر فوق الزر "المزيد"
  • تأكد من عدم ظهور الخطأ (على سبيل المثال ، لا يوجد عنصر div.error)

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

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

أثناء إعادة الهيكلة ، استمر الاختبار في العمل كما هو متوقع. لم يظهر عنصر div.error ، ولم يكن هناك سبب للسقوط. ولكن الآن لا يوجد عنصر "div.error" على الإطلاق - وبالتالي ، لن يفشل الاختبار أبدًا ، بغض النظر عما يحدث في التطبيق.

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

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

تحقق من وجود عنصر


كيفية التأكد من نجاح التفاعل التجريبي مع الواجهة وعمل كل شيء؟ يظهر هذا غالبًا في التغييرات التي حدثت في هذه الواجهة.

تأمل في مثال. تحتاج إلى التأكد من أنه عند إرسال رسالة تظهر في الدردشة:



يبدو النص كما يلي:

  • افتح ملف تعريف المستخدم
  • افتح الدردشة معه
  • اكتب رسالة
  • إرسال
  • انتظر حتى تظهر الرسالة.

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

p.message_text

هذه هي الطريقة التي نتحقق من ظهور العنصر:

 this.waitForPresence(By.css('p.message_text'), "Cannot find sent message."); 

إذا كان انتظارنا يعمل ، فكل شيء على ما يرام: يتم رسم رسائل الدردشة.

كما كنت قد خمنت ، بعد فترة ، فواصل إرسال رسائل الدردشة ، ولكن اختبارنا يستمر في العمل دون انقطاع. دعنا نحصل على حق.

اتضح أنه في اليوم السابق لظهور عنصر جديد في الدردشة: بعض النصوص التي تحث المستخدم على تمييز الرسالة إذا ذهبت فجأة دون أن يلاحظها أحد:



والأكثر غرابة أنه يقع أيضًا تحت محددنا. فقط لديه فئة إضافية تميزه عن الرسائل المرسلة:

p.message_text.highlight

لم ينكسر اختبارنا عندما ظهرت هذه الكتلة ، ولكن توقف التحقق من "انتظار ظهور الرسالة". العنصر الذي كان مؤشرًا لحدث ناجح موجود الآن دائمًا.

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

  • افتح ملف تعريف المستخدم
  • افتح الدردشة معه
  • تأكد من عدم وجود رسائل مرسلة
  • اكتب رسالة
  • إرسال
  • انتظر حتى تظهر الرسالة.

بيانات عشوائية


في كثير من الأحيان ، تعمل اختبارات واجهة المستخدم مع النماذج التي تُدخل البيانات إليها. على سبيل المثال ، لدينا نموذج تسجيل:



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

نصيحتي: لا تفعل. والآن سأخبرك لماذا.

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

تخيل الآن أنه بعد فترة من الوقت يصبح تدفق التسجيل مختلفًا. على سبيل المثال ، نمنح الفتاة مكافآت مجانية فور التسجيل ، والتي نبلغها عنها بتراكب خاص.

في الاختبار ، لا يوجد منطق لإغلاق التراكب ، ولكنه بدوره يتداخل مع أي إجراءات أخرى منصوص عليها في الاختبار. نحصل على اختبار يقع في 50٪ من الحالات. ستؤكد أي أداة أتمتة أن اختبارات واجهة المستخدم ليست مستقرة بطبيعتها. وهذا أمر طبيعي ، يجب على المرء أن يعيش معه ، ويتناول باستمرار بين المنطق الزائد "لجميع المناسبات" (التي تفسد بشكل ملحوظ قراءة الشفرة وتعقد دعمها) وهذا عدم الاستقرار نفسه.

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

الآن دعنا ننتقل. ماذا لو انكسر هذا التراكب؟ سيستمر الاختبار في اجتياز 50٪ من الحالات ، مما يؤدي إلى تأخير كبير في العثور على المشكلة.

ومن الجيد أنه ، بسبب التوزيع العشوائي للبيانات ، نقوم بإنشاء موقف "50 في 50". لكن هذا يحدث بشكل مختلف. على سبيل المثال ، قبل التسجيل ، تم اعتبار كلمة المرور مقبولة بثلاثة أحرف على الأقل. نكتب رمزًا يأتي بكلمة مرور عشوائية لا تقل عن ثلاثة أحرف (أحيانًا ثلاثة أحرف ، وأحيانًا أكثر). ثم تتغير القاعدة - ويجب أن تحتوي كلمة المرور بالفعل على أربعة أحرف على الأقل. ما هو احتمال السقوط في هذه الحالة؟ وإذا اكتشف اختبارنا خطأً حقيقيًا ، فكم بسرعة سنكتشفه؟

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

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

الذرية الاختبارات (الجزء 1)


دعونا نلقي نظرة على المثال التالي. نحن نكتب اختبارًا يتحقق من عداد المستخدمين في التذييل.



السيناريو بسيط:

  • افتح التطبيق
  • البحث عن عداد تذييل
  • تأكد من أنه مرئي

نسمي هذا الاختبار testFooterCounter وتشغيله. ثم يصبح من الضروري التحقق من أن العداد لا يظهر صفرًا. نضيف هذا الاختبار إلى اختبار موجود ، لماذا لا؟

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

من ناحية ، فإن هذا النهج له مزايا: توفير الوقت ، وتخزين جميع الشيكات الخاصة بجزء من تطبيقنا (في هذه الحالة ، تذييل) في مكان واحد.

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

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

الذرية الاختبارات (الجزء 2)


تأمل في مثال آخر. نحن نكتب اختبار دردشة يتحقق من المنطق التالي. إذا كان لدى المستخدمين تعاطف متبادل ، فسيظهر promoblock التالي في الدردشة:



السيناريو كما يلي:

  • تصويت المستخدم أ للمستخدم ب
  • تصويت المستخدم ب للمستخدم أ
  • المستخدم محادثة مفتوحة مع المستخدم ب
  • تأكد من أن الوحدة في مكانها

لبعض الوقت ، يعمل الاختبار بنجاح ، ولكن بعد ذلك يحدث ما يلي ... لا ، هذه المرة لا يفوت الاختبار أي خلل. :)

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

ينشأ نفس السؤال: اكتب اختبارًا آخر أو أدخل اختبارًا في اختبار موجود؟ تبدو كتابة واحدة جديدة غير مناسبة ، لأن 99 ٪ من الوقت سيفعل نفس الشيء الموجود. ونقرر إضافة الاختبار إلى الاختبار الموجود بالفعل:

  • تصويت المستخدم أ للمستخدم ب
  • تصويت المستخدم ب للمستخدم أ
  • المستخدم محادثة مفتوحة مع المستخدم ب
  • تأكد من أن الوحدة في مكانها
  • إغلاق الدردشة
  • افتح الدردشة
  • تأكد من أن الوحدة في مكانها

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

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

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

خطأ في النقر على عنصر موجود


المثال التالي ألقى بي بيبدوكس ، والذي يعد إضافة كبيرة له في الكرمة!

هناك موقف مثير للاهتمام عندما يصبح رمز الاختبار بالفعل ... إطار عمل. لنفترض أن لدينا طريقة مثل هذه:

  public void clickSomeButton() { WebElement button_element = this.waitForButtonToAppear(); button_element.click(); } 

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

الجزء الأصعب في هذا الموقف هو أن الاختبار يمكن أن ينجح في بعض الأحيان. :)

دعنا نحصل على حق. افترض أن الزر المذكور في المثال يقع على مثل هذا التراكب:



هذا تراكب خاص يمكن من خلاله للمستخدم على موقعنا ملء معلومات عن نفسه. عندما تنقر على زر التراكب المميز ، يبدو أن الكتلة التالية تملأ.

من أجل المتعة ، دعنا نضيف فئة OLOLO إضافية لهذا الزر:



بعد ذلك نضغط على هذا الزر. بصريا ، لم يتغير شيء ، لكن الزر نفسه بقي في مكانه:



ماذا حدث؟ في الواقع ، عندما أعاد JS إعادة رسم الكتلة لنا ، أعاد رسم الزر أيضًا. لا يزال متاحًا على نفس محدد المواقع ، ولكن هذا زر آخر. ويتجلى ذلك في عدم وجود فئة OLOLO التي أضفناها.

في الكود أعلاه ، نقوم بتخزين العنصر في متغير $ element. إذا تم إعادة إنشاء عنصر خلال هذا الوقت ، فقد لا يكون مرئيًا بشكل مرئي ، ولكن لا يمكنك النقر فوقه بعد الآن - ستفشل طريقة click ().

هناك العديد من الحلول:

  • انقر فوق التفاف في محاولة كتلة وفي عنصر إعادة بناء الصيد
  • أضف زرًا إلى سمة للإشارة إلى أنه قد تغير

نص الخطأ


وأخيرًا ، نقطة بسيطة ، ولكنها ليست أقل أهمية.

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

 WebElement element = this.waitForPresence(By.css("a.link"), "Cannot find button"); 

ما الذي قد يكون غير مفهوم في هذا الرمز؟ يتوقع الاختبار ظهور زر ، وإذا لم يكن هناك يسقط بشكل طبيعي.

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



ماذا سيفعل؟

من غير المجدي فتح الصفحة التي وقع عليها الاختبار والتحقق من محدد "a.link" - لا يوجد عنصر. لذلك ، يجب عليك دراسة الاختبار بعناية ومعرفة ما يتحقق منه.

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

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

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

الملخص


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

  • تغطية أكبر عدد ممكن من الحالات
  • العمل بأسرع وقت ممكن
  • أن يكون مفهوما
  • فقط قم بالتوسيع
  • سهلة الصيانة
  • طلب البيتزا
  • وهكذا ...

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

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

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


All Articles