Typer Tester: Hvorfor Ikke Begge deler?

0
13

Hver nå og da, en debatt flares opp om verdien av skrevet i JavaScript. “Bare å skrive flere tester!” roper noen motstandere. “Erstatt unit tester med typer!” skriker andre. Begge har rett på noen måter, og feil i andre. gir lite rom for nyanser. Men i løpet av denne artikkelen, kan vi prøve å legge ut en begrunnet argument for hvordan både kan og bør være.

Korrekthet: hva vi alle virkelig ønsker

Det er best å starte på slutten. Hva vi egentlig vil ha ut av alt dette meta-engineering på slutten, er korrekte. Jeg mener ikke de strenge teoretisk informatikk definisjon av det, men en mer generell tilslutning av programmet oppførselen til sin spesifikasjon: Vi har en idé om hvordan vårt program burde fungere i hodene våre, og prosessen med programmering organiserer bits og bytes for å gjøre ideen til virkelighet. Fordi vi ikke alltid presis på hva vi vil, og fordi vi ønsker å ha tillit til at vårt program ikke bryte når vi har gjort en endring, vi skriver typer og tester på toppen av raw-koden vi allerede har for å skrive bare for å få ting til å fungere i første omgang.

Så, hvis vi aksepterer at korrekthet er hva vi vil, og-typer og tester er bare automatiserte måter å få det, det ville være flott å ha en visuell modell av hvordan typer og tester som hjelper oss med å oppnå korrekthet, og derfor forstår hvor de overlapper hverandre og hvor de utfyller hverandre.

En visuell modell av programmet riktigheten

Hvis vi tenker oss hele uendelig Turing-komplett mulig plass av alt programmer noensinne kan muligens gjøre — inclusive feil — som en stor grå flate, så hva vi vil at våre program for å gjøre, vår spesifikasjon er en veldig, veldig, veldig liten undergruppe av at det er mulig plass (den grønne diamant nedenfor, overdrevet i størrelse for moro skyld viser noe):

Vår jobb er å programmere er å argumentere vårt program så nær den spesifikasjonen som mulig (å vite, selvfølgelig, vi er ufullkomne, og vår spec er stadig i bevegelse, f.eks. på grunn av menneskelige feil, nye funksjoner eller under-spesifisert oppførsel, så vi aldri helt klarer å oppnå nøyaktig overlapp):

Legg igjen merke til at grensene for vårt program atferd også inneholde planlagte og ikke-planlagte feil ved anvendelsen av vår diskusjon her. Våre betydningen av “korrekthet” har planlagt feil, men omfatter ikke ikke-planlagte feil.

Tester og Korrekthet

Vi skriver tester for å sikre at våre programmet passer våre forventninger, men har en rekke valg av ting å teste:

Den ideelle tester er den oransje prikker i bildet — de nøyaktig test at vårt program ikke overlapper spec. I denne visualisering, vi egentlig ikke skille mellom ulike typer tester, men du kan forestille deg unit tester som virkelig små prikker, mens integrering/ende-til-ende-tester er store prikker. Uansett, de er prikker, fordi ingen test fullt ut beskriver hver bane gjennom et program. (Faktisk kan du ha 100% kode dekning og fortsatt ikke teste hver vei på grunn av kombinatoriske eksplosjon!)

Den blå prikken i dette diagrammet er en dårlig test. Jada, det tester som vårt program fungerer, men det gjør faktisk ikke feste det til underliggende spec (hva vi egentlig vil ha ut av vårt program, på slutten av dagen). I det øyeblikket vi fikse vårt program for å fokusere nærmere spec, denne testen bryter, gir oss en falsk positiv.

Den lilla prikk er en verdifull test fordi det tester hvordan vi tenker programmet vårt skal fungere, og som identifiserer et område hvor vårt program for øyeblikket ikke. Ledende med lilla tester og fikse programmet gjennomføring følgelig er også kjent som Test-Drevet Utvikling.

Den røde test i dette diagrammet er en sjelden test. I stedet for vanlig (oransje) tester som tester “happy stier” (inkludert planlagte feil states), dette er en test som forventer og bekrefter at “ulykkelig stier” ikke bestått. Hvis denne testen “passerer” der det skal “ikke bestått”, som er en stor tidlig varsling tegn på at noe gikk galt — men det er i utgangspunktet umulig å skrive nok tester til å dekke de enorme expanse av mulige ulykkelig stier som eksisterer utenfor den grønne spec området. Folk sjelden finne verdi teste at ting som ikke fungerer, fungerer ikke, så de ikke gjør det, men det kan likevel være en nyttig tidlig varsling tegn når ting går galt.

Typer og Korrekthet

Hvor tester er enkelt poeng på muligheten plass av hva programmet kan gjøre, typer representerer kategorier carving hele seksjoner fra den totale mulig plass. Vi kan visualisere dem som rektangler:

Vi plukker et rektangel for å kontrast diamant som representerer programmet, fordi det ikke er noen type system alene kan helt beskrive vårt program atferd ved hjelp av typer alene. (For å hente et trivielt eksempel på dette, en id som alltid skal være et positivt heltall er en nummertype, men antall type aksepterer også fraksjoner og negative tall. Det er ingen måte å begrense antall skriv til et bestemt område, utover en veldig enkel union av antall verdiane.)

