Testing Data-Intensive Code With Go, Del 3

Oversikt

Dette er del tre av fem i en opplæringsserie om testing av dataintensiv kode med Go. I del to, dekket jeg testing mot et ekte minne lagringsdata basert på den populære SQLite. I denne opplæringen går jeg over testing mot et lokalt komplekst datalag som inneholder en relasjons DB og en Redis-buffer.

Testing mot et lokalt datalag

Testing mot et lagringsdata lag er fantastisk. Tester er lynrask, og du har full kontroll. Men noen ganger må du være nærmere den faktiske konfigurasjonen av produksjonsdata laget. Her er noen mulige årsaker:

  • Du bruker spesifikke detaljer for din relasjons DB som du vil teste.
  • Datalaget ditt består av flere samhandlende datalager.
  • Koden under test består av flere prosesser som gir tilgang til det samme datalaget.
  • Du vil forberede eller observere testdataene dine ved hjelp av standardverktøy.
  • Du vil ikke implementere et dedikert lagringsdatagelag hvis datalaget ditt er i flux.
  • Du vil bare vite at du tester mot ditt faktiske datalag.
  • Du må teste med mye data som ikke passer i minnet.

Jeg er sikker på at det er andre grunner, men du kan se hvorfor det bare ikke er nok i mange tilfeller å bare bruke et lagringsdata for lagring for testing..

OK. Så vi vil teste et faktisk datalag. Men vi vil fortsatt være så lett og smidig som mulig. Det betyr et lokalt datalag. Her er fordelene:

  • Du trenger ikke å forsyne og konfigurere noe i datasenteret eller skyen.
  • Du trenger ikke å bekymre deg for våre tester som ødelegger produksjonsdataene ved et uhell.
  • Du trenger ikke å koordinere med andre utviklere i et delt testmiljø. 
  • Ingen treghet over nettverket ringer.
  • Full kontroll over innholdet i datalaget, med muligheten til å starte fra begynnelsen til enhver tid.  

I denne opplæringen kommer vi opp ante. Vi implementerer (veldig delvis) et hybrid datalag som består av en MariaDB relasjons DB og en Redis server. Da bruker vi Docker til å opprette et lokalt datalag som vi kan bruke i våre tester. 

Bruke Docker for å unngå installasjon Hodepine

Først trenger du Docker, selvfølgelig. Sjekk ut dokumentasjonen hvis du ikke er kjent med Docker. Det neste trinnet er å få bilder for våre datalager: MariaDB og Redis. Uten å komme inn i for mye detalj, er MariaDB en stor relasjons DB kompatibel med MySQL, og Redis er en stor minneverdighetsbutikk (og mye mer). 

> docker pull mariadb ...> docker pull redis ...> docker bilder REPOSITORY TAG IMAGE ID CREATED SIZE mariadb siste 51d6a5e69fa7 2 uker siden 402MB redis latest b6dddb991dfa 2 uker siden 107MB 

Nå som vi har Docker installert og vi har bildene for MariaDB og Redis, kan vi skrive en docker-compose.yml-fil, som vi skal bruke til å lansere våre datalager. La oss ringe vår DB "songify".

mariadb-songify: image: mariadb: siste kommando:> - generell logg - generell loggfil = / var / log / mysql / query.log avslør: - "3306" porter: - "3306: 3306" miljø : MYSQL_DATABASE: "songify" MYSQL_ALLOW_EMPTY_PASSWORD: "true" volumes_from: - mariadb-data mariadb-data: bilde: mariadb: siste volumer: - / var / lib / mysql inngangspunkt: / bin / bash redis: image: redis expose: 6379 "porter: -" 6379: 6379 " 

Du kan starte datalagerene dine med docker-komponere opp kommando (ligner på vagrant opp). Utgangen skal se slik ut: 

