(في) الحرب المحدودة

حرب لا حصر لها


لدينا مشكلة. المشكلة مع الاختبار. المشكلة في اختبار مكونات التفاعل ، وهي أساسية للغاية. إنه يتعلق بالفرق بين unit testing integration testing . يتعلق الأمر بالفرق بين ما نسميه اختبار الوحدة وما نسميه اختبار التكامل والحجم والنطاق.


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


يعلم الجميع كيفية اختبار المكونات البسيطة (فهي بسيطة) ، وربما يعرفون كيفية اختبار التطبيقات (E2E). كيفية اختبار الأشياء المحددة واللانهائية ...


تحديد المشكلة


هناك طريقتان مختلفتان لاختبار React Component - shallow وكل شيء آخر ، بما في ذلك react-testing-library mount ، react-testing-library ، webdriver وما إلى ذلك. shallow فقط هي الخاصة - الباقي يتصرف بنفس الطريقة.


وهذا الاختلاف يدور حول الحجم ، والنطاق - حول ما سيتم اختباره ، وكيف يحدث جزئيًا.


باختصار - لن يسجل shallow سوى دعوات إلى React.createElement ، ولكن لن يتم تشغيل أي آثار جانبية ، بما في ذلك تقديم عناصر DOM - إنه تأثير جانبي (جبري) من React.createElement.


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


والمشكلة هي التالية: you can NOT run each and every side effect .


لم لا؟


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


  • ولكن هذه ليست مشكلة بالنسبة ... dumb components . فهي غبية ، تحتوي فقط على طبقة العرض التقديمي ، ولكن ليس "تأثيرات جانبية".


  • ولكن هذه مشكلة Containers . طالما أنها ليست غبية ، وتحتوي على ما يريدون ، وبشكل كامل عن الآثار الجانبية. هم المشكلة!



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


TRDL: المكون المحدود

المكونات الذكية والبكم


وفقا لمكونات عرض مقال دان ابراموف هي:


  • نشعر بالقلق مع كيف تبدو الأمور.
  • قد يحتوي على مكونات ** ومكونات حاوية ** الداخل ، وعادةً ما تحتوي على بعض علامات وأنماط DOM الخاصة بها.
  • غالبًا ما تسمح بالاحتواء عبر this.props.children.
  • لا تعتمد على بقية التطبيق ، مثل إجراءات Flux أو المتاجر.
  • لا تحدد كيفية تحميل البيانات أو تحورها.
  • تلقي البيانات وردود الفعل حصرا عبر الدعائم.
  • نادراً ما يكون لديهم حالة خاصة بهم (عندما يفعلون ، تكون حالة واجهة المستخدم بدلاً من البيانات).
  • يتم كتابتها كمكونات وظيفية ما لم تكن بحاجة إلى حالة أو خطافات دورة حياة أو تحسينات في الأداء.
  • أمثلة: الصفحة ، الشريط الجانبي ، القصة ، UserInfo ، القائمة.
  • ....
  • والحاويات هي مجرد مزودي البيانات / الدعائم لهذه المكونات.

وفقًا للأصول: في التطبيق المثالي ...
الحاويات هي الشجرة. المكونات هي أوراق الشجر.


العثور على القطة السوداء في الغرفة المظلمة


الصلصة السرية هنا ، تغيير واحد يتعين علينا تعديله في هذا التعريف ، مخفية داخل "قد تحتوي على مكونات ** ومكونات حاوية ** " ، واسمحوا لي أن أذكر مقالة أصلية:


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

حسنًا ، ولكن ماذا عن القاعدة ، مما يجعل وحدة مكونات العرض التقديمي قابلة للاختبار - "هل لديك أي تبعيات على بقية التطبيق" ؟


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


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


مكونات العرض يجب أن تحتوي فقط على مكونات العرض التقديمي


والسؤال الوحيد ، يجب أن تسأل (النظر في قاعدة التعليمات البرمجية الحالية الخاصة بك): كيف؟ : tableflip:؟!


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


الحل 1 - DI


