(in) Guerre finie

infitite war


Nous avons un problème. Le problème avec les tests. Le problème avec les tests des composants React, et c'est assez fondamental. Il s'agit de la différence entre les unit testing et integration testing . Il s'agit de la différence entre ce que nous appelons les tests unitaires et ce que nous appelons les tests d'intégration, la taille et la portée.


Il ne s'agit pas de se tester, mais de l'architecture des composants. À propos de la différence entre les composants de test, les bibliothèques autonomes et les applications finales.


Tout le monde sait comment tester des composants simples (ils sont simples), sait probablement comment tester des applications (E2E). Comment tester des choses finies et infinies ...


Définissez le problème


Il existe 2 façons différentes de tester le composant React - shallow et tout le reste, y compris le mount , la react-testing-library webdriver , le webdriver , etc. Seul shallow est spécial - les autres se comportent de la même manière.


Et cette différence concerne la taille et la portée - ce qui serait testé, et juste partiellement comment .


En bref - shallow enregistrera uniquement les appels à React.createElement, mais sans exécuter d'effets secondaires, y compris le rendu des éléments DOM - c'est un effet secondaire (algébrique) de React.createElement.


Toute autre commande exécutera le code que vous avez fourni, chaque effet secondaire étant également exécuté. Comme ce serait en réalité, et c'est le but.


Et le problème est le suivant: you can NOT run each and every side effect .


Pourquoi pas?


Pureté fonctionnelle? Pureté et immuabilité - les vaches sacrées d'aujourd'hui. Et vous en abattez un. Les axiomes des tests unitaires - aucun effet secondaire, isolement, moquerie, tout sous contrôle.


  • Mais ce n'est pas un problème pour ... dumb components . Ils sont stupides, ne contiennent que la couche de présentation, mais pas les "effets secondaires".


  • Mais c'est un problème pour les Containers . Tant qu'ils ne sont pas stupides, contiennent tout ce qu'ils veulent et entièrement sur les effets secondaires. Ils sont le problème!



Probablement, si nous définissons les règles de "The Right Component", nous pourrions facilement tester - cela nous guidera et nous aidera.


TRDL: le composant fini

Composants intelligents et stupides


Selon Dan Abramov, les composants de présentation d' article sont:


  • Sont préoccupés par l'apparence des choses.
  • Peut contenir à la fois des composants de présentation et de conteneur ** intérieur, et possède généralement un balisage DOM et des styles qui leur sont propres.
  • Autorisez souvent le confinement via this.props.children.
  • N'ayez aucune dépendance vis-à-vis du reste de l'application, comme les actions Flux ou les magasins.
  • Ne spécifiez pas comment les données sont chargées ou mutées.
  • Recevez des données et des rappels exclusivement via des accessoires.
  • Ils ont rarement leur propre état (lorsqu'ils le font, c'est l'état de l'interface utilisateur plutôt que les données).
  • Sont écrits en tant que composants fonctionnels à moins qu'ils n'aient besoin de crochets d'état, de cycle de vie ou d'optimisation des performances.
  • Exemples: Page, Sidebar, Story, UserInfo, List.
  • ....
  • Et les conteneurs ne sont que des fournisseurs de données / accessoires pour ces composants.

Selon les origines: Dans l'application idéale ...
Les conteneurs sont l'Arbre. Les composants sont des feuilles d'arbre.


Trouvez le chat noir dans la pièce sombre


La sauce secrète ici, un changement que nous devons modifier dans cette définition, est cachée à l'intérieur "Peut contenir à la fois des composants de présentation et des conteneurs ** " , permettez-moi de citer l'article original:


Dans une version antérieure de cet article, j'ai affirmé que les composants de présentation ne devraient contenir que d'autres composants de présentation. Je ne pense plus que ce soit le cas. Qu'un composant soit un composant de présentation ou un conteneur est son détail d'implémentation. Vous devriez pouvoir remplacer un composant de présentation par un conteneur sans modifier aucun des sites d'appel. Par conséquent, les composants de présentation et de conteneur peuvent contenir très bien d'autres composants de présentation ou de conteneur.

D'accord, mais qu'en est-il de la règle, qui rend l'unité de composants de présentation testable - «N'a pas de dépendances sur le reste de l'application» ?


Malheureusement, en incluant des conteneurs dans les composants de présentation, vous rendez les seconds infinis et injectez des dépendances dans le reste de l'application.


Ce n'est probablement pas quelque chose que vous étiez censé faire. Donc, je n'ai pas d'autre choix, mais pour rendre le composant stupide fini:


