Testování

Radim Štěpaník

QEST, CTO

Testování?

Testování?

- není to o tom že někdo kliká ale jde o věci jako:
    - jak píšeme naši aplikaci
    - míra automatizace testů
    - proces vývoje
    - CI/CD
    - komunikační schopnosti v rámci týmu
    - jak funguje process
    - monitoring a logování

Proč vůbec testovat?

Nebudeme mít žádné bugy 🐛

Proč vůbec testovat?

Testování je drahé

Proč vůbec testovat?

Budeme testovat až nakonec

Testování v rámci procesu

Role testera

  • Kdo to je?
  • Co by měl mít za vlastnosti
  • problém že to spousta lidí bere jako vstupní pozici 
  • Klikač
    • těžká pozice
    • náročná repetitivní práce
    • nejlepší uživatel

Role testera - kam se chceme dostat

  • HARDCORE tester 🤯
    • moderní pozice
    • automatizátor
    • čte logy
    • QA 
    • monitoring 
    • nastavení testovacích prostředí
    • nechce tupě klikat

Process a role testingu

  • Jasně definovaný vývojový process
  • víme co je otestováno 

Správně popsané tickety - DOD, DOR

User story

Akceptační kritéria

Subtasky (nezaměřit tickety na technologie
ale na business)

GIT

Release do produkce

  • alpha, beta, preprod?, produkce vs review apps - speciální release per feature
  • automatizace vs manuální proces (další slidy)
  • CD vs release
    • mobilní aplikace - spíše klasický release (hybrdidní aplikace OTA udpates)
    • webové aplikace - fail fast

Release do produkce

Release do produkce

Regresní testování

Regresní testování

Regresní testování vs CD

  • mnohem těžší - horší uchopení 
  • spíše na pravidelné bázi, ale nesouvisí s ověřením správnosti release
  • měl bych se spolehnout spíše na automatizaci 

Role vývojářů a testingu

  • do jaké míry kontrolují po sobě vlastní práci - obecně problém
  • chce to trpělivost a zkušenosti
  • směřovat k tomu, že chci dotáhnout svůj ticket až do produkce

Automatizaceautomatizace a zase ta automatizace

Co není automatizované tak prostě ve světě IT nefunguje.

(motivace pro začátek - ukazujte si čísla)

Automatizaceautomatizace a zase ta automatizace

  • součást každého ticketu
  • dopady na psaní kódu
  • TDD
  • databáze
  • vývojový proces
  • dobré návyky vývojářů

 

Co používáme za nástroje

  •  E2E testy
    • cypress testy, wdio
  • Integrační testy
    • cypress
  • Integrační testy/unit
    • jest

Co testovat na frontendu?

  •  Backend většinou není problém pokrýt na 100 procent
  • Jak ale na frontend
    • 20 procent - oddělovat business logic do jednoduchých fcí - unit testing
    • 75 procent zbytek E2E cypress testy
    • 5 procent unit testy komponent

A co backend?

Co nám říká teorie

  •  rozdělení testů
    • unit testy
    • integrační testy
    • funkční testy

 

Sedí tento model na to co děláme?

  • monolitické aplikace
  • serverless, mikroslužby, služby
  • nechci psát kód který nepotřebuji
    • v kódu navíc můžu udělat chybu

Sedí tento model na to co děláme?

  • integrační testy rulezz 💪
  • implementační detaily
  • integrated testy
https://engineering.atspotify.com/2018/01/11/testing-of-microservices/

Co mockovat a co ne

  • obecně je dobré mít "naostro" vše co dokážeme v rámci aplikace ovlivnit
    • databáze - používáme 
    • API 3. stran - mockovat

Jak se tomu vyvarovat

  • nastavit si robustnější přístup k architektuře
  • závislosti v kódu
    • OOP - dependency injection
    • functional prorgramming - pure functions
  • psát kód tak, abychom nepředávali zbytečné závislosti❗️

