Testing av dataintensiv kod med go, del 3

Översikt

Detta är del tre av fem i en tutorial-serie om testning av dataintensiv kod med Go. I del två täckte jag testning mot ett realt minne i minnet baserat på den populära SQLite. I den här handledningen går jag över testning mot ett lokalt komplext datalager som innehåller en relationell DB och en Redis-cache.

Testa mot ett lokalt datalager

Testning mot ett minnesdataskikt är fantastiskt. Testerna är blixtsnabba, och du har full kontroll. Men ibland måste du närma dig den faktiska konfigurationen av ditt produktionsdata lager. Här är några möjliga skäl:

  • Du använder specifika detaljer för din relationella DB som du vill testa.
  • Datalagret består av flera interaktiva datalager.
  • Koden som testas består av flera processer som har samma datalager.
  • Du vill förbereda eller observera dina testdata med hjälp av standardverktyg.
  • Du vill inte implementera ett dedikerat minnesdataskikt om ditt datalag är i flöde.
  • Du vill bara veta att du testar mot ditt faktiska datalager.
  • Du måste testa med mycket data som inte passar i minnet.

Jag är säker på att det finns andra orsaker, men du kan se varför att bara använda ett minnesdataskikt för testning kanske inte räcker i många fall.

OK. Så vi vill testa ett faktiskt datalager. Men vi vill fortfarande vara så lätta och smidiga som möjligt. Det betyder ett lokalt datalager. Här är fördelarna:

  • Inget behov av att tillhandahålla och konfigurera någonting i datacentret eller molnet.
  • Ingen anledning att oroa oss för våra tester som skadar produktionsdata av misstag.
  • Inget behov av att samordna med medarbetare i en gemensam testmiljö. 
  • Ingen långsamhet över nätverkssamtal.
  • Full kontroll över innehållet i datalaget, med möjlighet att börja från början varje gång.  

I denna handledning kommer vi upp ante. Vi implementerar (mycket delvis) ett hybriddatalag som består av en MariaDB relationell DB och en Redis-server. Då använder vi Docker för att ställa upp ett lokalt datalag som vi kan använda i våra test. 

Använda Docker för att undvika installation Huvudvärk

Först behöver du Docker, förstås. Kolla in dokumentationen om du inte är bekant med Docker. Nästa steg är att få bilder för våra datalager: MariaDB och Redis. Utan att gå in på för mycket detaljer är MariaDB en bra relationell DB-kompatibel med MySQL, och Redis är en stor viktminskningsbutik (och mycket mer). 

> docker pull mariadb ...> docker pull redis ...> docker bilder REPOSITORY TAG IMAGE ID CREATED STORLEK mariadb senaste 51d6a5e69fa7 2 veckor sedan 402MB redis senaste b6dddb991dfa 2 veckor sedan 107MB 

Nu när vi har Docker installerat och vi har bilderna för MariaDB och Redis, kan vi skriva en docker-compose.yml-fil, som vi ska använda för att starta våra datalager. Låt oss ringa vår DB "Songify".

mariadb-songify: image: mariadb: senaste kommandot:> --general-loggen --general-log-file = / var / log / mysql / query.log avslöja: - "3306" portar: - "3306: 3306" miljö : MYSQL_DATABASE: "songify" MYSQL_ALLOW_EMPTY_PASSWORD: "true" volymer_from: - mariadb-data mariadb-data: bild: mariadb: senaste volymer: - / var / lib / mysql entrypoint: / bin / bash redis: bild: redis expose: 6379 "portar: -" 6379: 6379 " 

Du kan starta dina datalager med docker-komponera upp kommando (liknar vagrant upp). Utgången ska se ut så här: 

> docker-compose up Starta hybridtest_redis_1 ... Starta hybridtest_mariadb-data_1 ... Starta hybridtest_redis_1 Starta hybridtest_mariadb-data_1 ... done Starta hybridtest_mariadb-songify_1 ... Starta hybridtest_mariadb-songify_1 ... done Fästa i hybridtest_mariadb-data_1, hybridtest_redis_1, hybridtest_mariadb-songify_1 ... redis_1 | * DB laddad från disk: 0,002 sekunder redis_1 | * Klar för att acceptera anslutningar ... mariadb-songify_1 | [Notera] mysqld: redo för anslutningar ... 

Vid denna tidpunkt har du en fullvärdig MariaDB-serverns lyssning på port 3306 och en Redis-server lyssnar på port 6379 (båda är standardportarna).

Hybriddataskiktet

Låt oss utnyttja dessa kraftfulla databutiker och uppgradera vårt datalager till ett hybriddatalager som cachar låtar per användare i Redis. När GetSongsByUser ()kallas, ska data lagret först kontrollera om Redis redan lagrar låtarna för användaren. Om det gör så returnerar du bara låtarna från Redis, men om det inte gör (cache miss) kommer det att hämta låtarna från MariaDB och fylla i Redis-cachen, så det är klart för nästa gång. 

Här är struktur- och konstruktördefinitionen. Struct håller ett DB-handtag som tidigare och även en redis-klient. Konstruktören ansluter sig till relationell DB såväl som till Redis. Det skapar schemat och spola redis bara om motsvarande parametrar är sanna, vilket bara behövs för testning. I produktion skapar du schemat en gång (ignorerar scheman migreringar).

