(in) Guerra finita

guerra infinita


Tenemos un problema El problema con las pruebas. El problema con probar componentes React, y es bastante fundamental. Se trata de la diferencia entre las unit testing y las integration testing . Se trata de la diferencia entre lo que llamamos pruebas unitarias y lo que llamamos pruebas de integración, el tamaño y el alcance.


No se trata de probarse a sí mismo, sino de Arquitectura de componentes. Acerca de la diferencia entre probar componentes , bibliotecas independientes y aplicaciones finales.


Todos saben cómo probar componentes simples (son simples), probablemente sepan cómo probar aplicaciones (E2E). Cómo probar cosas finitas e infinitas ...


Definir el problema


Hay 2 formas diferentes de probar React Component: shallow y todo lo demás, incluido el mount , react-testing-library webdriver , el webdriver , etc. Solo shallow es especial: el resto se comporta de la misma manera.


Y esta diferencia es sobre el tamaño y el alcance : sobre QUÉ se probaría, y solo parcialmente cómo .


En resumen: shallow solo registrará llamadas a React.createElement, pero no ejecutará ningún efecto secundario, incluida la representación de elementos DOM: es un efecto secundario (algebraico) de React.createElement.


Cualquier otro comando ejecutará el código que proporcionó con todos y cada uno de los efectos secundarios que también se ejecutan. Como sería en real, y ese es el objetivo.


Y el problema es el siguiente: you can NOT run each and every side effect .


Por que no


Función de pureza? Pureza e Inmutabilidad: las vacas santas de hoy. Y estás matando a uno de ellos. Los axiomas de las pruebas unitarias: sin efectos secundarios, aislamiento, burlas, todo bajo control.


  • Pero eso no es un problema para ... dumb components . Son tontos, contienen solo la capa de presentación, pero no "efectos secundarios".


  • Pero eso es un problema para los Containers . Siempre y cuando no sean tontos, contengan lo que quieran y estén completamente relacionados con los efectos secundarios. Ellos son el problema!



Probablemente, si definimos las reglas de "El Componente Correcto" podríamos probar fácilmente: nos guiará y nos ayudará.


TRDL: el componente finito

Componentes inteligentes y tontos


De acuerdo con Dan Abramov, los componentes de presentación del artículo son:


  • Les preocupa cómo se ven las cosas.
  • Puede contener componentes de presentación y contenedor ** interior y, por lo general, tiene algunas marcas DOM y estilos propios.
  • A menudo permiten la contención a través de this.props.children.
  • No dependa del resto de la aplicación, como acciones o tiendas Flux.
  • No especifique cómo se cargan o mutan los datos.
  • Reciba datos y devoluciones de llamadas exclusivamente a través de accesorios.
  • Raramente tienen su propio estado (cuando lo hacen, es el estado de la interfaz de usuario en lugar de los datos).
  • Se escriben como componentes funcionales a menos que necesiten estado, ganchos de ciclo de vida u optimizaciones de rendimiento.
  • Ejemplos: página, barra lateral, historia, información de usuario, lista.
  • ....
  • Y los contenedores son solo proveedores de datos / accesorios para estos componentes.

Según los orígenes: en la aplicación ideal ...
Los contenedores son el árbol. Los componentes son hojas de árbol.


Encuentra al gato negro en el cuarto oscuro


La salsa secreta aquí, un cambio que tenemos que enmendar en esta definición, está oculto dentro de "Puede contener componentes tanto de presentación como de contenedor ** " , permítanme citar el artículo original:


En una versión anterior de este artículo, afirmé que los componentes de presentación solo deberían contener otros componentes de presentación. Ya no creo que este sea el caso. Si un componente es un componente de presentación o un contenedor es su detalle de implementación. Debería poder reemplazar un componente de presentación con un contenedor sin modificar ninguno de los sitios de llamadas. Por lo tanto, tanto los componentes de presentación como los de contenedor pueden contener otros componentes de presentación o de contenedor perfectamente.

Ok, pero ¿qué pasa con la regla, que hace que la unidad de componentes de presentación sea comprobable: "No tiene dependencias del resto de la aplicación" ?


Desafortunadamente, al incluir contenedores en los componentes de la presentación, los segundos se vuelven infinitos y se inyecta dependencia al resto de la aplicación.