LES COMPOSANTS DE PRÉSENTATION NE DOIVENT CONTENIR QUE D'AUTRES COMPOSANTS DE PRÉSENTATION


Et la seule question que vous devriez poser (en examinant votre base de code actuelle): comment? : tableflip:?!


Aujourd'hui, les composants et les conteneurs de présentation ne sont pas seulement enchevêtrés, mais parfois simplement non extraits en tant qu'entités «pures» (bonjour GraphQL).


Solution 1 - DI


La solution 1 est simple - ne contient pas de conteneurs imbriqués dans le composant stupide - contient des slots . Acceptez simplement le "contenu" (enfants), comme accessoires, et cela résoudrait le problème:


  • vous pouvez tester un composant stupide sans "le reste de votre application"
  • vous pouvez tester l'intégration avec le test smoke / integration / e2e, pas les tests.

 // 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> ); 

Approuvé par Dan lui-même:
{% twitter 1021850499618955272%}


DI (Dependecy Injection et Dependency Inversion), probablement, est une technique la plus réutilisable ici, capable de vous rendre la vie beaucoup plus facile.


Pointez ici - Les composants stupides sont stupides!

Solution 2 - Limites


Il s'agit d'une solution assez déclarative, et pourrait étendre la Solution 1 - il suffit de déclarer tous les points d' extension . Enveloppez-les simplement avec ...


 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> ); 

Ensuite, vous pouvez désactiver, juste zéro, la Boundary pour réduire la portée du composant et la rendre finie .


Pointez ici - La limite est au niveau de la composante Dumb. Le composant stupide contrôle à quel point il est stupide.

Solution 3 - Niveau


Est la même que la solution 2, mais avec une frontière plus intelligente, capable de se moquer de la couche ou du niveau , ou tout ce que vous dites:


 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); 

Même si cela est presque similaire à l'exemple Boundary - le composant Dumb est Dumb et les conteneurs contrôlent la visibilité des autres conteneurs.

Solution 4 - Préoccupations distinctes


Une autre solution consiste simplement à séparer les préoccupations! Je veux dire - vous l'avez déjà fait, et il est probablement temps de l'utiliser.


En connect le composant à Redux ou GQL, vous produisez des conteneurs bien connus . Je veux dire - avec des noms bien connus - Container(WrapperComponent) . Vous pouvez vous moquer d'eux par leurs noms

 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/) 

Cette approche est un peu grossière - elle effacera tout , ce qui rendra plus difficile le test des Contaiers eux-mêmes, et vous pouvez utiliser un mocking un peu plus complexe pour garder le "premier":


 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> ) 

Pointez ici: il n'y a pas de logique à l'intérieur ni de présentation, pas de conteneur - toute logique est à l'extérieur.

Solution bonus - Préoccupations distinctes


Vous pouvez garder un couplage serré en utilisant defaultProps et annuler ces accessoires dans les tests ...


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

Alors?


Je viens donc de publier quelques façons de réduire la portée de n'importe quel composant et de les rendre beaucoup plus testables. Le moyen simple de sortir un gear de la gearbox . Un modèle simple pour vous faciliter la vie.


Les tests E2E sont excellents, mais il est difficile de simuler certaines conditions, qui pourraient se produire dans une fonction profondément imbriquée et être prêtes pour elles. Vous devez avoir des tests unitaires pour pouvoir simuler différents scénarios. Vous devez avoir des tests d'intégration pour vous assurer que tout est correctement câblé.


Vous savez, comme Dan l'a écrit dans son autre article :


Par exemple, si un bouton peut être dans l'un des 5 états différents (normal, actif, en vol stationnaire, danger, désactivé), le code de mise à jour du bouton doit être correct pour 5 × 4 = 20 transitions possibles - ou interdire certaines d'entre elles. Comment apprivoiser l'explosion combinatoire d'états possibles et rendre la sortie visuelle prévisible?

Bien que la bonne solution ici soit les machines à états, être en mesure de choisir un seul atome ou une seule molécule et de jouer avec - est l'exigence de base.


Les principaux points de cet article


  1. Les composants de présentation ne doivent contenir que d'autres composants de présentation.
  2. Les conteneurs sont l'Arbre. Les composants sont des feuilles d'arbre.
  3. Vous ne devez pas toujours contenir de conteneurs à l'intérieur des conteneurs de présentation, mais pas uniquement dans les tests.

Vous pouvez plonger plus profondément dans le problème en lisant l' article moyen , mais ici, sautons tout le sucre.

PS: Il s'agit d'une traduction de la version habr de l' article ru-habr .

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


All Articles