Gegevens-intensieve code testen met Go, deel 3

Overzicht

Dit is deel drie van de vijf in een zelfstudieserie over het testen van gegevensintensieve code met Go. In deel twee behandelde ik het testen van een echte in-memory gegevenslaag op basis van de populaire SQLite. In deze zelfstudie zal ik het testen uitvoeren tegen een lokale complexe gegevenslaag met een relationele DB en een Redis-cache.

Testen tegen een lokale gegevenslaag

Testen tegen een gegevenslaag in het geheugen is geweldig. De tests zijn razendsnel en je hebt volledige controle. Maar soms moet u dichter bij de daadwerkelijke configuratie van uw productiedatabase zijn. Hier zijn enkele mogelijke redenen:

  • U gebruikt specifieke details van uw relationele database die u wilt testen.
  • Uw gegevenslaag bestaat uit verschillende interactieve gegevensarchieven.
  • De te testen code bestaat uit verschillende processen die toegang hebben tot dezelfde gegevenslaag.
  • U wilt uw testgegevens voorbereiden of observeren met behulp van standaardhulpmiddelen.
  • U wilt geen specifieke in-memory gegevenslaag implementeren als uw gegevenslaag in beweging is.
  • U wilt gewoon weten dat u aan het testen bent tegen uw werkelijke gegevenslaag.
  • U moet testen met veel gegevens die niet in het geheugen passen.

Ik ben er zeker van dat er nog andere redenen zijn, maar je kunt zien waarom alleen het gebruik van een in-memory gegevenslaag voor testen in veel gevallen niet voldoende is.

OK. We willen dus een echte gegevenslaag testen. Maar we willen nog steeds zo licht en wendbaar mogelijk zijn. Dat betekent een lokale gegevenslaag. Dit zijn de voordelen:

  • U hoeft niets in het datacenter of de cloud in te stellen en te configureren.
  • U hoeft zich geen zorgen te maken dat onze tests de productiegegevens per ongeluk beschadigen.
  • Niet nodig om te coördineren met mede-ontwikkelaars in een gedeelde testomgeving. 
  • Geen traagheid over de netwerkoproepen.
  • Volledige controle over de inhoud van de gegevenslaag, met de mogelijkheid om op elk gewenst moment vanuit het niets te beginnen.  

In deze tutorial gaan we de ante op. We zullen (zeer gedeeltelijk) een hybride gegevenslaag implementeren die bestaat uit een MariaDB relationele DB en een Redis-server. Vervolgens gebruiken we Docker om op te staan ​​voor een lokale gegevenslaag die we in onze tests kunnen gebruiken. 

Docker gebruiken om installatiehoofdpijn te voorkomen

Ten eerste, je hebt natuurlijk Docker nodig. Bekijk de documentatie als u niet bekend bent met Docker. De volgende stap is om afbeeldingen te verkrijgen voor onze datastores: MariaDB en Redis. Zonder al te veel in detail te treden, is MariaDB een geweldige relationele DB die compatibel is met MySQL, en Redis is een geweldige sleutel-waarde winkel in het geheugen (en nog veel meer). 

> docker pull mariadb ...> docker pull redis ...> docker-afbeeldingen REPOSITORY TAG IMAGE ID CREATED SIZE mariadb nieuwste 51d6a5e69fa7 2 weken geleden 402MB redis laatste b6dddb991dfa 2 weken geleden 107MB 

Nu Docker is geïnstalleerd en we de afbeeldingen voor MariaDB en Redis hebben, kunnen we een bestand docker-compose.yml schrijven, dat we gebruiken om onze gegevensopslag te starten. Laten we onze DB "songify" noemen.

mariadb-songify: image: mariadb: laatste opdracht:> --general-log --general-log-file = / var / log / mysql / query.log expose: - "3306" ports: - "3306: 3306" environment : MYSQL_DATABASE: "songify" MYSQL_ALLOW_EMPTY_PASSWORD: "true" volumes_from: - mariadb-data mariadb-data: image: mariadb: nieuwste volumes: - / var / lib / mysql entrypoint: / bin / bash redis: image: redis expose: - " 6379 "poorten: -" 6379: 6379 " 