Probablemente eso no sea algo que debías hacer. Entonces, no tengo otra opción, pero hacer que el componente tonto sea finito:


LOS COMPONENTES DE PRESENTACIÓN SOLO DEBEN CONTENER OTROS COMPONENTES DE PRESENTACIÓN


Y la única pregunta que debe hacer (mirando su base de código actual): ¿Cómo? : tableflip:?!


Hoy, los componentes y contenedores de presentación no solo están enredados, sino que a veces no se extraen como entidades "puras" (hola GraphQL).


Solución 1 - DI


La solución 1 es simple: no contenga contenedores anidados en el componente tonto: contenga slots . Simplemente acepte "contenido" (niños), como accesorios, y eso resolvería el problema:


  • puede probar el componente tonto sin "el resto de su aplicación"
  • puede probar la integración con prueba de humo / integración / e2e, no pruebas.

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

Aprobado por el propio Dan:
{% twitter 1021850499618955272%}


La DI (tanto la inyección de dependencia como la inversión de dependencia), probablemente, es la técnica más reutilizable aquí, capaz de hacerte la vida mucho más fácil.


Señale aquí: ¡los componentes tontos son tontos!

Solución 2 - Límites


Esta es una solución bastante declarativa, y podría extender la Solution 1 : simplemente declare todos los puntos de extensión . Solo envuélvelos con ... 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> ); 

Luego, puede desactivar, solo cero, Boundary para reducir el alcance del Componente y hacerlo finito .


Punto aquí: el límite está en el nivel de componente tonto. El componente tonto controla cuán tonto es.

Solución 3 - Nivel


Es lo mismo que la Solución 2, pero con un límite más inteligente, capaz de simular capa o nivel , o lo que usted diga:


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

Incluso si esto es casi similar al ejemplo de límite: el componente tonto es tonto y los contenedores controlan la visibilidad de otros contenedores.

Solución 4 - Preocupaciones separadas


¡Otra solución es solo separar las preocupaciones! Quiero decir, ya lo hiciste, y probablemente es hora de utilizarlo.


Al connect componente a Redux o GQL, está produciendo contenedores conocidos . Quiero decir, con nombres conocidos , Container(WrapperComponent) . Puedes burlarte de ellos por sus nombres

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

Este enfoque es un poco grosero: lo borrará todo , lo que hará que sea más difícil probar los Contaiers, y puede usar burlas un poco más complejas para mantener el "primero":


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

Señale aquí: no hay lógica dentro ni Presentación, no Contenedor: toda la lógica está afuera.

Solución adicional: preocupaciones separadas


Puede mantener un acoplamiento estrecho con defaultProps y anular estos accesorios en las pruebas ...


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

Entonces?


Así que acabo de publicar algunas formas de reducir el alcance de cualquier componente y hacer que sean mucho más comprobables. La forma simple de sacar un gear de la gearbox de gearbox . Un patrón simple para hacerte la vida más fácil.


Las pruebas E2E son excelentes, pero es difícil simular algunas condiciones, que podrían ocurrir dentro de una característica profundamente anidada y estar listas para ellas. Debe tener pruebas unitarias para poder simular diferentes escenarios. Debe tener pruebas de integración para asegurarse de que todo esté conectado correctamente.


Ya sabes, como Dan escribió en su otro artículo :


Por ejemplo, si un botón puede estar en uno de los 5 estados diferentes (normal, activo, flotante, peligro, desactivado), el código que actualiza el botón debe ser correcto para 5 × 4 = 20 posibles transiciones, o prohibir algunas de ellas. ¿Cómo domesticamos la explosión combinatoria de posibles estados y hacemos que la salida visual sea predecible?

Si bien la solución correcta aquí son las máquinas de estado, el requisito básico es poder elegir un solo átomo o molécula y jugar con él.


Los puntos principales de este artículo.


  1. Los componentes de presentación solo deben contener otros componentes de presentación.
  2. Los contenedores son el árbol. Los componentes son hojas de árbol.
  3. No siempre tiene que contener contenedores dentro de los de presentación, pero no debe contenerlos solo en las pruebas.

Puede profundizar en el problema leyendo el artículo medio , pero aquí omita todo el azúcar.

PD: Esta es una traducción del artículo ru- habr versión habr .

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


All Articles