> docker-compose up Starte hybridtest_redis_1 ... Starte hybridtest_mariadb-data_1 ... Starte hybridtest_redis_1 Starte hybridtest_mariadb-data_1 ... ferdig Starte hybridtest_mariadb-songify_1 ... Starte hybridtest_mariadb-songify_1 ... ferdig Vedlegg til hybridtest_mariadb-data_1, hybridtest_redis_1, hybridtest_mariadb-songify_1 ... redis_1 | * DB lastet fra disk: 0.002 sekunder redis_1 | * Klar til å akseptere tilkoblinger ... mariadb-songify_1 | [Note] mysqld: klar for tilkoblinger ... 

På dette tidspunktet har du en fullverdig MariaDB-server lytter på port 3306 og en Redis-server lytter på port 6379 (begge er standardportene).

Hybrid Data Layer

La oss dra nytte av disse kraftige datalagerene og oppgradere vårt datalag til et hybrid datalag som caches sanger per bruker i Redis. Når GetSongsByUser ()kalles, vil datalaget først sjekke om Redis allerede lagrer sangene til brukeren. Hvis det gjør det, så returner du sangene fra Redis, men hvis det ikke gjør det (cache miss), så henter det sangene fra MariaDB og fyller Redis-cachen, så det er klart for neste gang.. 

Her er struktur- og konstruktørdefinisjonen. Struct holder et DB håndtak som før og også en redis klient. Konstruktøren knytter seg til relasjons DB så vel som til Redis. Det skaper skjemaet og spyler redis bare hvis de tilsvarende parametrene er sanne, noe som kun er nødvendig for testing. I produksjon lager du skjemaet en gang (ignorerer skjema migreringer).

skriv HybridDataLayer struktur db * sql.DB redis * redis.Client func NewHybridDataLayer (dbHost-streng, dbPort int, redisHost-streng, createSchema bool, clearRedis bool) (* HybridDataLayer, feil) dsn: = fmt.Sprintf tcp (% s:% d) / ", dbHost, dbPort) hvis createSchema err: = createMariaDBSchema (dsn) hvis err! = null return null, err db, err: = sql.Open (" mysql " dsn + "desongcious? parseTime = true") hvis err! = null return nil, err redisClient: = redis.NewClient (og redis.Options Addr: redisHost + ": 6379", Passord: "", DB: 0, ) _, err = redisClient.Ping (). Resultat () hvis err! = null return null, err hvis clearRedis redisClient.FlushDB () returnerer & HybridDataLayer db, redisClient, null

Bruke MariaDB

MariaDB og SQLite er litt forskjellige så langt som DDL går. Forskjellene er små, men viktige. Go har ikke en moden cross-DB verktøykasse som Pythons fantastiske SQLAlchemy, så du må klare det selv (nei, Gorm teller ikke). De viktigste forskjellene er:

  • SQL-driveren er "github.com/go-sql-driver/mysql".
  • Databasen lever ikke i minnet, så det gjenskapes hver gang (slipp og lag). 
  • Skjemaet må være et stykke uavhengige DDL-setninger i stedet for en streng av alle setninger.
  • De primære taster som øker automatisk, er merket med AUTO_INCREMENT.
  • VARCHAR i stedet for TEKST.

Her er koden:

