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:
- 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.
- O sucesso dos testes de unidade dependerá da correção do preenchimento preliminar de dados usado pelo banco de dados do servidor MySQL.
- 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.