U kunt uw datastores lanceren met de koppelaar-compose up commando (vergelijkbaar met zwervend op). De uitvoer zou er als volgt uit moeten zien: 

> docker-compose up Beginnen met hybridtest_redis_1 ... Starting hybridtest_mariadb-data_1 ... Starting hybridtest_redis_1 Starting hybridtest_mariadb-data_1 ... done Starting hybridtest_mariadb-songify_1 ... Starting hybridtest_mariadb-songify_1 ... done Attaching to hybridtest_mariadb-data_1, hybridtest_redis_1, hybridtest_mariadb-songify_1 ... redis_1 | * DB geladen vanaf schijf: 0.002 seconden redis_1 | * Klaar om verbindingen te accepteren ... mariadb-songify_1 | [Opmerking] mysqld: klaar voor verbindingen ... 

Op dit punt hebt u een volwaardige MariaDB-server die luistert op poort 3306 en een Redis-server die luistert op poort 6379 (beide zijn de standaardpoorten).

De hybride gegevenslaag

Laten we profiteren van deze krachtige gegevensopslag en onze gegevenslaag upgraden naar een hybride gegevenslaag die nummers per gebruiker in Redis in de cache opslaat. Wanneer GetSongsByUser ()wordt genoemd, controleert de gegevenslaag eerst of Redis de nummers al voor de gebruiker opslaat. Als dit het geval is, stuur dan de nummers gewoon terug vanuit Redis, maar als dat niet het geval is (cache miss), dan haalt het de nummers op van MariaDB en vult de Redis-cache in, dus het is klaar voor de volgende keer. 

Hier is de definitie van de struct en constructor. De struct houdt een DB-greep zoals voorheen en ook een redis-client. De constructor maakt verbinding met zowel de relationele database als met Redis. Het maakt het schema en spoelt alleen opnieuw als de bijbehorende parameters waar zijn, wat alleen nodig is voor testen. In productie maakt u het schema één keer (terwijl u schemamigraties negeert).

type HybridDataLayer struct db * sql.DB redis * redis.Client func NewHybridDataLayer (dbHost string, dbPort int, redisHost string, createSchema bool, clearRedis bool) (* HybridDataLayer, error) dsn: = fmt.Sprintf ("root @ tcp (% s:% d) / ", dbHost, dbPort) if createSchema err: = createMariaDBSchema (dsn) if err! = nil return nil, err db, err: = sql.Open (" mysql ", dsn + "desongcious? parseTime = true") if err! = nil return nil, err redisClient: = redis.NewClient (& redis.Options Addr: redisHost + ": 6379", Password: "", DB: 0, ) _, err = redisClient.Ping (). Resultaat () if err! = nil return nil, err als clearRedis redisClient.FlushDB () return & HybridDataLayer db, redisClient, nihil

MariaDB gebruiken

MariaDB en SQLite verschillen wat betreft DDL enigszins van elkaar. De verschillen zijn klein, maar belangrijk. Go heeft geen uitgebreide cross-DB toolkit zoals de fantastische SQLAlchemy van Python, dus je moet het zelf beheren (nee, Gorm telt niet). De belangrijkste verschillen zijn:

  • Het SQL-stuurprogramma is "github.com/go-sql-driver/mysql".
  • De database leeft niet in het geheugen, dus deze wordt elke keer opnieuw gemaakt (drop en create). 
  • Het schema moet een segment van onafhankelijke DDL-instructies zijn in plaats van één reeks van alle instructies.
  • De automatisch oplopende primaire sleutels worden gemarkeerd door AUTO_INCREMENT.
  • VARCHAR in plaats van TEKST.

Hier is de code:

