Développement d'une équipe pour interroger les données de la base de données

Actuellement, je suis engagĂ© dans la mise en Ɠuvre de l'interaction avec le fournisseur de services KYC. Comme d'habitude, rien de cosmique. Il vous suffit de sĂ©lectionner dans votre base de donnĂ©es un ensemble assez important de copies de divers enregistrements, de les tĂ©lĂ©charger vers le fournisseur de services et de demander au fournisseur d'enregistrements de les vĂ©rifier.


L'Ă©tape initiale du traitement contient une douzaine d'opĂ©rations identiques avec l'envoi de demandes de rĂ©cupĂ©ration de donnĂ©es d'un utilisateur spĂ©cifique Ă  partir de diverses tables de base de donnĂ©es. On suppose que dans ce cas, une partie assez importante du code peut ĂȘtre rĂ©utilisĂ©e comme abstraction de Request . Je vais essayer de suggĂ©rer comment cela pourrait ĂȘtre utilisĂ©. J'Ă©crirai le premier test:


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

Ça a l'air bien? Peut-ĂȘtre imparfait, mais Ă  premiĂšre vue, il semble que Request soit essentiellement une qui renvoie Promise avec le rĂ©sultat? De cela, il est tout Ă  fait possible de commencer. Je vais esquisser le code pour que le test puisse ĂȘtre exĂ©cutĂ©.


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

npm test et observe le point vert du test terminé dans la console.


Alors. J'ai une demande, et elle peut ĂȘtre exĂ©cutĂ©e. En rĂ©alitĂ©, cependant, je devrai en quelque sorte informer ma demande du tableau dans lequel il devrait rechercher les donnĂ©es nĂ©cessaires et des critĂšres auxquels ces donnĂ©es devraient rĂ©pondre. Je vais essayer d'Ă©crire un nouveau test:


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

Ok? À mon avis, tout Ă  fait. Étant donnĂ© que j'ai dĂ©jĂ  deux tests qui utilisent une instance de la variable de request , je vais initialiser cette variable dans une mĂ©thode spĂ©ciale qui est exĂ©cutĂ©e avant le dĂ©but de chaque test. Ainsi, dans chaque test, j'aurai une nouvelle instance de l'objet de requĂȘte:


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

J'implémente cette fonctionnalité dans la classe de demande, j'y ajoute une méthode qui enregistre les paramÚtres dans la variable d'instance de classe, comme le montre le test.


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

Je lance les tests et maintenant je vois deux points verts. Deux de mes tests ont rĂ©ussi. Cependant. Il est supposĂ©, cependant, que mes requĂȘtes seront adressĂ©es Ă  la base de donnĂ©es. Maintenant, il vaut probablement la peine d'essayer de voir de quel cĂŽtĂ© la demande recevra des informations sur la base de donnĂ©es. Je vais revenir aux tests et Ă©crire du code:


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

Il me semble qu'une telle version classique de l'initialisation via le constructeur satisfait pleinement mes exigences actuelles.


Naturellement, je ne vais pas utiliser les tests unitaires pour utiliser l'interface avec la vraie base de données MySQL avec laquelle notre projet fonctionne. Pourquoi? Parce que:


  1. Si, Ă  ma place, un de mes collĂšgues doit travailler sur cette partie du projet et effectuer des tests unitaires, alors avant de pouvoir faire quoi que ce soit, il devra consacrer du temps et des efforts Ă  installer et configurer sa propre instance du serveur MySQL.
  2. Le succÚs des tests unitaires dépendra de l'exactitude du remplissage préliminaire des données utilisé par la base de données du serveur MySQL.
  3. Le temps d'exécution des tests à l'aide de la base de données MySQL sera considérablement plus long.

D'accord. Et pourquoi, par exemple, ne pas utiliser de base de donnĂ©es en mĂ©moire dans les tests unitaires? Il fonctionnera rapidement et le processus de configuration et d'initialisation peut ĂȘtre automatisĂ©. C'est vrai, mais pour le moment je ne vois aucun avantage Ă  utiliser cet outil supplĂ©mentaire. Il me semble que mes besoins actuels sont plus rapides et moins chers (pas besoin de passer du temps Ă  Ă©tudier) en utilisant des classes et des mĂ©thodes de stubs et de objets, qui ne feront que simuler le comportement d'interfaces censĂ©es ĂȘtre utilisĂ©es dans des conditions de combat.


Au fait. Au combat, je suggĂšre d'utiliser la bibliothĂšque en conjonction avec knex . Pourquoi? Parce que suite Ă  la documentation sur l'installation, la configuration et l'utilisation de ces deux outils, j'ai rĂ©ussi Ă  crĂ©er et exĂ©cuter une requĂȘte dans la base de donnĂ©es en quelques minutes.


Qu'est-ce qui en dĂ©coule? Il en rĂ©sulte que je devrais modifier le code de la classe Request pour que l'exĂ©cution de la requĂȘte corresponde aux interfaces exportĂ©es par mes outils de combat. Alors maintenant, le code devrait ressembler Ă  ceci:


 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; 

Je vais exécuter les tests et voir ce qui se passe. Ouais. DbMock sûr, je n'ai pas le module DbMock , donc la premiÚre chose que je fais est d'implémenter un stub pour lui:


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

Je vais relancer les tests. Et maintenant La princesse Jasmine me dit Jasmine que mon DbMock pas la propriété Model . Je vais essayer de trouver quelque chose:


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

