(dalam) Perang Terbatas

perang infitite


Kami punya masalah. Masalah dengan pengujian. Masalah dengan pengujian komponen Bereaksi, dan itu cukup mendasar. Ini tentang perbedaan antara unit testing dan integration testing . Ini tentang perbedaan antara apa yang kita sebut pengujian unit dan apa yang kita sebut pengujian integrasi, ukuran dan ruang lingkup.


Ini bukan tentang pengujian itu sendiri, tetapi tentang Arsitektur Komponen. Tentang perbedaan antara komponen pengujian, perpustakaan mandiri, dan aplikasi akhir.


Semua orang tahu cara menguji komponen sederhana (mereka sederhana), mungkin tahu cara menguji Aplikasi (E2E). Cara menguji hal-hal yang Terbatas dan Tak Terbatas ...


Definisikan masalahnya m


Ada 2 cara berbeda untuk menguji React Component - shallow dan yang lainnya, termasuk mount , react-testing-library , webdriver dan sebagainya. Hanya yang shallow yang istimewa - sisanya berperilaku dengan cara yang sama.


Dan perbedaan ini adalah tentang ukuran, dan ruang lingkup - tentang APA yang akan diuji, dan hanya sebagian caranya .


Singkatnya - shallow hanya akan merekam panggilan ke React.createElement, tetapi tidak menjalankan efek samping, termasuk merender elemen DOM - itu adalah efek samping (aljabar) dari React.createElement.


Perintah lain akan menjalankan kode yang Anda berikan dengan masing-masing dan setiap efek samping juga dieksekusi. Karena itu akan nyata, dan itulah tujuannya.


Dan masalahnya adalah sebagai berikut: you can NOT run each and every side effect .


Kenapa tidak


Fungsi kemurnian? Kemurnian dan Kekekalan - sapi suci hari ini. Dan Anda membantai salah satu dari mereka. Aksioma pengujian unit - tidak ada efek samping, isolasi, ejekan, semuanya terkendali.


  • Tapi itu bukan masalah bagi ... dumb components . Mereka bodoh, hanya berisi lapisan presentasi, tetapi bukan "efek samping".


  • Tapi itu masalah bagi Containers . Selama mereka tidak bisu, mengandung apa pun yang mereka inginkan, dan sepenuhnya tentang efek samping. Mereka masalahnya!



Mungkin, jika kita mendefinisikan aturan "The Right Component" kita dapat dengan mudah menguji - itu akan membimbing kita, dan membantu kita.


TRDL: Komponen Hingga

Komponen pintar dan bodoh


Menurut Komponen Penyajian Artikel Dan Abramov adalah:


  • Khawatir dengan bagaimana hal-hal terlihat.
  • Mungkin mengandung komponen presentasi dan wadah ** di dalamnya, dan biasanya memiliki beberapa markup DOM dan gaya mereka sendiri.
  • Seringkali mengizinkan penahanan melalui this.props.children.
  • Tidak memiliki dependensi pada sisa aplikasi, seperti tindakan atau toko Flux.
  • Jangan menentukan bagaimana data dimuat atau bermutasi.
  • Terima data dan panggilan balik secara eksklusif melalui alat peraga.
  • Jarang memiliki status mereka sendiri (ketika mereka melakukannya, ini adalah keadaan UI daripada data).
  • Ditulis sebagai komponen fungsional kecuali memerlukan status, kait siklus hidup, atau optimalisasi kinerja.
  • Contoh: Halaman, Bilah Samping, Cerita, UserInfo, Daftar.
  • ....
  • Dan Kontainer hanyalah penyedia data / properti untuk komponen-komponen ini.

Menurut asal: Dalam Aplikasi yang ideal ...
Wadah adalah Pohonnya. Komponennya adalah Tree Leafs.


Temukan kucing hitam di ruangan gelap


Saus rahasia di sini, satu perubahan yang harus kami ubah dalam definisi ini, disembunyikan di dalam "Mungkin mengandung komponen presentasi dan wadah ** " , izinkan saya mengutip artikel asli:


Dalam versi yang lebih awal dari artikel ini saya mengklaim bahwa komponen presentasi hanya boleh mengandung komponen presentasi lainnya. Saya tidak lagi berpikir ini adalah masalahnya. Apakah komponen merupakan komponen presentasi atau wadah adalah detail implementasinya. Anda harus dapat mengganti komponen presentasi dengan wadah tanpa mengubah salah satu situs panggilan. Oleh karena itu, komponen penyajian dan wadah dapat berisi komponen penyajian atau wadah lainnya dengan baik.

Oke, tapi bagaimana dengan aturan, yang membuat unit komponen presentasi dapat diuji - β€œTidak memiliki ketergantungan pada aplikasi lainnya” ?


Sayangnya, dengan memasukkan kontainer ke dalam komponen presentasi, Anda membuat yang kedua tanpa batas , dan menyuntikkan ketergantungan ke seluruh aplikasi.


Mungkin itu bukan sesuatu yang harus Anda lakukan. Jadi, saya tidak punya pilihan lain, tetapi untuk membuat komponen bodoh hingga:


