Entwicklung eines Teams zum Abfragen von Daten aus der Datenbank

Derzeit beschäftige ich mich mit der Implementierung der Interaktion mit dem KYC-Dienstleister. Wie immer nichts Kosmisches. Sie müssen nur eine ziemlich große Anzahl von Kopien verschiedener Datensätze aus Ihrer Datenbank auswählen, diese auf den Dienstanbieter hochladen und den Datensatzanbieter bitten, diese zu überprüfen.


Die Anfangsphase der Verarbeitung enthält ein Dutzend identischer Vorgänge mit dem Senden von Anforderungen zum Abrufen von Daten eines bestimmten Benutzers aus verschiedenen Datenbanktabellen. Es wird davon ausgegangen, dass in diesem Fall ein ziemlich großer Teil des Codes als Abstraktion von Request wiederverwendet werden kann. Ich werde versuchen vorzuschlagen, wie dies verwendet werden könnte. Ich werde den ersten Test schreiben:


 describe('Request', () => { const Request = require('./Request'); it('execute should return promise', () => { const request = new Request(); request.execute().then((result) => { expect(result).toBeNull(); }); }); }); 

Sieht ziemlich gut aus? Vielleicht unvollkommen, aber auf den ersten Blick scheint es, dass Request im Wesentlichen ein , der Promise mit dem Ergebnis zurückgibt? Von hier aus kann man durchaus beginnen. Ich skizziere den Code, damit der Test ausgeführt werden kann.


 class Request { constructor(){} execute(){ return new Promise((resolve, reject) => { resolve(null); }); } } module.exports = Request; 

Ich führe einen npm test und beobachte den grünen Punkt des abgeschlossenen Tests in der Konsole.


Also. Ich habe eine Anfrage, die ausgeführt werden kann. In Wirklichkeit muss ich meine Anfrage jedoch irgendwie darüber informieren, in welcher Tabelle er nach den erforderlichen Daten suchen soll und welche Kriterien diese Daten erfüllen sollen. Ich werde versuchen, einen neuen Test zu schreiben:


 it('should configure request', () => { const options = { tableName: 'users', query: { id: 1 } }; request.configure(options); expect(request.options).toEqual(options); }); 

Ok? Meiner Meinung nach ganz. Da ich bereits zwei Tests habe, die eine Instanz der request , initialisiere ich diese Variable in einer speziellen Methode, die vor jedem Test ausgeführt wird. Daher habe ich in jedem Test eine neue Instanz des Anforderungsobjekts:


 let request = null; beforeEach(() => { request = new Request(); }); 

Ich implementiere diese Funktionalität in der Anforderungsklasse und füge eine Methode hinzu, die die Einstellungen in der Klasseninstanzvariablen speichert, wie der Test zeigt.


 configure(options){ this.options = options; } 

Ich führe die Tests durch und sehe jetzt zwei grüne Punkte. Zwei meiner Tests wurden erfolgreich abgeschlossen. Allerdings. Es wird jedoch davon ausgegangen, dass meine Anfragen an die Datenbank gerichtet werden. Jetzt lohnt es sich wahrscheinlich zu versuchen, herauszufinden, auf welcher Seite die Anfrage Informationen über die Datenbank erhält. Ich werde zu den Tests zurückkehren und Code schreiben:


 const DbMock = require('./DbMock'); let db = null; beforeEach(() => { db = new DbMock(); request = new Request(db); }); 

Es scheint mir, dass eine solche klassische Version der Initialisierung durch den Konstruktor meine aktuellen Anforderungen vollständig erfüllt.


Natürlich werde ich die Komponententests nicht verwenden, um die Schnittstelle zur realen MySQL-Datenbank zu verwenden, mit der unser Projekt arbeitet. Warum? Weil:


  1. Wenn einer meiner Kollegen anstelle von mir an diesem Teil des Projekts arbeiten und Komponententests durchführen muss, muss er Zeit und Mühe aufwenden, um eine eigene Instanz des MySQL-Servers zu installieren und einzurichten, bevor er etwas tun kann.
  2. Der Erfolg von Komponententests hängt von der Richtigkeit der vorläufigen Datenfüllung ab, die von der MySQL-Serverdatenbank verwendet wird.
  3. Die Zeit zum Ausführen von Tests mit der MySQL-Datenbank ist erheblich länger.

