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 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:
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:
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.
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).
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
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:
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
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
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.
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!