Typer tjene som en begrensning på hvor vårt program kan gå som du koden. Hvis vårt program begynner å overskride de angitte grensene for programmet typer, vår type-brikken (som maskinskrevet kopi eller strøm) vil rett og slett nekter å la oss kompilere programmet vårt. Dette er fint, fordi i et dynamisk språk som JavaScript, det er veldig enkelt å skulle lage en krasj program som absolutt ikke var noe du hadde tenkt. Den enkleste verdi legge til er automatisert null kontroll. Hvis foo har ingen metode som kalles bar, så ringer foo.bar() vil føre til altfor velkjente udefinert er ikke en funksjon runtime unntak. Hvis foo ble skrevet på alle, kan dette ha blitt fanget opp av type-stavekontroll mens du skriver, med spesiell henvisning til det problematiske linje med kode (med autofullfør som en samtidig fordel). Dette er noe som tester rett og slett ikke kan gjøre.

Vi ønsker kanskje å skrive strenge typer for vårt program, som om vi prøver å skrive minst mulig rektangel som fortsatt passer vår spec. Dette har imidlertid en læringskurve, fordi å ta full nytte av typen systemer innebærer å lære et helt nytt syntaks og grammatikk operatører og generisk type logikk er nødvendig å modellere hele det dynamiske området for JavaScript. Håndbøker og Cheatsheets bidra til å redusere denne læringskurven, og mer satsing er nødvendig her.

Heldigvis, dette adopsjon/læringskurven har ikke til å stoppe oss. Siden type-sjekking er en opt-in prosessen med Flyt og konfigurerbar strenghet med maskinskrevet kopi (med mulighet til å velge å ignorere plagsom linjer med kode), har vi plukke fra et spekter av typen sikkerhet. Vi kan også modell for dette, også:

Større rektangler, som den store røde i figuren ovenfor, representerer en svært flytende adopsjon av en type system på codebase — for eksempel, slik at implicitAny og fullt avhengig av type slutning å bare begrense vårt program fra det verste av våre koding.

Moderat strenghet (som medium-size-grønn rektangel) vil kunne representere en mer trofast å skrive, men med rikelig med rømme luker, som ved hjelp av eksplisitt forekomster av alle over codebase og manuell type påstander. Det er likevel mulig areal av gyldig programmer som ikke samsvarer med våre spec er massivt redusert selv med denne lette å skrive arbeid.

Maksimal strenghet, som den lilla rektangel, holder ting så tett til vår spec at det noen ganger finner deler av programmet som ikke passer inn (og disse er ofte uforutsette feil i programmet atferd). Å finne feil i en eksisterende program som dette er en veldig vanlig historie fra lagene konvertering vanilje JavaScript codebases. Imidlertid, å få maksimal type sikkerhet ut av vår type-checker sannsynlig innebærer å ta nytte av generiske typer og spesielle operatorer som er utformet for å avgrense og innsnevre mulig plass typer for hver variabel og funksjon.

Legg merke til at vi ikke teknisk nødt til å skrive vår første program før du skriver typer. Tross alt, vi vil bare vår typer å følge nøye med modellen vår spec, så egentlig kan vi skrive våre typer første og så etterfylle gjennomføring senere. I teorien, ville dette være Type-Drevet Utvikling; i praksis er det få mennesker faktisk utvikle denne måten siden typer nært gjennomsyre og interleave med våre selve programkoden.

Å sette dem sammen

Hva vi til slutt bygger opp til er en intuitiv visualisering av hvordan både typer og tester utfylle hverandre i noe som garanterer vårt program korrekthet.

Våre Tester hevde at vårt program spesielt utfører slik det er ment i velg-tasten stier (selv om det er visse andre varianter av testene som er omtalt ovenfor, er det store flertallet av tester gjøre dette). I språk av visualisering har vi utviklet, de “pin” den mørke grønne diamant i programmet vårt til lys grønn diamant av våre spec. Enhver bevegelse bort av vårt program bryter disse testene, noe som gjør dem squawk. Dette er utmerket! Testene er også uendelig fleksibel og kan konfigureres for de mest tilpasset for bruk saker.

Våre Typer hevde at vårt program ikke kjører som det bort fra oss ved å tillate mulig svikt moduser utover en grense som trekker vi, forhåpentligvis så stramt som mulig rundt vår spec. I språket vårt visualisering, de “inneholde” mulig drift av vårt program bort fra vår spec (som vi alltid er ufullkomne, og hver eneste feil du gjør vi legger til flere feil virkemåten til programmet vårt). Typer er også sløv, men kraftig (på grunn av type slutning og redaktør verktøy) verktøy som har nytte av et sterkt fellesskap leverer typene du trenger ikke å skrive fra bunnen av.

Kort sagt:

  • Tester som er best på å sikre glad stier arbeid.
  • Typer er best på å hindre ulykkelig stier fra eksisterende.

Bruk dem sammen basert på deres sterke sider, for best resultat!

Hvis du ønsker å lese mer om hvordan Typer og Tester krysser hverandre, Gary Bernhardt er utmerket snakke om Grenser og Kent C. Dodds’ Testing Trophy var betydelig innflytelse i min tenkning for denne artikkelen.