skriv HybridDataLayer struktur db * sql.DB redis * redis.Client func NewHybridDataLayer (dbHost-sträng, dbPort int, redisHost-sträng, skapaSchema bool, clearRedis bool) (* HybridDataLayer, fel) dsn: = fmt.Sprintf tcp (% s:% d) / ", dbHost, dbPort) om createSchema err: = createMariaDBSchema (dsn) om err! = nil return nil, err db, err: = sql.Open (" mysql " dsn + "desongcious? parseTime = true") om err! = nil return nil, err redisClient: = redis.NewClient (& redis.Options Addr: redisHost + ": 6379", Lösenord: "", DB: 0, ) _, err = redisClient.Ping (). Resultat () om err! = nil return nil, err om clearRedis redisClient.FlushDB () returnerar & HybridDataLayer db, redisClient, nil

Använda MariaDB

MariaDB och SQLite är lite annorlunda när DDL går. Skillnaderna är små, men viktiga. Go har inte en mogen cross-DB verktygslåda som Pythons fantastiska SQLAlchemy, så du måste hantera det själv (nej, Gorm räknas inte). De viktigaste skillnaderna är:

  • SQL-drivrutinen är "github.com/go-sql-driver/mysql".
  • Databasen bor inte i minnet, så det återskapas varje gång (släpp och skapa). 
  • Schemat måste vara en skiva av oberoende DDL-satser istället för en sträng av alla uttalanden.
  • De primära nycklarna för automatisk ökning markeras med AUTO_INCREMENT.
  • VARCHAR istället för TEXT.

Här är koden:

func createMariaDBSchema (dsn-string) fel db, err: = sql.Open ("mysql", dsn) om err! = nil return err // Återskapa DB-kommandon: = [] string "DROP DATABASE songify;" "CREATE DATABASE songify;", för _, s: = range (kommandon) _, err = db.Exec (s) om err! = Nil return err // Skapa schema db, err = sql.Open ("mysql", dsn + "songify? parseTime = true") om err! = nil return err schema: = [] sträng 'CREATE TABLE IF NOT EXISTS sång (id INTEGER PRIMARY KEY AUTO_INCREMENT, url VARCHAR (2088) UNIQUE VARCHAR (100), VARCHAR (100), VARCHAR (100), VARCHAR (100), E-post VARCHAR (100) UNIQUE, registered_at TIMESTAMP, last_login TIMESTAMP), "CREATE TABLE IF NOT EXISTS användaren (id INTEGER PRIMARY KEY AUTO_INCREMENT, namn VARCHAR "," SKAPA INDEX user_email_idx ON användare (email); "," Skapa tabell om inte EXISTS-etikett (id INTEGER PRIMARY KEY AUTO_INCREMENT, namn VARCHAR (100) UNIQUE); "," SKAPA INDEX label_name_idx ON label "Skapa tabell om inte existerar label_song (label_id INTEGER NOT NULL REFE RENCES-etikett (id), song_id INTEGER NOT NULL REFERENCES låt (id), PRIMARY KEY (label_id, song_id)); "," Skapa tabell om inte EXISTS user_song (user_id INTEGER NOT NULL REFERENCES användare (id), song_id INTEGER INTE NULL REFERENSER låt (id), PRIMARY KEY (user_id, song_id));,, _, s: = range (schema) _, err = db.Exec (s) om err! = nil return err returnera noll 

Använda Redis

Redis är väldigt lätt att använda från Go. Klientbiblioteket "github.com/go-redis/redis" är mycket intuitivt och följer följaktligen kommandona Redis. Till exempel, för att testa om en nyckel finns, använder du bara Utgångar () metod för redis-klienten, som accepterar en eller flera nycklar och returnerar hur många av dem som finns. 

I det här fallet kontrollerar jag endast en nyckel:

 räkna, err: = m.redis.Exists (email) .Result () om err! = nil return err

Testa åtkomst till flera datalager

Testerna är faktiskt identiska. Gränssnittet ändrades inte, och beteendet förändrades inte. Den enda förändringen är att implementeringen nu håller ett cache i Redis. De GetSongsByEmail () Metod nu bara samtal refreshUser_Redis ().

func (m * HybridDataLayer) GetSongsByUser (u användare) (låtar [] Song, err error) err = m.refreshUser_Redis (u.Email, & songs) returnerar 

De refreshUser_Redis () Metoden returnerar användarsångerna från Redis om de finns och hämtar dem på annat sätt från MariaDB.

typ Songs * [] Song func (m * HybridDataLayer) refreshUser_Redis (email sträng, ut Songs) fel count, err: = m.redis.Exists (email) .Resultat () om err! = nil return err om räkna == 0 err = m.getSongsByUser_DB (email, out) om err! = Nil return err för _, låt: = range * ut s, err: = serializeSong (song) om err! = Nil return error  _, err = m.redis.SAdd (email, s) .Resultat () om err! = nil return err returnera medlemmar, err: = m.redis.SMembers (email) .Result () för _ , medlem: = radmedlemmar låt, err: = deserializeSong ([] byte (medlem)) om err! = nil return err * ut = lägg till (* ut, låt) returnera, nil 

Det finns ett litet problem här ur testmetodiksynpunkt. När vi testar genom det abstrakta datalagergränssnittet, har vi ingen synbarhet i implementeringen av datalagret.

Det är till exempel möjligt att det finns en stor fel där datalagret helt hoppar över cacheminnet och hämtar alltid data från DB. Testerna passerar, men vi får inte dra nytta av cacheminnet. Jag ska prata i del fem om att testa din cache, vilket är mycket viktigt.  

Slutsats

I den här handledningen täckte vi testning mot ett lokalt komplext datalager som består av flera datalager (en relations DB och en Redis-cache). Vi utnyttjade även Docker för att enkelt distribuera flera datalager för testning.

I del fyra kommer vi att fokusera på testning mot fjärrdatabutiker, med hjälp av ögonblicksbilder av produktionsdata för våra test, och även att generera egna testdata. Håll dig igång!