Desenvolvimento de uma equipe para consulta de dados do banco de dados

Atualmente, estou envolvido na implementação da interação com o provedor de serviços KYC. Como sempre, nada cósmico. Você só precisa selecionar em seu banco de dados um conjunto razoavelmente grande de cópias de vários registros, enviá-las ao provedor de serviços e solicitar ao provedor de registros para verificá-las.


O estágio inicial do processamento contém uma dúzia de operações idênticas com o envio de solicitações para extrair dados de um usuário específico de várias tabelas do banco de dados. Supõe-se que, nesse caso, uma parte bastante grande do código possa ser reutilizada como uma abstração de Request . Vou tentar sugerir como isso pode ser usado. Vou escrever o primeiro teste:


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

Parece muito bom? Talvez imperfeito, mas à primeira vista parece que Request é essencialmente um que retorna Promise com o resultado? A partir disso, é bem possível começar. Vou esboçar o código para que o teste possa ser executado.


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

npm test e observo o ponto verde do teste concluído no console.


Então Eu tenho uma solicitação e ela pode ser executada. Na realidade, no entanto, precisarei de alguma forma informar minha solicitação sobre em qual tabela ele deve procurar os dados necessários e quais critérios esses dados devem atender. Vou tentar escrever um novo teste:


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

Ok Na minha opinião, bastante. Como já tenho dois testes que usam uma instância da variável de request , inicializarei essa variável em um método especial que é executado antes de cada teste. Assim, em cada teste, terei uma nova instância do objeto de solicitação:


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

Eu implemento essa funcionalidade na classe request, adiciono um método que salva as configurações na variável de instância da classe, como demonstra o teste.


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

Eu executo os testes e agora vejo dois pontos verdes. Dois dos meus testes foram concluídos com sucesso. No entanto. Supõe-se, no entanto, que minhas consultas serão endereçadas ao banco de dados. Agora provavelmente vale a pena tentar ver de que lado a solicitação receberá informações sobre o banco de dados. Voltarei aos testes e escreverei algum código:


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

Parece-me que uma versão clássica de inicialização por meio do construtor satisfaz totalmente meus requisitos atuais.


Naturalmente, não vou usar os testes de unidade para usar a interface com o banco de dados MySQL real com o qual nosso projeto trabalha. Porque Porque:


  1. Se, em vez de mim, um de meus colegas precisar trabalhar nesta parte do projeto e realizar testes de unidade antes que eles possam fazer qualquer coisa, eles terão que gastar tempo e esforço instalando e configurando sua própria instância do servidor MySQL.
  2. O sucesso dos testes de unidade dependerá da correção do preenchimento preliminar de dados usado pelo banco de dados do servidor MySQL.
  3. O tempo para executar testes usando o banco de dados MySQL será significativamente maior.

Ok. E por que, por exemplo, não usar nenhum banco de dados na memória em testes de unidade? Ele funcionará rapidamente e o processo de configuração e inicialização pode ser automatizado. Tudo isso é verdade, mas no momento não vejo vantagens em usar essa ferramenta adicional. Parece-me que minhas necessidades atuais são mais rápidas e baratas (não é preciso gastar tempo estudando) usando classes e métodos de e objetos, que simularão apenas o comportamento de interfaces que deveriam ser usadas em condições de combate.


A propósito. Em combate, sugiro usar a estante de livros em conjunto com o knex . Porque Porque, seguindo a documentação sobre instalação, configuração e uso dessas duas ferramentas, consegui criar e executar uma consulta no banco de dados em alguns minutos.


O que se segue disso? Daqui resulta que devo modificar o código da classe Request para que a execução da solicitação corresponda às interfaces exportadas por minhas ferramentas de combate. Então agora o código deve ficar assim:


 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; 

Vou fazer os testes e ver o que acontece. Sim DbMock , não tenho o módulo DbMock , então a primeira coisa que faço é implementar um stub para ele:


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

Vou executar os testes novamente. O que agora A princesa Jasmine me diz que meu DbMock não implementa a propriedade Model . Vou tentar pensar em algo:


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