func createMariaDBSchema (dsn streng) feil db, err: = sql.Open ("mysql", dsn) hvis err! = null return err // Opprett DB kommandoer: = [] string "DROP DATABASE songify;", "CREATE DATABASE songify;", for _, s: = range (kommandoer) _, err = db.Exec (s) hvis err! = Null return err // Opprett skjema db, err = sql.Open ("mysql", dsn + "songify? parseTime = true") hvis err! = null return err skjema: = [] streng 'CREATE TABLE IF ikke EXISTS sang (id INTEGER PRIMARY KEY AUTO_INCREMENT, url VARCHAR (2088) UNIQUE , tittel VARCHAR (100), beskrivelse VARCHAR (500)); ',' SKAP TABELL Dersom ikke EXISTS bruker (ID INTEGER PRIMARY KEY AUTO_INCREMENT, navn VARCHAR (100), email VARCHAR (100) UNIQUE, registered_at TIMESTAMP, last_login TIMESTAMP); ',' CREATE INDEX user_email_idx PÅ bruker (e-post); ',' CREATE TABLE IF NOT EXISTS-etikett (id INTEGER PRIMARY KEY AUTO_INCREMENT, navn VARCHAR (100) UNIQUE); ', "CREATE INDEX label_name_idx ON label 'CREATE TABLE IF NOT EXISTS label_song (label_id INTEGER IKKE NULL REFE RENCES-etikett (id), song_id INTEGER IKKE NULL REFERENSER sang (id), PRIMARY KEY (label_id, song_id)); ',' SKAP TABELL HVIS IKKE EXISTER user_song (user_id INTEGER IKKE NULL REFERENSER bruker (id), song_id INTEGER IKKE NULL REFERANSER sang (id), PRIMARY KEY (user_id, song_id)); ', for _, s: = rekkevidde (skjema) _, err = db.Exec (er) hvis feil! = null retur err retur null 

Bruke Redis

Redis er veldig enkelt å bruke fra Go. Klientbiblioteket "github.com/go-redis/redis" er veldig intuitivt og følger troverdig Redis-kommandoene. For eksempel, for å teste om en nøkkel eksisterer, bruker du bare Utganger () metode for redis klienten, som aksepterer en eller flere nøkler og returnerer hvor mange av dem som eksisterer. 

I dette tilfellet kontrollerer jeg bare for en nøkkel:

 telle, feil: = m.redis.Exists (email) .Resultat () hvis feil! = null return err

Testing tilgang til flere datalager

Testene er faktisk identiske. Grensesnittet endret seg ikke, og oppførselen endret seg ikke. Den eneste endringen er at implementeringen nå holder en cache i Redis. De GetSongsByEmail () Metode nå bare ringer refreshUser_Redis ().

func (m * HybridDataLayer) GetSongsByUser (u Bruker) (sanger [] Song, feil feil) err = m.refreshUser_Redis (u.Email, & songs) return 

De refreshUser_Redis () Metoden returnerer brukerens sanger fra Redis hvis de eksisterer og ellers henter dem fra MariaDB.

type sanger * [] Sangfunksjon (m * HybridDataLayer) refreshUser_Redis (e-poststreng, ut Songs) feil telle, err: = m.redis.Exists (email) .Resultat () hvis err! = null return err == 0 err = m.getSongsByUser_DB (email, out) hvis feil! = Null return err for _, sang: = rekkevidde * ut s, err: = serializeSong (sang) hvis feil! = Null  _, err = m.redis.SAdd (email, s) .Resultat () hvis err! = null return err return medlemmer, feil: = m.redis.SMembers (email) .Resultat () for _ , medlem: = rekkevidde medlemmer (sang, err: = deserializeSong ([] byte (medlem)) hvis err! = null retur err * ut = legg til (* ut, sang) gå ut, null 

Det er et lite problem her fra testmetodisk synspunkt. Når vi tester gjennom det abstrakte datalaggrensesnittet, har vi ingen synlighet i datalagets implementering.

For eksempel er det mulig at det er stor feil der datalaget helt hopper over cachen og alltid henter dataene fra DB. Testene vil passere, men vi får ikke nytte av hurtigbufferen. Jeg snakker i del fem om å teste cachen din, noe som er veldig viktig.  

Konklusjon

I denne opplæringen dekket vi testing mot et lokalt komplekst datalag som består av flere datalager (en relasjonell DB og en Redis-cache). Vi benyttet også Docker til enkelt å distribuere flere datalager for testing.

I del fire vil vi fokusere på testing mot eksterne datalager, ved hjelp av øyeblikksbilder av produksjonsdata for våre tester, og også generere egne testdata. Følg med!