Okay. Und warum zum Beispiel bei Unit-Tests keine Datenbank im Speicher verwenden? Es wird schnell funktionieren und der Prozess seiner Konfiguration und Initialisierung kann automatisiert werden. Das ist alles wahr, aber im Moment sehe ich keine Vorteile durch die Verwendung dieses zusätzlichen Tools. Es scheint mir, dass meine aktuellen Bedürfnisse schneller und billiger sind (keine Zeit zum Lernen), wenn ich Klassen und Methoden von und verwende, die nur das Verhalten von Schnittstellen simulieren, die unter Kampfbedingungen verwendet werden sollen.


Übrigens. Im Kampf empfehle ich die Verwendung eines Bücherregals in Verbindung mit Knex . Warum? Da ich der Dokumentation zur Installation, Konfiguration und Verwendung dieser beiden Tools gefolgt bin, konnte ich in wenigen Minuten eine Abfrage an die Datenbank erstellen und ausführen.


Was folgt daraus? Daraus folgt, dass ich den Code der Request Klasse so ändern muss, dass die Ausführung der Anfrage mit den von meinen Kampftools exportierten Schnittstellen übereinstimmt. Nun sollte der Code also so aussehen:


 class Request { constructor(db){ this.db = db; } configure(options){ this.options = options; } execute(){ const table = this.db.Model.extend({ tableName: this.options.tableName }); return table.where(this.options.query).fetch(); } } module.exports = Request; 

Ich werde die Tests durchführen und sehen, was passiert. Ja. DbMock ich das DbMock Modul nicht, also implementiere ich als erstes einen Stub dafür:


 class DbMock { constructor(){} } module.exports = DbMock; 

Ich werde die Tests erneut ausführen. Was jetzt? Prinzessin Jasmine sagt mir Jasmine , dass mein DbMock die Model Eigenschaft nicht implementiert. Ich werde versuchen, mir etwas auszudenken:


 class DbMock { constructor(){ this.Model = { extend: () => {} }; } } module.exports = DbMock; 

Führen Sie die Tests erneut aus. Der Fehler besteht nun darin, dass ich in meinem Komponententest die Abfrage ausführe, ohne zuvor ihre Parameter mithilfe der configure festzulegen. Ich behebe das:


 const options = { tableName: 'users', query: { id: 1 } }; it('execute should return promise', () => { request.configure(options); request.execute().then((result) => { expect(result).toBeNull(); }); }); 

Da ich bereits in zwei Tests eine Instanz der options verwendet habe, habe ich sie in den Initialisierungscode der gesamten Testsuite eingefügt und die Tests erneut ausgeführt.


Wie erwartet hat die extend Methode, die Eigenschaften des Model , der DbMock Klasse uns undefined , daher hat unsere Anfrage natürlich keine Möglichkeit, die where Methode aufzurufen.


Ich verstehe bereits, dass die Model Eigenschaft der DbMock Klasse außerhalb der DbMock Klasse DbMock implementiert werden sollte. DbMock für die Ausführung der vorhandenen Tests die Implementierung von erforderlich ist, sind zu viele verschachtelte Bereiche erforderlich, wenn die Model Eigenschaft direkt in der DbMock Klasse initialisiert wird. Es wird völlig unmöglich sein zu lesen und zu verstehen ... Und dies wird mich jedoch nicht von einem solchen Versuch abhalten, da ich sicherstellen möchte, dass ich immer noch die Möglichkeit habe, nur ein paar Codezeilen zu schreiben und die Tests erfolgreich auszuführen.


Also. Atme ein, aus, schwache Nerven verlassen den Raum. Ergänzung der Implementierung des DbMock Konstruktors. Ta-daaaammmmm ....


 class DbMock { constructor(){ this.Model = { extend: () => { return { where: () => { return { fetch: () => { return new Promise((resolve, reject) => { resolve(null); }); } }; } }; } }; } } module.exports = DbMock; 

Zinn! Führen Sie die Tests jedoch mit fester Hand durch und stellen Sie sicher, dass Jasmine uns wieder grüne Punkte zeigt. Und das heißt, wir sind immer noch auf dem richtigen Weg, obwohl etwas unangemessen angeschwollen ist.


Was weiter? Mit bloßem Auge ist zu erkennen, dass die Model Eigenschaft einer Pseudodatenbank als etwas völlig Separates implementiert werden muss. Obwohl ohne weiteres nicht klar ist, wie es umgesetzt werden soll.


Aber ich weiß mit Sicherheit, dass ich die Datensätze in dieser Pseudodatenbank jetzt in den gewöhnlichsten Arrays speichern werde. Und da ich für die vorhandenen Tests nur die users simulieren muss, werde ich zunächst ein Array von Benutzern mit einem Datensatz implementieren. Aber zuerst schreibe ich einen Test:


 describe('Users', () => { const users = require('./Users'); it('should contain one user', () => { expect(Array.isArray(users)).toBeTruthy(); expect(users.length).toEqual(1); const user = users[0]; expect(user.Id).toEqual(1); expect(user.Name).toEqual('Jack'); }); }); 