func createMariaDBSchema (dsn string) error db, err: = sql.Open ("mysql", dsn) if err! = nil return err // Maak DB-commando's opnieuw: = [] string "DROP DATABASE songify;", "CREATE DATABASE songify;", voor _, s: = bereik (opdrachten) _, err = db.Exec (s) if err! = Nil return err // Maak schema db, err = sql.Open ("mysql", dsn + "songify? parseTime = true") if err! = nil return err schema: = [] string 'MAAK TABLE INDIEN NIET BEGONNEN song (id INTEGER PRIMARY KEY AUTO_INCREMENT, url VARCHAR (2088) UNIEK , titel VARCHAR (100), beschrijving VARCHAR (500)); ',' MAKEN TAFEL ALS NIET BESTAAT gebruiker (id INTEGER PRIMAIRE TOETS AUTO_INCREMENT, naam VARCHAR (100), e-mail VARCHAR (100) UNIEK, registered_at TIMESTAMP, last_login TIMESTAMP); ',' CREATE INDEX gebruiker_email_idx AAN gebruiker (e-mail); ", 'MAAK TAFEL ALS NIET BESTAAT label (id INTEGER PRIMAIRE TOETS AUTO_INCREMENT, naam VARCHAR (100) UNIEK);'," CREËER INDEX label_name_idx ON label (naam); ", 'MAAK LIJST ALS NIET BESTAAT' label_song (label_id INTEGER NIET NULL REFE RENCES-label (id), song_id INTEGER NOT NULL REFERENCES song (id), PRIMARY KEY (label_id, song_id)); ',' MAKEN LIJST INDIEN NIET BESTAAT user_song (user_id INTEGER NOT NULL REFERENCES user (id), song_id INTEGER NOT NULL REFERENCES song (id), PRIMARY KEY (user_id, song_id)); ', voor _, s: = bereik (schema) _, err = db.Exec (s) if err! = nil return err return nil 

Redis gebruiken

Redis is heel gemakkelijk te gebruiken vanuit Go. De clientbibliotheek "github.com/go-redis/redis" is zeer intuïtief en volgt getrouw de Redis-opdrachten. Als u bijvoorbeeld wilt testen of een sleutel bestaat, gebruikt u gewoon de Uitgangen () methode van de redis-client, die een of meer sleutels accepteert en teruggeeft hoeveel er zijn. 

In dit geval controleer ik slechts op één sleutel:

 count, err: = m.redis.Exists (email) .Result () if err! = nil return err

Toegang testen tot meerdere gegevensarchieven

De tests zijn eigenlijk identiek. De interface veranderde niet en het gedrag veranderde niet. De enige verandering is dat de implementatie nu een cache in Redis houdt. De GetSongsByEmail () methode nu gewoon oproepen refreshUser_Redis ().

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

De refreshUser_Redis () methode retourneert de gebruikerssongs van Redis als ze bestaan ​​en verzamelt ze anders van MariaDB.

type Songs * [] Song func (m * HybridDataLayer) refreshUser_Redis (e-mailstring, out Songs) error count, err: = m.redis.Exists (email) .Result () if err! = nil return err als het telt == 0 err = m.getSongsByUser_DB (email, out) if err! = Nil return err voor _, song: = range * out s, err: = serializeSong (song) if err! = Nil return err  _, err = m.redis.SAdd (email, s) .Result () if err! = nil return err return members, err: = m.redis.SMembers (email) .Result () voor _ , member: = range members song, err: = deserializeSong ([] byte (member)) if err! = nil return err * out = append (* out, song) return out, nihil 

Er is hier een klein probleem vanuit het oogpunt van testmethodologie. Wanneer we de abstracte gegevenslaaginterface testen, hebben we geen zicht op de implementatie van de gegevenslaag.

Het is bijvoorbeeld mogelijk dat er een grote fout is waar de gegevenslaag de cache volledig overslaat en altijd de gegevens uit de database ophaalt. De tests gaan voorbij, maar we kunnen niet profiteren van de cache. Ik zal in deel vijf over het testen van je cache praten, wat erg belangrijk is.  

Conclusie

In deze zelfstudie hebben we het testen vergeleken met een lokale complexe gegevenslaag die bestaat uit meerdere gegevensarchieven (een relationele DB en een Redis-cache). We hebben Docker ook gebruikt om eenvoudig meerdere datastores in te zetten voor testen.

In deel vier zullen we ons richten op het testen van dataopslag op afstand, het gebruik van snapshots van productiegegevens voor onze tests en het genereren van onze eigen testgegevens. Blijf kijken!