Executando os testes novamente. Agora, o erro é que, no meu teste de unidade, eu executo a consulta sem primeiro definir seus parâmetros usando o método configure . Eu corrijo isso:


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

Como já usei uma instância da variável options em dois testes, coloquei-a no código de inicialização de todo o conjunto de testes e executei os testes novamente.


Como esperado, o método extend , as propriedades do Model , da classe DbMock nos retornaram undefined ; portanto, naturalmente, nossa solicitação não tem como chamar o método where .


Eu já entendo que a propriedade Model da classe DbMock deve ser implementada fora da DbMock classe DbMock . Primeiro, devido ao fato de que a implementação de necessária para a execução dos testes existentes, serão necessários muitos escopos aninhados ao inicializar a propriedade Model diretamente na classe DbMock . Será completamente impossível ler e entender ... E isso, no entanto, não vai me impedir de tentar, porque quero ter certeza de que ainda tenho a oportunidade de escrever apenas algumas linhas de código e fazer os testes executados com êxito.


Então Inspire, expire, os fracos de coração saem da sala. Complementando a implementação do construtor DbMock . Ta-daaaammmmm ....


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

Estanho! No entanto, com a mão firme, execute os testes e verifique se Jasmine novamente mostra os pontos verdes. E isso significa que ainda estamos no caminho certo, embora algo tenha inchado de maneira inadequada.


O que vem a seguir? Pode-se ver a olho nu que a propriedade Model de um pseudo-banco de dados deve ser implementada como algo completamente separado. Embora de improviso, não está claro como deve ser implementado.


Mas tenho certeza de que armazenarei os registros nesse pseudo-banco de dados agora mesmo nas matrizes mais comuns. E já que para os testes existentes eu preciso apenas simular a tabela de users , para começar, implementarei uma matriz de usuários, com um registro. Mas primeiro, vou escrever um teste:


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

Eu executo testes. Garanto que eles não sejam aprovados e implementei meu contêiner simples com o usuário:


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

Agora os testes estão sendo executados e me ocorre que semanticamente o Model , no pacote bookshell , é o provedor da interface para acessar o conteúdo da tabela no banco de dados. Não é à toa que passamos um objeto com um nome de tabela para o método extend . Por que é chamado de extend , e não por exemplo, get , eu não sei. Talvez isso seja apenas uma falta de conhecimento sobre a API do bookshell .


Bem, que Deus o abençoe, por enquanto tenho uma ideia na cabeça sobre o tópico do seguinte teste:


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

Como no momento eu preciso de uma implementação que simule apenas a funcionalidade de um driver de armazenamento real, nomeio as classes adequadamente, adicionando o sufixo Mock :


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

Mas fetch não fetch o único método que pretendo usar na versão de combate, então adiciono mais um teste:


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

O lançamento do qual, como esperado, exibe uma mensagem de erro para mim. Então, complemento a implementação do TableMock com o método where :


 where(){ return this; } 

Agora os testes são executados e você pode seguir para o tópico de implementação da propriedade Model na classe DbMock . Como já sugeri, este será um determinado provedor de instâncias de objeto do tipo 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(); }); }); 

Por que TableMockMap , porque semanticamente é isso. Apenas em vez do nome do método get , o nome do método de extend é usado.


Desde que o teste falha, fazemos uma implementação:


 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; 

Execute os testes e veja seis pontos verdes no console. A vida é linda.


Parece-me agora que você já pode se livrar da inicialização no construtor da classe DbMock , usando o maravilhoso TableMockMap . Não o adiaremos, principalmente porque já seria bom tomar chá. A nova implementação é deliciosamente elegante:


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

Execute os testes ... e oops! Nosso teste mais importante cai. Mas isso é bom mesmo, porque era um esboço de teste e agora precisamos corrigi-lo:


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

Testes concluídos com sucesso. E agora você pode fazer uma pausa e retornar à finalização do código de solicitação resultante, porque ele ainda está muito, muito longe da perfeição, mas mesmo de uma interface fácil de usar, apesar do fato de os dados que o utilizarem serem de bases já podem ser recebidas.

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


All Articles