Ich führe Tests durch. Ich stelle sicher, dass sie nicht bestanden werden, und implementiere meinen einfachen Container mit dem Benutzer:


 const Users = [ { Id: 1, Name: 'Jack' } ]; module.exports = Users; 

Jetzt werden die Tests ausgeführt, und mir fällt ein, dass das Model im bookshell Paket semantisch der Anbieter der Schnittstelle für den Zugriff auf den Inhalt der Tabelle in der Datenbank ist. Nicht umsonst übergeben wir ein Objekt mit einem Tabellennamen an die extend Methode. Warum es extend heißt und nicht zum Beispiel get , weiß ich nicht. Vielleicht ist dies nur ein Mangel an Wissen über die bookshell API.


Nun, Gott segne ihn, denn jetzt habe ich eine Idee zum Thema des folgenden Tests im Kopf:


 describe('TableMock', () => { const container = require('./Users'); const Table = require('./TableMock'); const users = new Table(container); it('should return first item', () => { users.fetch({ Id: 1 }).then((item) => { expect(item.Id).toEqual(1); expect(item.Name).toEqual('Jack'); }); }); }); 

Da ich momentan eine Implementierung benötige, die nur die Funktionalität eines echten Speichertreibers simuliert, benenne ich die Klassen entsprechend und füge das Suffix Mock :


 class TableMock { constructor(container){ this.container = container; } fetch() { return new Promise((resolve, reject) => { resolve(this.container[0]); }); } } module.exports = TableMock; 

Aber fetch nicht die einzige Methode, die ich in der Kampfversion verwenden möchte. Deshalb füge ich noch einen Test hinzu:


 it('where-fetch chain should return first item', () => { users.where({ Id: 1 }).fetch().then((item)=> { expect(item.Id).toEqual(1); expect(item.Name).toEqual('Jack'); }); }); 

Beim Start wird erwartungsgemäß eine Fehlermeldung angezeigt. Daher ergänze ich die Implementierung von TableMock mit der where Methode:


 where(){ return this; } 

Jetzt werden die Tests durchgeführt und Sie können mit dem Thema der Implementierung der Model Eigenschaft in der DbMock Klasse DbMock . Wie ich bereits vorgeschlagen habe, ist dies ein bestimmter Anbieter von Objektinstanzen vom Typ TableMock :


 describe('TableMockMap', () => { const TableMock = require('./TableMock'); const TableMockMap = require('./TableMockMap'); const map = new TableMockMap(); it('extend should return existent TableMock', () => { const users = map.extend({tableName: 'users'}); expect(users instanceof TableMock).toBeTruthy(); }); }); 

Warum TableMockMap , weil es semantisch so ist. Anstelle des Namens der get Methode wird der Name der extend Methode verwendet.


Da der Test abstürzt, führen wir eine Implementierung durch:


 const Users = require('./Users'); const TableMock = require('./TableMock'); class TableMockMap extends Map{ constructor(){ super(); this.set('users', Users); } extend(options){ const container = this.get(options.tableName); return new TableMock(container); } } module.exports = TableMockMap; 

Führen Sie die Tests aus und sehen Sie sechs grüne Punkte in der Konsole. Das Leben ist schön.


DbMock scheint es mir, dass Sie die Initialisierungspyramide im Konstruktor der DbMock Klasse bereits mit der wunderbaren TableMockMap . Wir werden es nicht verschieben, zumal es schön wäre, schon Tee zu trinken. Die neue Implementierung ist herrlich elegant:


 const TableMockMap = require('./TableMockMap'); class DbMock { constructor(){ this.Model = new TableMockMap(); } } module.exports = DbMock; 

Führen Sie die Tests durch ... und hoppla! Unser wichtigster Test fällt. Aber das ist sogar gut, denn es war ein Teststummel und jetzt müssen wir es nur noch beheben:


 it('execute should return promise', () => { request.configure(options); request.execute().then((result) => { expect(result.Id).toEqual(1); expect(result.Name).toEqual('Jack'); }); }); 

Tests erfolgreich abgeschlossen. Und jetzt können Sie eine Pause einlegen und dann zum Finalisieren des resultierenden Anforderungscodes zurückkehren, da dieser immer noch sehr, sehr weit davon entfernt ist, perfekt zu sein, aber auch nur von einer benutzerfreundlichen Oberfläche, obwohl die Daten, die ihn verwenden, von stammen Basen können bereits empfangen werden.

Source: https://habr.com/ru/post/de436228/


All Articles