Aplikace jako složený container

  • všechny součástky aplikace vnímáme stejně, jsou to dílky skládačky
  • v rámci zdrojového kódu existují pouze implementace jednotlivých komponent
  • každá komponenta, služba, controller definuje rozhraní se kterým komunikuje s ostatními 
    • jednoznačná definice závislostí, toho co je zapotřebí

Aplikace jako složený container

  • v rámci integrace - jednoduše mohu otestovat jakoukoliv službu nebo controller, služba je prostě vytažena z kontejneru a testována
  • v rámci izolovaného testu - díky jasně definovanému rozhraní mohu vytvořit službu pro potřeby testu (předám mock definice)
  • mockování - díky tomu, že jsou služby předané v kontejneru a mohu si je odsud vytáhnout, mohu namockovat jakoukoliv službu nebo její metodu

Dopady na psaní kódu

  • integrační testy rulezz 💪
  • unit testování "těžkých"
    komponent
  • aplikace container

Příklad 

  • Jak to udělat lépe?

Integration tests

Special test for HRSystem

Testing of business logic

Dopady na psaní kódu

  • psát kód tak, aby bylo možné otestova jakoukoliv část aplikace bez obtíží
  • nese nároky na strukturu aplikace
  • škálovat kód do menších bloků 
  • využití dependency injection nebo funkcionálního programování

Testy blízko vašeho kódu

  • zjednodušení při code reviews
  • jasně viditelné změny
  • neděláme rozdíly co je integrační test a co unit test

Testování jako dokumentace aneb archeologie v praxi 🙉

  • jako správně dokumentovat funkcionalitu systému?
  • proč je v kódu takhle divná věc
  • jak se používá tahle knihovna?
  • myslete na to, že testy a jejich popis můžou někomu jednou hodně pomoct

Testování - mikroslužby

  • obecně problém distribuovaných systémů - těžko testovatelné
  • nároky na dobrou architekturu a dobré návyky, infrastrukturu etc. 
  • testování kontraktů
    • kontrakty vs dynamicky typovované jazyky 
    • jak z toho ven s javascriptem?

Slovo závěrem 👋

  • budťe připraveni na změnu a nebojte se je dělat ❗️
  • nebuďte klikači UI a postmana
  • zdokumentujte to jakým způsobem jste přemýšleli nad tvorbou featury
  • myslete na ty co přijdou po vás

Díky za pozornost

Dopady na psaní kódu

Příklad - Aplikace Cirkus.io🎪

  • Aplikace 🎪 Cirkus - webová aplikace, představení, uživatelé kupují lístky na představení
  • It's to simple!
  • Zaměříme se na use case získání uživatele 
export const getUser = async (req: Request, res: Response) => {
    const user = await UserMongooseModel.findOne({entityId: req.params.userId}).exec()
    return res.json(user);
};

Příklad - Aplikace Cirkus.io🎪

  • Pokud uživatel nemá nastaveného avatara přiřaď default
const DEFAULT_AVATAR = "/defaultAvatar.jpg"

export const getUser = async (req: Request, res: Response) => {
    const user = await UserMongooseModel.findOne({entityId: req.params.userId}).exec()
    const avatar = user.avatar ?? DEFAULT_AVATAR
    return res.json({...user, avatar});
};

Příklad - Aplikace Cirkus.io🎪

  • Všem uživatelům s více než 50 vstupy nastav členství gold
const DEFAULT_AVATAR = "/defaultAvatar.jpg"

export const getUser = async (req: Request, res: Response) => {
    const user = await UserMongooseModel.findOne({entityId: req.params.userId}).exec()
    const avatar = user ?? DEFAULT_AVATAR

    const numberOfVisits = await EntranceHistory.find({userId: req.params.userId})
      .count()
      .exec()

    let membership = "basic"

    if (numberOfVisits > 50 ) {
       membership = "gold"
    }

    return res.json({...user, avatar, membership});
};

