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