Testlash o'zi nimaga kerak, tinchgina yashasak bo'lmaydimi!
Tags: javascript, testing, webdev, programming
Published: 2023-03-21 02:28:22

### Bismillah!

> _Taxminiy o'qish vaqti: ~5 daqiqa_

Assalom Alaykum!

O'tgan qismimizda asosan `frontend` dagi relizgacha bo'lgan barcha jarayonlarni umumlashtirilgan shalkda ko'rib chiqdik. Bu va bundan keyingi qismlarda dastlabki maqolada keltirilgan mavzularga kengroq kiramiz va ularni qanday oddiy setup qilishlikni ko'rib chiqamiz. Bugun shu mavzularga kirish qismi qilamiz!

> _Ushbu turkum maqolalarda ishlatilgan barcha kodlarni quyidagi github repodan topishingiz mumkin: [testing-demo-app](https://github.com/humoyun/testing-demo-app). Alohida repodan asosiy maqsad ham bu mavzularni iloji boricha amaliy ko'rinishga keltirishdir._
> {% github humoyun/testing-demo-app %}


### Testlash o'zi nimaga kerak, tinchgina yashasak bo'lmaydimi!

> _Spoiler_: **_Ground truth of testing  is the confidence!_** 😊 

Hozirgi kunda sifatli dasturiy ta'minotni ishlab chiqish jarayonini testlarsiz tasavvur qilish qiyin. Yozayotgan kodimiz doimo to'g'ri ishlashiga ishonch hosil qilishimiz uchun albatta ularni testlar bilan himoyalab qo'yishimiz lozim. Chunki tabiatan har qanday dasturiy ta'minot vaqt o'tishi bilan o'zgarishlarga mahkum va har qanday o'zgarishlardan keyin ham uning to'g'ri ishlayotganiga ishonch hosil qilishimiz uchun ham testlarni yozamiz. Ho'p test yozish muhim ekan lekin ularni qanday yozamiz, ularning qanday turlari bor, ularni yozishda qandaydir usullar, qonun qoidalar ham bormi... Shu va shunga o'xshash savollar test yozishni yangi boshlayotganlarda bo'lishi tabiiy. Keling shular haqida biroz gaplashamiz. 

> _**Little story**_: Esimda, bundan taxminan 3 yilcha oldin [Agoda](https://www.agoda.com) bilan bo'lgan intervyumda Agodani senior engineeri mendan: hozirgi ishxonangda testlarni qanday yozaslar deb soragandi. Uhh dedim ichimda, xo'sh testlar yozish nimaligini bilaman, ahamiyatini ham, o'zimcha bir nechta test yozib ko'rganman, lekin baribir ishxonamda yozmaymizku. Aldashni keragi yo'q, deb to'g'risini aytdim. Bizda test yozilmaydi, chunki startup bo'lganimiz uchun vaqtimiz kam (oo ajoyib bahona keltirdim deb o'yladim ichimda). U esa menga kulib, bizni ham vaqtimiz kam, vaqtimiz yana ham kamaymasligi uchun test yozamiz deb javob berdi. Shu suhbatdan keyingina, testlarning haqiqiy ahamiyatini tushundim (shekilli?).

Har qanday ishga tizimli tarzda yondashish doimo yaxshi natija beradi. Testlarni yozishda bunday tizimli yondashuv odatda _**testlash strategiyasi**_ deb nomlangan bo'lib ular turli test turlarini qanday kombinatsiyalarda bo'lishini muayyan bir qolipga solib tashkillashtirishni anglatadi. Dasturchilar orasida ko'p tarqalgan bunday strategiyalardan biri bu [Testing Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html)dir. Unga ko'ra `UI` testlar (ba'zida `e2e` testlar nazarda tutiladi) bu eng qimmat, eng ko'p vaqt talab qiladigan va eng sekin ishlaydigan testlardir, unit testlar esa aksincha eng tez va eng oson yoziladigan, kam mashaqqat talab qiladigan jarayondir. 

> _**Unit testlar** bu dastur kodining eng kichik va o'zi mustaqil biron amal bajara oladigan bo'lagini testlaydi xuddi funksiya yoki React component kabi. Ularni `run` qilishimiz uchun bizga browser, server yoki database kerak emas shuning uchun ularni ishlashi juda tez._ 
> _**UI testlar**ni (odatda e2e nazarda tutiladi) `run` qilish uchun esa butun boshli dasturni browserda ishga tushirish kerak bo'ladi, odatda haqiqiy server va database bilan birgalikda, so'ngra esa bu testlar hosil qilgan `trash data`ni tozalalashimiz (o'chirish) kerak. Yuqorida ham aytib o'tilganidek, bu ko'p vaqt oladigan jarayon, va ularni yozish ancha mashaqqatliroq._ 
> _**Integration testlar** esa (service yoki funksional deb ham nomlanadi ba'zida) bu ikki turdagi testlarni orasida joylashgan. Ular dasturning bir nechta o'zaro bo'gliq qismlarini tekshiradi lekin browsersiz va odatda haqiqiy serversiz._


**_Demak biz ko'plab unit testlar va birozgina UI testlar yozishimiz kerak, shundaymi?_**

Bu uslub sinalgan va ishlaydigan strategiya bo'lishiga qaramay, `frontend`ga qo'llashda o'ziga yarasha kamchiliklari ham bor, bundan tashqari bu strategiya tanishtirilgan vaqtni ham hisobga olish juda muhim. `Ajax` revolyutsiyasidan oldin JavaScript odatda formalarni validatsiya qilish va sodda animatsiyalarni hosil qilish kabi oddiy amaliyotlar uchungina ishlatilalardi va u paytlari testing uchun ayniqsa `frontend`da testlar yozish uchun vositalar kam va imkoniyatlari cheklangan bo'lishiga qaramay o'sha vaqtdagi talab va ehtiyojlariga javob bera olardi. Lekin so'nggi 10 yillikda web va uning atrofidagi texnologiyalarning soni va imkoniyatlari ancha kengaydi bu esa `frontend`ning ancha murakkablashuviga olib keldi. **SPA** ya'ni `client-side routing`, `client-side state management` va `client-side rendering` kabi texnologiyalar va bular atrofidagi juda ko'plab `framework`lar va `library`lar web dasturlash olamiga kirib keldi va testlashdagi oldingi usullar, texnologiyalar va vositalar hozirgi kun `frontend`dagi testlash talablarini va ehtiyojlarini to'liq qondira olmay qoldi. Bunga yana quyidagi sabablarni ham ko'rsatish mumkin:

- `frontend` da UI doimiy o'zgarishda (funksional hech narsa o'zgarmasa ham), bu esa ko'p yozilgan unit testlarni fail bo'lishiga olib kelaveradi va har doim biron `styling` bilan bog'liq o'zgarish qilganimizda unit testlarni o'zgartirishimizga to'g'ri keladi.
- unit testlar qanchalik ko'p bo'lmasin va barchasi muvaffaqiyatli ishlamasin agar ular tasdiqlab bergan dasturning qismlari birgalikda to'g'ri ishlamasa unda ulardan katta naf bo'lmaydi. Buning muqobilida, agar bu qismlar birgalikda to'g'ri ishlasa unda juda ko'p unit testlarni yozishga ham hojat qolmaydi. Chunki shuning o'zi dasturning imkoniyatlari to'g'ri ishlayotgani haqida bizga yetarli `confidence` ya'ni ishonch darajasini bera oladi.

> Balki frontendda test yozishda sal boshqacharoq yondashuv kerakdir?

Frontendda testlashning zamonaviy strategiyalardan biri bu [**Testing Trophy**](https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications). 

| Modern Testing Trophy        | Standard Testing Pyramid  |
|  :------------------:        |  :----------------------: |
| ![testing-trophy-by-kent-c-dodds](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tzyrbcvw9d4dxe9o6r6x.jpeg)| ![standard-testing-pyramid](https://cdn-contents.anymindgroup.com/corporate/wp-uploads/2021/09/28103833/pyramid.png)|


### Testing Trophy :trophy:

Bu strategiya ko'ra eng katta **ROI** (`return on investment`) bu integration testlarda. 
 
> _**ROI** - sarflangan xarajat yoki mashaqqatlarga nisbatan keltirilgan natija yoki foyda._

Integration testlar dasturning muayyan imkoniyatlarini (`feature`) yoki butun boshli sahifani to'liq tekshiradi  lekin odatda haqiqiy backend va databasega ulanmagan holatda. Masalan, quyidagi `auth flow` ni misol qilib olishimiz mumkin (Login sahifasini):

1. Login sahifamizni `render` qilamiz (oldinlari odatda browsersiz [JSDOM](https://github.com/jsdom/jsdom) kabi headless `runtime`da edi hozir esa [Cypress](https://www.cypress.io) kabi zamonaviy testlash vositalari yordamida to'g'ridan to'g'ri browserni o'zida ham `render` qilsa bo'ladi)
2. _username_ va _password_ input elementlarini topamiz va kerakli test user `credential`larni kirgazamiz.
3. `Sign In` buttonni topamiz va `Click`.
4. To'g'ri `request` yuborilganini tekshiramiz.
5. Oxirgi bosqich holatga qarab har xil, misol uchun:
   - Login sahifasidan dasturning asosiy qismiga `redirect` qilinganligi va biron textning asosiy sahifada borligini tekshirish yoki...
   - `SessionId` yoki `JWT token` ni **_cookie_** yoki **_localStorage_**ga yozilganligini tekshiramiz yoki...
   - va hokazo

Lekin bu jarayonda `network request`lar serverga yetib bormaydi va **msw** kabi vositalar yordamida bu `request`lar `intercept` qilinib mock natijalar qaytariladi  bunga keyinroq to'liqroq misol bilan to'xtalamiz.

Keling endi unit va integration testlarni solishtiramiz:

<table>
  <tbody>
    <tr>
      <th align="center">Unit testlar</th>
      <th align="center">Integration testlar</th>
    </tr>
    <tr>
      <td>
        <ul>
         <li> bitta test faqat bitta kichik dastur qisminigina qoplay oladi (e.g. funksiya yoki component)</li>
         <li> odatda har bir refactoringdan keyin o'zgartirishga to'g'ri keladi. </li>
         <li> odatda implementation detallaridan holi tarzda testlash qiyin ya'ni testdagi ketma-ketliklar foydalanuvchilarning dasturni ishlatishdagi ketma-ketliklariga mos tushmaydi </li>
         <li> LOW confidence. :droplet:</li>
        </ul>    
      </td>
      <td>
        <ul>
<li style="font-style: italic"> bitta test butun boshli userflow, featureni yoki hatto sahifani ham qoplaydi. </li>
<li> odatda refactoringdan o'zgarishsiz chiqadi </li>
<li> implementation detallaridan deyarli holi va foydalanuvchilar dasturni ishlatishidagi ketma ketliklarni yaxshi aks ettiradi</li>
<li> HIGHER confidence. :muscle: </li>
        </ul>
      </td>

    </tr>
  </tbody>
</table>

Oxirgi farq bu yerda eng muhim jihatlardan biridir ya'ni turli testlar beradigan turli ishonch darajalari (`level of confidence`)! Integration testlar bizga dasturiy ta'minotimiz kutilganidek ishlayotganiga yetarli darajada ishonch bera oladi. Bu qolgan testlarni yozish kerak emas degani ham emas, shunchaki asosiy vaqt va e'tiborni integration testlarga berish bizga yaxshi natija olib keladi.

> _**user flow** - dasturda muayyan natijaga foydalanuvchi  qanday erishishini ko'rsatuvchi ketma-ketlikdir (`e-commerce` appda muayyan mahsulotni topish, uni korzinkaga qo'shish va checkout qilish)._

> _**implementatsiya detallari** - dasturimizning ichki ishlash mexanizmi bo'lib foydalanuvchilarga ko'rinmaydi va dasturning spesifikatsiyasida ham ko'rsatilmaydi. Dasturchini ishlashi jarayonida hosil bo'lib boradi va odatda vaqt o'tishi bilan o'zgarishlarga mahkum._

Quyida integration testlarni muhimroq ekanligi haqida keltirilgan ajoyib xazilomuz gif (original tweet muallifi [_Erin McGrath_](https://twitter.com/erinfranmc/status/1148986961207730176))
```
expect(umbrellaOpens).toBe(true)

tests: 1 passed, 1 total

**all unit tests passed, hooray**
```

![all tests passed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pd9lnuzi8w3hgto9pbdr.gif)

Demak xulosa:

![write mostly integration tests](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6e9lzi5eh9f2hsnctc8x.png)

Xo'sh testlash, testlarning turlari va testlash strategiyasi haqida bilib oldik va Testing Trophy strategiyasini `frontend` uchun tanlab oldik. Keyingi maqolalarda ularning har bir qatlami bilan alohida alohida tanishib chiqamiz.