KOMPONEN PRESENTASI HARUS MENGANDUNG KOMPONEN PRESENTASI LAINNYA


Dan satu-satunya pertanyaan, Anda harus bertanya (melihat ke basis kode Anda saat ini): Bagaimana? : tableflip:?!


Komponen dan Kontainer Presentasi Hari Ini tidak hanya terjerat, tetapi kadang-kadang juga tidak diekstraksi sebagai entitas "murni" (halo GraphQL).


Solusi 1 - DI


Solusi 1 sederhana - tidak mengandung wadah bersarang di komponen bodoh - mengandung slots . Terima saja "konten" (anak-anak), sebagai alat peraga, dan itu akan menyelesaikan masalah:


  • Anda dapat menguji komponen bodoh tanpa "sisa aplikasi Anda"
  • Anda dapat menguji integrasi dengan tes asap / integrasi / e2e, bukan tes.

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

Disetujui oleh Dan sendiri:
{% twitter 1021850499618955272%}


DI (baik Dependecy Injection maupun Dependency Inversion), mungkin, adalah teknik yang paling dapat digunakan kembali di sini, mampu membuat hidup Anda jauh, lebih mudah.


Poin di sini - Komponen bisu bodoh!

Solusi 2 - Batas


Ini adalah solusi yang cukup deklaratif, dan dapat memperluas Solution 1 - cukup nyatakan semua titik ekstensi . Bungkus saja dengan ... 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> ); 

Lalu - Anda dapat menonaktifkan, hanya nol, Boundary untuk mengurangi cakupan Komponen, dan membuatnya terbatas .


Poin di sini - Batas ada pada level komponen Bodoh. Komponen bodoh mengendalikan bagaimana bodohnya itu.

Solusi 3 - Tingkat


Sama dengan Solution 2, tetapi dengan Boundary yang lebih pintar, mampu mengejek layer , atau tier , atau apa pun yang Anda katakan:


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

Bahkan jika ini hampir mirip dengan Boundary example - Komponen bisu adalah Bisu, dan Kontainer mengendalikan visibilitas Kontainer lain.

Solusi 4 - Kekhawatiran Terpisah


Solusi lain hanya untuk Masalah Terpisah! Maksud saya - Anda sudah melakukannya, dan mungkin sekarang saatnya untuk menggunakannya.


Dengan connect komponen ke Redux atau GQL Anda menghasilkan Kontainer terkenal . Maksud saya - dengan nama-nama terkenal - Container(WrapperComponent) . Anda dapat mengejek mereka dengan nama mereka

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

Pendekatan ini agak kasar - itu akan menghapus semuanya , membuat lebih sulit untuk menguji Contaiers sendiri, dan Anda dapat menggunakan mengejek yang sedikit lebih rumit untuk mempertahankan "yang pertama":


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

Intinya di sini: tidak ada logika di dalam atau Presentasi, bukan Wadah - semua logika ada di luar.

Solusi Bonus - Kekhawatiran Terpisah


Anda dapat tetap menggunakan kopling ketat menggunakan defaultProps , dan membatalkan defaultProps ini dalam pengujian ...


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

Jadi?


Jadi saya baru saja memposting beberapa cara untuk mengurangi cakupan komponen apa pun, dan membuatnya lebih dapat diuji. Cara sederhana untuk mengeluarkan satu gear dari gearbox . Pola sederhana untuk membuat hidup Anda lebih mudah.


Tes E2E bagus, tetapi sulit untuk mensimulasikan beberapa kondisi, yang dapat terjadi di dalam fitur yang sangat bersarang dan siap untuk mereka. Anda harus memiliki unit test untuk dapat mensimulasikan berbagai skenario. Anda harus memiliki tes integrasi untuk memastikan semuanya terhubung dengan benar.


Anda tahu, seperti yang ditulis Dan dalam artikelnya yang lain :


Misalnya, jika sebuah tombol dapat berada di salah satu dari 5 keadaan yang berbeda (normal, aktif, melayang, bahaya, dinonaktifkan), kode yang memperbarui tombol harus benar untuk 5 Γ— 4 = 20 kemungkinan transisi - atau melarang beberapa di antaranya. Bagaimana kita menjinakkan ledakan kombinatorial dari keadaan yang memungkinkan dan membuat keluaran visual dapat diprediksi?

Sementara solusi yang tepat di sini adalah mesin negara, dapat memilih satu atom atau molekul tunggal dan bermain dengannya - adalah persyaratan dasar.


Poin utama dari artikel ini


  1. Komponen presentasi hanya boleh mengandung komponen presentasi lainnya.
  2. Wadah adalah Pohonnya. Komponennya adalah Tree Leafs.
  3. Anda tidak harus selalu TIDAK mengandung Kontainer di dalam Presentational, tetapi tidak hanya memuatnya dalam tes.

Anda dapat menyelam lebih dalam ke masalah dengan membaca artikel menengah , tetapi di sini mari kita lewati semua gula.

PS: Ini adalah terjemahan dari artikel ru- habr versi habr .

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


All Articles