Příklad - Aplikace Cirkus.io🎪

  • Pokud se jedná o personál cirkusu přiřaď speciální příznak
const DEFAULT_AVATAR = "/defaultAvatar.jpg"

export const getUser = async (req: Request, res: Response) => {
    const user = await UserMongooseModel.findOne({entityId: req.params.userId}).exec()
    const avatar = user ?? DEFAULT_AVATAR

    const numberOfVisits = await EntranceHistory.findOne({userId: req.params.userId}).count().exec()
    let membership = "basic"

    if (numberOfVisits > 50 ) {
       membership = "gold"
    }
    const hrSystemApi =  axios.create(config.internalHRSystemUrl)
    const response =  await hrSystemApi.get(`/user/${req.params.userId}`)
    const meta = {hrSystemInfo: response.data}

    return res.json({...user, avatar, membership, meta});
};

Příklad - Aplikace Cirkus.io🎪

  • Pokud je uživatel zaměstnanec, byl narozen za úplňku a zároveň je dneska venku hezky ☀️
const DEFAULT_AVATAR = "/defaultAvatar.jpg"

export const getUser = async (req: Request, res: Response) => {
    const user = await UserMongooseModel.findOne({entityId: req.params.userId}).exec()
    const avatar = user ?? DEFAULT_AVATAR

    const numberOfVisits = await EntranceHistory.findOne({userId: req.params.userId}).count().exec()
    let membership = "basic"

    if (numberOfVisits > 50 ) {
       membership = "gold"
    }
    const hrSystemApi =  axios.create(config.internalHRSystemUrl)
    const response =  await hrSystemApi.get(`/user/${req.params.userId}`)
    const meta = {hrSystemInfo: response.data}

    const calendarApi =  axios.create(config.calendarApiUrl)
    const isFullMoon =  (await calendarApi.get(`/date/${user.birthDate}/isFullMoon`)).data

    const wheaterApi =  axios.create(config.wheaterAppUrl)
    const wheater =  (await wheaterApi.get(`/whater`)).data


    if (meta.hrSystemInfo && isFullMoon && wheater.temperature > 30 && wheater.currentConditions === "sunlight") {
        membership = "supergold"
    }


    return res.json({...user, avatar, membership, meta});
};

Příklad - Aplikace Cirkus.io🎪

  • Co to pro nás znamená
    • v rámci jedné funkce sleduji několik různých stavů
    • pro otestování business funkcionality musím jít poměrně hluboko do implementace
    • počáteční nastavení testu se stává velmi neprůhledné, čím více vstupních proměnných do funkce, tím větší pravědpodobnost že se stane chyba
    • side effects - co když selže některé API? 

Příklad - Aplikace Cirkus.io🎪

  • Jak to udělat lépe?
export const getUser = (fullUserService: FullUserService) => {
    return async (req: Request, res: Response) => {
        const user = fullUserService.getUser((req.params.id))
        return res.json(user);
    };
}


class FullUserService {
    constructor(
        private dataProvider: FullUserDataProvider, 
        private membershipResolver: MembershipResolver
    ) {}

    async getUser(id: string): FullUser {
        const user = this.dataProvider.getUser(id)

        const [ hrSystemInfo, wheater, numberOfVisits] = await Promise.all([
            this.dataProvider.getWheater(),
            this.dataProvider.isFullMoon(user.birthDate),
            this.dataProvider.numberOfVisits(user)
        ])
        const membershipData = {user, hrSystemInfo, wheater, numberOfVisits}
        const membership = this.membershipResolver.resolve(membershipData)
        return {...user, membership}
    }
}

Node.js tooling - supertest/apollo 

  •  jest, mocha
  • supertest - pro 

První pokusy o testování

  •  skvělý projekt pro testování
  • skvělé nástroje proto to udělat 
  • proč to nefungovalo?

Testování aplikací

By Radim Štěpaník

Testování aplikací

  • 359