Relancer les tests. Maintenant, l'erreur est que dans mon test unitaire, j'exĂ©cute la requĂȘte sans d'abord dĂ©finir ses paramĂštres Ă  l'aide de la mĂ©thode configure . Je corrige ceci:


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

Comme j'ai déjà utilisé une instance de la variable options dans deux tests, je l'ai placée dans le code d'initialisation de la suite de tests entiÚre et réexécute les tests.


Comme prĂ©vu, la mĂ©thode extend , les propriĂ©tĂ©s du Model , de la classe DbMock nous a renvoyĂ© undefined , donc, naturellement, notre requĂȘte n'a aucun moyen d'appeler la mĂ©thode where .


Je comprends dĂ©jĂ  que la propriĂ©tĂ© Model de la classe DbMock doit ĂȘtre implĂ©mentĂ©e en dehors de la classe DbMock - DbMock . Tout d'abord, Ă©tant donnĂ© que l'implĂ©mentation des nĂ©cessaire pour que les tests existants s'exĂ©cutent, il faudra trop d'Ă©tendues imbriquĂ©es lors de l'initialisation de la propriĂ©tĂ© Model directement dans la classe DbMock . Ce sera complĂštement impossible Ă  lire et Ă  comprendre ... Et cela, cependant, ne m'empĂȘchera pas de faire une telle tentative, car je veux m'assurer d'avoir encore la possibilitĂ© d'Ă©crire quelques lignes de code et de rĂ©ussir les tests.


Alors. Inspirez, expirez, les faibles de cƓur quittent la piĂšce. ComplĂ©ter l'implĂ©mentation du constructeur DbMock . Ta-daaaammmmm ....


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

Tin! Cependant, avec une main ferme, exécutez les tests et assurez-vous que Jasmine nous montre à nouveau les points verts. Et cela signifie que nous sommes toujours sur la bonne voie, bien que quelque chose ait gonflé de maniÚre inappropriée.


Et ensuite? On peut voir Ă  l'Ɠil nu que la propriĂ©tĂ© Model d'une pseudo-base de donnĂ©es doit ĂȘtre implĂ©mentĂ©e comme quelque chose de complĂštement sĂ©parĂ©. Bien qu'inadaptĂ©, il n'est pas clair comment il devrait ĂȘtre mis en Ɠuvre.


Mais je sais avec certitude que je vais stocker les enregistrements dans cette pseudo-base de données en ce moment dans les tableaux les plus ordinaires. Et comme pour les tests existants, je n'ai besoin que de simuler la table des users , pour commencer je vais implémenter un tableau d'utilisateurs, avec un enregistrement. Mais d'abord, je vais écrire un 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'); }); }); 

Je lance des tests. Je m'assure qu'ils ne passent pas, et j'implémente mon simple container avec l'utilisateur:


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

Maintenant, les tests sont effectuĂ©s, et il me semble que sĂ©mantiquement le Model , dans le package de la bookshell , est le fournisseur de l'interface pour accĂ©der au contenu de la table dans la base de donnĂ©es. Ce n'est pas pour rien que nous passons un objet avec un nom de table Ă  la mĂ©thode extend . Pourquoi on l'appelle extend , et non par exemple get , je ne sais pas. C'est peut-ĂȘtre juste un manque de connaissances sur l'API de la bookshell .


Eh bien, que Dieu le bĂ©nisse, pour l'instant j'ai une idĂ©e en tĂȘte sur le sujet du test suivant:


 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'); }); }); }); 

Étant donnĂ© que pour le moment j'ai besoin d'une implĂ©mentation qui simule uniquement la fonctionnalitĂ© d'un vĂ©ritable pilote de stockage, je nomme les classes en consĂ©quence, en ajoutant le suffixe Mock :


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

Mais le fetch pas la seule méthode que j'ai l'intention d'utiliser dans la version combat, alors j'ajoute un autre test:


 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'); }); }); 

Le lancement dont, comme prévu, affiche un message d'erreur pour moi. Je complÚte donc l'implémentation de TableMock avec la méthode where :


 where(){ return this; } 

Maintenant, les tests sont en cours d'exécution et vous pouvez passer à la rubrique de l'implémentation de la propriété Model dans la classe DbMock . Comme je l'ai déjà suggéré, ce sera un certain fournisseur d'instances d'objet de type 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(); }); }); 

Pourquoi TableMockMap , car sémantiquement c'est tout. Juste au lieu du nom de la méthode get , le nom de la méthode d' extend est utilisé.


Depuis que le test plante, nous faisons une implémentation:


 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; 

Exécutez les tests et voyez six points verts dans la console. La vie est belle.


Il me semble en ce moment que vous pouvez déjà vous débarrasser de la initialisation dans le constructeur de la classe DbMock , en utilisant le merveilleux TableMockMap . Nous ne la repousserons pas, d'autant plus qu'il serait bien d'avoir déjà du thé. La nouvelle implémentation est délicieusement élégante:


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

ExĂ©cutez les tests ... et oups! Notre test le plus important tombe. Mais c'est mĂȘme bien, car c'Ă©tait un talon de test et maintenant nous devons juste le corriger:


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

Les tests ont rĂ©ussi. Et maintenant, vous pouvez faire une pause, puis revenir Ă  la finalisation du code de requĂȘte rĂ©sultant, car il est encore trĂšs, trĂšs loin d'ĂȘtre parfait, mais mĂȘme Ă  partir d'une interface simple Ă  utiliser, malgrĂ© le fait que les donnĂ©es l'utilisant proviennent de les bases peuvent dĂ©jĂ  ĂȘtre reçues.

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


All Articles