الحل 1 بسيط - لا يحتوي على حاويات متداخلة في المكون الغبي - يحتوي على slots . ما عليك سوى قبول "المحتوى" (الأطفال) ، كدعائم ، وهذا من شأنه أن يحل المشكلة:


  • أنت قادر على اختبار المكون الغبي بدون "بقية تطبيقك"
  • أنت قادر على اختبار التكامل مع اختبار الدخان / التكامل / e2e ، وليس الاختبارات.

 // Test me with mount, with "slots emty". const PageChrome = ({children, aside}) => ( <section> <aside>{aside}</aside> {children} </section> ); // test me with shallow, or real integration test const PageChromeContainer = () => ( <PageChrome aside={<ASideContainer />}> <Page /> </PageChrome> ); 

وافق عليه دان نفسه:
{٪ twitter 1021850499618955272٪}


DI (كلا Dependecy Injection و Dependency Inversion) ، على الأرجح ، هي تقنية قابلة لإعادة الاستخدام هنا ، قادرة على جعل حياتك أسهل بكثير.


أشر هنا - مكونات غبية هي غبية!

الحل 2 - الحدود


هذا حل توضيحي تمامًا ويمكن أن يمتد Solution 1 - قم فقط بالإعلان عن جميع نقاط التمديد . مجرد التفاف لهم ... Boundary


 const Boundary = ({children}) => ( process.env.NODE_ENV === 'test' ? null : children // or `jest.mock` ); const PageChrome = () => ( <section> <aside><Boundary><ASideContainer /></Boundary></aside> <Boundary><Page /></Boundary> </section> ); 

بعد ذلك - أنت قادر على تعطيل ، فقط صفر ، Boundary لتقليل نطاق المكونات ، وجعلها محدودة .


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

الحل 3 - المستوى


هو نفس الحل 2 ، ولكن مع حدود أكثر ذكاءً ، قادرة على الاستهزاء بالطبقة ، أو الطبقة ، أو أي شيء تقوله:


 const checkTier = tier => tier === currentTier; const withTier = tier => WrapperComponent => (props) => ( (process.env.NODE_ENV !== 'test' || checkTier(tier)) && <WrapperComponent{...props} /> ); const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> ); const ASideContainer = withTier('UI')(...) const Page = withTier('Page')(...) const PageChromeContainer = withTier('UI')(PageChrome); 

حتى لو كان هذا مشابهًا تقريبًا لمثال Boundary - مكون Dumb هو Dumb ، والحاويات التي تتحكم في إمكانية رؤية حاويات أخرى.

الحل 4 - مخاوف منفصلة


حل آخر هو فقط لفصل المخاوف! أقصد - لقد فعلت ذلك بالفعل ، وربما حان الوقت للاستفادة منه.


من خلال connect مكون ing بـ Redux أو GQL ، فإنك تقوم بإنتاج حاويات معروفة جيدًا . أعني - بأسماء معروفة - Container(WrapperComponent) . قد تسخر منهم بأسمائهم

 const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> ); // remove all components matching react-redux pattern reactRemock.mock(/Connect\(\w\)/) // all any other container reactRemock.mock(/Container/) 

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


 import {createElement, remock} from 'react-remock'; // initially "open" const ContainerCondition = React.createContext(true); reactRemock.mock(/Connect\(\w\)/, (type, props, children) => ( <ContainerCondition.Consumer> { opened => ( opened ? ( // "close" and render real component <ContainerCondition.Provider value={false}> {createElement(type, props, ...children)} <ContainerCondition.Provider> ) // it's "closed" : null )} </ContainerCondition.Consumer> ) 

أشر هنا: لا يوجد منطق داخل أو عرض تقديمي ، وليس حاوية - كل المنطق خارج.

مكافأة الحل - مخاوف منفصلة


يمكنك الحفاظ على اقتران ضيق باستخدام defaultProps ، defaultProps هذه الدعائم في الاختبارات ...


 const PageChrome = ({Content = Page, Aside = ASideContainer}) => ( <section> <aside><Aside/></aside> <Content/> </section> ); 

اذا؟


لقد نشرت للتو عدة طرق لتقليل نطاق أي مكون ، وجعلها أكثر قابلية للاختبار. طريقة بسيطة للحصول على واحد من gear من gearbox . نمط بسيط لتجعل حياتك أسهل.


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


أنت تعرف ، كما كتب دان في مقاله الآخر :


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

في حين أن الحل الصحيح هنا هو آلات الحالة ، إلا أن القدرة على اختيار ذرة واحدة أو جزيء واحد واللعب بها - هو الشرط الأساسي.


النقاط الرئيسية في هذا المقال


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

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

ملاحظة: هذه ترجمة لنسخة مقالة رو-هابر .

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


All Articles