Å starte CS50P denne måneden føltes litt bakvendt, ærlig talt. Jeg har allerede sendt ut et Django-CRM, et kamerascript som sender meg e-post, et nyhetssammendrag, en håndfull scrapere og PDF-verktøy, to boot.dev-prosjekter (bookbot, webflyx). CS50P er "intro til Python"-kurset. Jeg vet allerede hva en for-løkke er.
Jeg gjorde det likevel, og jeg er glad jeg gjorde det, fire uker inn.
hva grunnleggende-kurs faktisk sjekker
Ikke om du kan skrive en for-løkke. Om du kan skrive en som en annen person, eller check50, eller fremtidig-meg om seks måneder, faktisk kan lese og stole på. CS50Ps test_-filkonvensjon, faktisk å skrive ekte pytest-tester i stedet for å øyemåle output i terminalen, var det som gjorde meg litt flau. Jeg har skrevet "ekte" script en stund og testet praktisk talt ingen av dem.
den første testfilen jeg skrev, som teknisk sett besto og beviste ingenting
Ukens oppgave fikk meg til å skrive en liten input-valideringsfunksjon, som sjekker at en skiltlignende kode følger et sett med formateringsregler, og skrive tester ved siden av. Mitt første forsøk på testfilen så slik ut:
from project import is_valid
def test_valid():
assert is_valid("CS50P")
def test_invalid():
assert not is_valid("cs50p")To tester. Begge besto. Grønne haker, føltes flott i omtrent fire minutter, helt til check50 kjørte sine egne skjulte tester mot is_valid-funksjonen min og feilet på en input jeg aldri faktisk hadde prøvd: et skilt med et tall i midten, som AB12CD, som funksjonen min feilaktig godtok.
:( is_valid rejects plates with a letter after a digit
expected "False", not "True"det faktiske problemet, når jeg først så
# project.py, the broken version
def is_valid(s):
return s[:2].isalpha() and s[2:].isalnum()Sjekker at den starter med bokstaver og resten er alfanumerisk. Sjekker ikke den faktiske rekkefølgen reglene krever, at når et tall først dukker opp, kan ingenting annet enn tall følge det. AB12CD består denne sjekken helt: bokstaver i starten, alfanumerisk resten, teknisk sett sant, feil svar.
Mine egne to tester hadde ikke fanget dette fordi jeg bare hadde testet ett åpenbart gyldig tilfelle og ett åpenbart ugyldig tilfelle. Ingen av dem var i nærheten av den faktiske kanten, som er nøyaktig der den ekte buggen skjulte seg.
det jeg faktisk googlet
"pytest test edge cases how many test cases is enough" ledet meg, til slutt, til ideen om tabelldrevne eller parametriserte tester, å skrive en liste med tilfeller på forhånd (input, forventet resultat) i stedet for én assert om gangen, spesifikt slik at det å legge til et nytt kanttilfelle bare er å legge til én rad i stedet for å skrive en helt ny funksjon.
import pytest
from project import is_valid
@pytest.mark.parametrize("plate, expected", [
("CS50P", True),
("cs50p", False),
("AB12CD", False),
("A1", False),
("", False),
])
def test_is_valid(plate, expected):
assert is_valid(plate) == expectedFem tilfeller i stedet for to, og å skrive dem ut som en tabell gjorde hullet i min egen tenkning åpenbart før check50 måtte påpeke det for meg. AB12CD var rett der så snart jeg ble tvunget til faktisk å liste opp kanttilfeller i stedet for å plukke to som føltes representative.
den fikserte funksjonen
# project.py, the fixed version
def is_valid(s):
seen_digit = False
for ch in s:
if ch.isdigit():
seen_digit = True
elif seen_digit:
return False
return len(s) >= 2 and s[:2].isalpha()Går gjennom strengen én gang, og i det øyeblikket en bokstav dukker opp etter at et tall allerede er sett, er den ugyldig. Besto mine utvidede lokale tester og check50s skjulte ved neste innsending.
omdøpingsscriptet, gjenbesøkt
Husker du bilde-omdøpingsscriptet jeg skrev for en stund siden, det som krasjet på en filnavnkollisjon første gang jeg kjørte det? Jeg gikk tilbake og så på det etter en CS50P-forelesning om unntak, og fortid-meg hadde pakket ingenting inn i en try-blokk, bare latt det krasje og fikset buggen ved å lese tilbakesporingen etterpå. Fungerte, teknisk sett. Ikke akkurat den anbefalte tilnærmingen.

Tilbake med friske øyne, fiksen handlet ikke egentlig om unntak i det hele tatt, det var at jeg aldri en eneste gang hadde vurdert hva som skulle skje når et omdøpingsmål allerede eksisterer, bare oppdaget det ved å se tingen krasje. En try/except FileExistsError rundt selve os.rename-kallet, som faller tilbake til et nummerert suffiks, ville gjort den første versjonen riktig dag én i stedet for riktig på det fjerde feilsøkingsforsøket.
en liten sidebemerkning om hvor lenge jeg kjempet mot forelesningsvideoen i stedet for koden
Genuint mistet omtrent førti minutter overbevist om at det var en bug i pytest selv, fordi den parametriserte testen min fortsatte å rapportere feil forventet verdi i feilmeldingen, og viste False når jeg var sikker på at jeg hadde skrevet True for den raden. Det var ingen bug. Jeg hadde tellet feil på rader mens jeg la til et nytt tilfelle midt i listen og forskjøv hver påfølgende forventede verdi med én, slik at rad fires skilt ble sjekket mot rad fems forventede svar. pytests feilmelding var helt riktig hele tiden. Jeg trodde bare ikke på det før jeg skrev ut hele parametrize-listen og telte for hånd som om det var en staveprøve. Tabelldrevne tester er bare like pålitelige som tabellen, som er åpenbart i ettertid og ikke var åpenbart i det hele tatt ved minutt tretti av å stirre på en rød X.
hva som faktisk er annerledes denne gangen
Jeg lærer ikke syntaks. Den har jeg allerede, mest ved et uhell, fra bare å bygge ting som måtte fungere. Det CS50P faktisk gir meg er greiene ingen peker direkte på før et kurs gjør det: ordentlig bibliotekbruk i stedet for å kopiere-lime Stack Overflow til det kompilerer, faktisk testdisiplin, kode som leser som den ble skrevet for en person og ikke bare for at tolkeren skal godta den.
Det er en rar type ydmykende. bookbot og webflyx fungerte begge, ble sendt ut, gjorde tingen de skulle gjøre. Ingen av dem ville overlevd en kodegjennomgang fra noen som faktisk visste hva de drev med, og jeg tror egentlig først det nå som jeg har sett hvordan alternativet ser ut, tabelldrevne tester inkludert.
hva jeg ville gjort annerledes fremover
Skrive tabellen med kanttilfeller før jeg skriver funksjonen, ikke etter. Hver bug jeg har truffet på i dette kurset så langt satt i gapet mellom "de to tilfellene jeg tenkte å teste" og "tilfellet check50 faktisk prøvde." Å liste opp kanttilfeller først ville gjort det gapet til noe jeg fanger selv, i stedet for noe et rettescript fanger for meg.
Jeg ville også sluttet å stole på en grønn testkjøring som bevis på noe som helst i seg selv. To beståtte tester fortalte meg ingenting nyttig om is_valid, fordi de to tilfellene jeg plukket begge var komfortabelt langt fra regelens faktiske kant. En testpakke er bare like god som tilfellene noen faktisk tenkte på å skrive, min inkludert, og "den besto" er en mye svakere setning enn den høres ut som første gang du skriver den.
Om jeg faktisk kommer til å fortsette å skrive tester for hvert engangsscript etter at kurset slutter er et eget spørsmål. Spør meg om noen måneder. Min ærlige gjetning akkurat nå er: for kamerascriptet og nyhetssammendraget, sannsynligvis ja, siden de kjører ubemannet og jeg ikke vil være der for å legge merke til en stille feil. For en engangs-scraper jeg kjører én gang og kaster, sannsynligvis fortsatt nei, og jeg er ikke sikker på at det faktisk er feil avgjørelse.