Repositório de TODOS os quarks a serem utilizados em nossos sistemas.
Um Quark nada mais é que a menor parte/módulo/função utilizada pelo Átomo.
Nesse caso o Átomo é um campo no Schema do Mongoose.
const Atom = {
type: String
, get: require('./../quarks/toUpper')
, set: require('./../quarks/toLower')
, validate: require('./../quarks/notEmptyStringValidate')
, required: true
, index: true
}
module.exports = Atom;
E esse campo possui alguns atributos, sendo esses os Quarks:
- type
- get
- set
- validate
- required
- index
Porém iremos nos focar inicialmente apenas nos Quarks de validate
.
Então vamos ver como é o Quark notEmptyStringValidate
:
// notEmptyStringValidate
'use strict';
const notEmpty = require('./notEmptyString');
module.exports = {
validator: (value) => {
return notEmpty.validate(value);
}
, message: 'O valor {VALUE} para o campo nome não pode ser vazio!'
};
Para facilitar o reuso eu separei os Quarks puros dos especificos para o Mongoose, pois o Quark do Mongose tem uma estrutura diferente do Quarks básico, os nossos Quarks não possuem o campo de mensagem, por exemplo.
Então no caso eu uso o Quark isEmptyString que utiliza os Quarks isEmpty e isString:
// isEmptyString
'use strict';
module.exports = (value) => {
const isEmpty = require('../isEmpty')(value)
const isString = require('../../isString/isString')(value)
if (isEmpty && isString) return true;
return false;
};
Que por sua vez usa 2 outros Quarks:
// isEmpty
module.exports = (value) => {
const isNull = (value === null);
const isUndefined = (value === undefined);
const isEmpty = (value === '');
if (isNull || isUndefined || isEmpty) return true;
return false;
}
// isString
module.exports = (value) => {
if (typeof value === 'string' || value instanceof String) return true;
return false;
}
Dessa forma podemos criar Quarks mais complexos apenas agregando Quarks menores.
Basicamente separamos os Quarks em 2 tipos:
- Modificadores: to{Name}
- Respondedores: is{Name}
Porém ainda temos outros tipos especiais como de validação do Mongoose.
Esse tipo de Quark é aquele que recebe 1 valor como entrada e retorna outro valor modificado.
Por exemplo o toUpperCase
:
module.exports = (value) => value.toUpperCase();
Um Quark básico para validação deverá retornar APENAS 1 valor booleano (ou true
ou false
) e deverá receber APENAS 1 valor como parâmetro da sua função.
Devemos começar com os Quarks que devem responder alguma pergunta simples, por exemplo:
- isEmpty
- isString
- isNumber
- isDate
- isBool
- etc
Nesse caso o padrão de módulo para esse tipo de Quark é:
'use strict';
module.exports = (value) => {
if (validação) return true;
return false;
}
Como esses Quarks serão usados como validação sempre iniciaremos retornando false
, pois caso passe por todos os testes que o validem como verdadeiro a função acabará chegando ao seu final para retornar false
, com isso invalidando o valor passado.
Vamos pegar o exemplo do isEmpty
:
'use strict';
module.exports = (value) => {
if (value === null || value === undefined || value === '') return true;
return false;
}
Mas podemos refatorar esse código dessa forma:
// isEmpty
'use strict';
module.exports = (value) => {
const isNull = (value === null);
const isUndefined = (value === undefined);
const isEmpty = (value === '');
if (isNull || isUndefined || isEmpty) return true;
return false;
}
Agora podemos criar Quarks mais complexos reusando esse Quark toda vez que precisemos garantir que um valor não seja nem null
nem undefined
nem ''
, ou seja, praticamente em todos os Átomos que definam required: true
.
Vamos criar, como exemplo, o Quark isNotEmptyMoney
que além de testar se não é vazio também deve verificar se o valor é maior que 0
, pois se for 0
será nossa definição de dinheiro vazio
:
'use strict';
module.exports = (value) => {
// validação base
const validated = require('./isEmpty')(value)
if (validated) return true;
// validação se é Number
// validação se maior que 0
return false;
};
Você deve se perguntar: por que 2 validações ali sendo que poderia ser 1 só?
Tudo bem agora eu lhe respondo com outra pergunta:
Quando vc vai validar o valor de qualquer campo numérico, o que você SEMPRE terá que fazer?
- Validar se é numérico
Pronto ta aí sua resposta. :p
Então sabemos que para adicionar outra validação basta fazer a mesma coisa da validação isEmpty
:
'use strict';
module.exports = (value) => {
// validação base
let isEmpty = require('./isEmpty')(value)
if (isEmpty) return false;
// validação se é Number
// validação se maior que 0
isNumber = require('./isNumber')(value)
if(!isNumber) return false;
if(value > 0) return true;
return false;
};
Vizualize comigo a lógica padrão dos nossos testes:
- SEMPRE testar se é vazio
- Se for vazio já retorna
false
para não precisar testar o resto - Se não for você irá testar o tipo do valor
- Se não for do tipo esperado já pode retornar
false
- Caso passe por esses teste você irá validar a regra de negócio
- Se for verdadeira pode retornar
true
- Se não entrar em nenhum
if
finaliza retornandofalse
Agora adicionamos o Quark novo isNumber
que ainda não existe, então vamos criá-lo:
'use strict';
module.exports = (value) => {
if(!isNaN(value)) return true;
return false;
};
Criamos uma validação bem simples que testa se o value
não é um NaN
, que significa Not a Number
, logo se for falso para isNaN(value)
o value
é um número, porém podemos garantir melhor com mais um teste, verificar se é um número finito:
'use strict';
module.exports = (value) => {
if(!isNaN(parseFloat(value)) && isFinite(value)) return true;
return false;
};
Pronto agora temos o Quark isNotEmptyMoney
que iremos utilizar no Quark especifico de validação do Mongoose, isNotEmptyMoneyValidate
:
'use strict';
module.exports = {
validator: (value) => {
return require('./isNotEmptyMoney')(value);
}
, message: 'O valor {VALUE} não pode ser vazio e precisa ser maior que 0!'
};
Dessa forma separamos todas as funções reusáveis do nosso sistema, pois uma vez criada qualquer função deverá ser apenas usada.
Como já visto anteriormente o padrão para o Quark de validação do Mongoose é:
'use strict';
module.exports = {
validator: (value) => {}
, message: 'O valor {VALUE} para o campo ...'
};
Ou seja, é apenas um objeto com 2 atributos:
- validator: função validadora
- message: mensagem de erro
Logo não precisamos criar 1 Quark com toda sua lógica dentro de validator
, precisamos apenas chamar o Quark dessa função:
'use strict';
module.exports = {
validator: (value) => {
return require('./isNotEmptyMoney')(value);
}
, message: 'O valor {VALUE} não pode ser vazio e precisa ser maior que 0!'
};
Não se preocupe que logo mais as mensagens também virarão Quarks. ;)
Depois de entender como funcionam os Quarks podemos listar alguns que sempre usaremos e que não dependem de outros, como:
- isEmpty
- isNumber
- isString
- isBoolean
- isDate
Depois deles podemos listar alguns Quarks específicos para campos:
- isUserID
- isName
- isEmail
- isDateOfBirth
- isPhone
- isPhoneDDD
- isPhoneNumber
- isCPF
- isCPNJ
- isAddress
- isAddressType //logradouro
- isAddressName
- isAddressNumber
- isAddressCEP
- isAddressCity
- isAddressState
- isAddressCountry
Todos eles usarão os Quarks base internamente.
Como devemos testar nossos Quarks?
Para isso vamos utilizar o Chai com expect
que foi ensinado na aula 9.
Vamos entender como escrever 1 teste com ele:
'use strict';
const expect = require('chai').expect;
const valueTRUE = 'Suissa';
const valueFALSE = 1;
describe('isString', () => {
describe('é String', () => {
it('testando: "'+valueTRUE+'"', () => {
expect(require('./isString')(valueTRUE)).to.equal(true);
});
});
describe('não é String', () => {
it('testando: "'+valueFALSE+'"', () => {
expect(require('./isString')(valueFALSE)).to.equal(false);
});
});
});
Então basicamente separamos nossos testes em 2:
- validado: 'é String'
- não validado: 'não é String'
describe('é String', () => {
it('testando: '+valueTRUE, () => {
expect(require('./isString')(valueTRUE)).to.equal(true);
});
});
describe('não é String', () => {
it('testando: '+valueFALSE, () => {
expect(require('./isString')(valueFALSE)).to.equal(false);
});
});
Para rodar esse teste basta executar mocha isString/isString.test.js
:
mocha isString/isString.test.js
isString
é String
✓ testando: Suissa
não é String
✓ testando: 1
2 passing (28ms)
Pronto ele não teve erros pois validamos nossos testes corretamente, porém testamos apenas com 1 valor e isso é ridículo né?
Então vamos agora criar um teste que valide vários valores, para fazer isso iniciamos colocando os valores verdadeiros e falsos em arrays:
'use strict';
const expect = require('chai').expect;
const valuesTRUE = ['Suissa', '1', '', ' '];
const valuesFALSE = [null, undefined, 1, true, {}, ()=>{}];
Certo? Então definimos em valuesTRUE
todos os valores possíveis que devem ser aceitos como String e em valuesFALSE
todos os valores que não podem ser String.
Agora criamos a estrutura para os 2 testes:
'use strict';
const expect = require('chai').expect;
const valuesTRUE = ['Suissa', '1', '', ' '];
const valuesFALSE = [null, undefined, 1, true, {}, ()=>{}];
describe('isString', () => {
describe('é String', () => {
});
describe('não é String', () => {
});
});
Bem simples não? Então basta fazer o que?
Criar uma função que itere sobre os valores do array e vá executando o it
que é a função que irá testar realmente os valores:
it('testando: '+valueTRUE, () => {
expect(require('./isString')(valueTRUE)).to.equal(true);
});
Bom então sabemos que precisamos fazer 1 it
para cada valor do array e obviamente não faremos isso manualmente, correto?
Então como faremos?
forEach
Mas como?
Dessa forma:
valuesTRUE.forEach( function(element, index) {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(true);
});
});
Percebeu que ele irá criar 1 it
dinamicamente para cada item do array valuesTRUE
?
Agora basta juntarmos tudo para ficar assim:
'use strict';
const expect = require('chai').expect;
const valuesTRUE = ['Suissa', '1', '', ' '];
const valuesFALSE = [null, undefined, 1, true, {}, ()=>{}];
describe('isString', () => {
describe('é String', () => {
valuesTRUE.forEach( function(element, index) {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(true);
});
});
});
describe('não é String', () => {
valuesFALSE.forEach( (element, index) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(false);
});
});
});
});
Executando nosso teste ficará assim:
mocha isString/isString.test.js
isString
é String
✓ testando: Suissa
✓ testando: 1
✓ testando:
✓ testando:
não é String
✓ testando: null
✓ testando: undefined
✓ testando: 1
✓ testando: true
✓ testando: [object Object]
✓ testando: ()=>{}
10 passing (28ms)
Mas é óbvio que ainda podemos melhorar esse código refatorando-o, acompanhe comigo pois iremos separar as funções de teste dos describe
s:
'use strict';
const expect = require('chai').expect;
const valuesTRUE = ['Suissa', '1', '', ' '];
const valuesFALSE = [null, undefined, 1, true, {}, ()=>{}];
const testTRUE = (values) => {
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(true);
});
});
};
const testFALSE = (values) => {
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(false);
});
});
};
describe('isString', () => {
describe('é String', () => testTRUE(valuesTRUE));
describe('não é String', () => testFALSE(valuesFALSE));
});
OK! Mas para que isso?
Ahhhhhhhh! Você ainda não notou o padrão?
Perceba essas duas funções: testTRUE
e testFALSE
.
Conseguiu ver o padrão agora?
Ainda não? Então vamos analisar!
const testTRUE = (values) => {
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(true);
});
});
};
const testFALSE = (values) => {
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(false);
});
});
};
Vamos retirar apenas o miolo delas:
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(true);
});
});
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(false);
});
});
Agora sim você deve ter percebido que o único valor que mudou nas 2 foi... ???
O valor que passamos para função
to.equal
!
Pronto! Agora basta mudarmos esse valor para uma variável que as 2 funções ficarão iguais:
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(valueToTest);
});
});
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(valueToTest);
});
});
Aí você deve se perguntar:
De onde vem o valor de
valueToTest
?
Ótima pergunta! Vem pela função genérica que iremos criar:
'use strict';
const expect = require('chai').expect;
const valuesTRUE = ['Suissa', '1', '', ' '];
const valuesFALSE = [null, undefined, 1, true, {}, ()=>{}];
const test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(valueToTest);
});
});
};
describe('isString', () => {
describe('é String', () => test(valuesTRUE, true));
describe('não é String', () => test(valuesFALSE, false));
});
Acho que agora temos um padrão bem simples e claro para utilizar em nossos testes, não?
Bom eu ainda quero refatorar mais um pouco, mas o que podemos fazer então?
Podemos ver que dentro do describe
master describe('isString'
nós SEMPRE teremos apenas 2 describe
s:
describe('isString', () => {
describe('é String', () => test(valuesTRUE, true));
describe('não é String', () => test(valuesFALSE, false));
});
Agora vamos analisar o padrão deles:
const messageTRUE = 'é String';
const messageFALSE = 'não é String';
describe('isString', () => {
describe(messageTRUE, () => test(valuesTRUE, true));
describe(messageFALSE, () => test(valuesFALSE, false));
});
Sabemos então que o describe
é formado de:
- mensagem para o teste
- função que executa o teste
O que precisamos fazer é criar um objeto que possa agregar toda essa lógica, por exemplo:
const valuesTRUE = ['Suissa', '1', '', ' '];
const valuesFALSE = [null, undefined, 1, true, {}, ()=>{}];
const test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(valueToTest);
});
});
};
const describes = [
{type: true, message: 'é String', test: test}
, {type: false, message: 'não é String', test: test}
];
Então em 1 objeto nós temos:
- type: tipo do teste
- message: mensagem do teste
- test: função de validação
Estamos usando a mesma função genérica test
criada anteriormente e agora como faço isso funcionar com o describe
?
Então aqui precisamos entender que não podemos fazer como no it
, que deixamos bem genérico, em vez disso precisamos obrigatoriamente ter 2 describe
s separados.
Podemos fazer da seguinte forma:
- itere no array
describes
- teste o
type
dodescribe
- crie o
describe
correto a partir dotype
- chame a função
test
corretamente
Fazendo isso nosso código ficará assim:
describe('isString', () => {
describes.forEach( (element, index) => {
if(element.type) {
describe(element.message, () => {
test(valuesTRUE, element.type);
});
}
else {
describe(element.message, () => {
test(valuesFALSE, element.type);
});
}
});
});
Perceba que quando ele entrar em if(element.type)
só entrará com o objeto com o type=true
, nesse caso irá criar o describe
correto para os teste que devem dar true
e logo após no else
cria o describe
para os valores que devem dar false
.
Juntando tudo isso nosso código ficou assim:
'use strict';
const expect = require('chai').expect;
const valuesTRUE = ['Suissa', '1', '', ' '];
const valuesFALSE = [null, undefined, 1, true, {}, ()=>{}];
const test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(valueToTest);
});
});
};
const describes = [
{type: true, message: 'é String', test: test}
, {type: false, message: 'não é String', test: test}
]
describe('isString', () => {
describes.forEach( (element, index) => {
if(element.type) {
describe(element.message, () => {
test(valuesTRUE, element.type);
});
}
else {
describe(element.message, () => {
test(valuesFALSE, element.type);
});
}
});
});
Agora execute ele no terminal:
mocha isString/isString.test.module.js
isString
é String
✓ testando: Suissa
✓ testando: 1
✓ testando:
✓ testando:
não é String
✓ testando: null
✓ testando: undefined
✓ testando: 1
✓ testando: true
✓ testando: [object Object]
✓ testando: ()=>{}
10 passing (16ms)
Agora sabe o que seria bom?
Dar uma refatoradinha marota!
MAS POR QUE CARAIOOOOO!!!??
Apenas observe:
'use strict';
const expect = require('chai').expect;
const test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(valueToTest);
});
});
};
const describes = [
{ type: true
, message: 'é String'
, test: test
, values: ['Suissa', '1', '', ' ']
}
,
{ type: false
, message: 'não é String'
, test: test
, values: [null, undefined, 1, true, {}, ()=>{}]
}
]
describe('isString', () => {
describes.forEach( (element, index) => {
if(element.type) {
describe(element.message, () => {
test(element.values, element.type);
});
}
else {
describe(element.message, () => {
test(element.values, element.type);
});
}
});
});
Ah legal você só mudou os valores para dentro do objeto e aí?
Tente advinhar o porquê eu fiz isso!
Tá vou te ajudar.
Imagine que você quer transformar agora esse código em um módulo de testes genérico, como você faria? Que possa ser usado dessa forma:
const describes = [
{ type: true
, message: 'é String'
, values: ['Suissa', '1', '', ' ']
}
,
{ type: false
, message: 'não é String'
, values: [null, undefined, 1, true, {}, ()=>{}]
}
];
require('./index')('isString', describes);
Vou lhe falar como eu faria então.
Perceba que não tenho mais a função test
nesse objeto pois não é da responsabilidade dele conhecer essa função, sua única responsabilidade é ter os dados necessários para testar, nossa função test
já é genérica para funcionar sem precisar ser definida anteriormente.
Beleza então vamos criar o testModule/testModule.js
:
'use strict';
const expect = require('chai').expect;
module.exports = (testName, describes) => {
}
Agora você entenderá o porquê passamos o testName
também, antes de refatorarmos vamos analisar a função test
:
const test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./isString')(element)).to.equal(valueToTest);
});
});
};
Percebeu que o únco valor definido diretamente ali é o './isString'
?
É exatamente por isso que passamos esse valor para nosso módulo em vez de definir manualmente, para que dessa forma ele possa funcionar com qualquer outro módulo.
Então nossa função refatorada fica assim:
const test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./../'+testName+'/'+testName)(element)).to.equal(valueToTest);
});
});
};
Claro que precisamos definir um padrão de pastas para que funcione sem problemas, mas isso é assunto para outra aula :p
Depois disso basta colocar o testName
no primeiro describe
e pronto!
'use strict';
const expect = require('chai').expect;
module.exports = (testName, describes) => {
const test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./../'+testName+'/'+testName)(element)).to.equal(valueToTest);
});
});
};
describe(testName, () => {
describes.forEach( (element, index) => {
if(element.type) {
describe(element.message, () => {
test(element.values, element.type);
});
}
else {
describe(element.message, () => {
test(element.values, element.type);
});
}
});
});
};
Agora sim você pode testar qualquer Quark nosso facilmente dessa forma:
'use strict';
const describes = [
{ type: true
, message: 'é String'
, values: ['Suissa', '1', '', ' ']
}
,
{ type: false
, message: 'não é String'
, values: [null, undefined, 1, true, {}, ()=>{}]
}
];
require('./testModule')('isString', describes);
BEM MELHOR AGORA NÃO??!!
Que bom pois esse é o nosso padrão para testes!
Agora chegamos a um impasse, sabe porquê?
Sim imagine que nossa função a ser testada não receba apenas 1 valor, mas sim 2!
Vou pegar um exemplo nosso, o isInArray
.
'use strict';
module.exports = (value, list) => {
const isIn = require('./createIsIn')(list)
const validated = isIn(value);
if (validated) return true;
return false;
};
Que usa o createIsIn
:
'use strict';
module.exports = (list) => (value) => !(list.indexOf(value) === -1);
Por que o isInArray recebe 2 valores e não apenas 1 como os outros?
Bom é bem simples, primeiro você precisa entender o que é o isIn
.
Ele é basicamente uma lista de valores onde para um valor ser aceito ele precisa obrigatoriamente existir nessa lista.
Ou seja, para testarmos essa funcionalidade precisamos passar o valor a ser testado e a lista de valores aceitáveis, correto?
Por isso testamos o createIsIn
que irá receber essa lista, no caso o array e irá retornar um valor booleano caso o value
exista em alguma posição de list
.
Como a função indexOf
irá retornar a posição em que encontrou o value
e caso não encontre ele retorna -1
, por isso testamos se o resultado é igual a -1
, pois se ele não achar e for igual a -1
irá resultar em verdadeiro certo?
Por isso que negamos esse resultado com !
para que retorne verdadeiro apenas se esse teste ist.indexOf(value) === -1
for falso. Simples não?
Com isso podemos usar o retorno desse módulo para validar se um valor se encontra no array:
const isIn = require('./createIsIn')(list)
const validated = isIn(value);
if (validated) return true;
Mas se você perceber passamos apenas o list
para esse módulo então como ele consegue testar o value
?
Analise comigo o createIsIn
:
module.exports = (list) => (value) => !(list.indexOf(value) === -1);
Esse código diz que meu módulo é uma função que recebe list
como parâmetro para ele ser construido:
module.exports = (list) => {};
E essa funçao irá retornar outra função que recebe value
como parâmetro:
(value) => !(list.indexOf(value) === -1)
Por isso usamos o módulo dessa forma, primeiramente passando o list
e recebendo uma função para depois usar essa função passando o value
:
const isIn = require('./createIsIn')(list)
const validated = isIn(value);
if (validated) return true;
Mas não vamos parar por aí, vamos dar aquela refatorada de sempre. :p
Analisando nosso código conseguimos perceber que se o validated
for true
ele irá retornar true
e se for false
ele não entra no if
e vai direto para o return false
.
Sabendo disso refatoramos o código assim:
module.exports = (value, list) => {
const isIn = require('./createIsIn')(list)
return isIn(value);
};
Certo?
Então olha o que ainda podemos fazer com esse maravilhoso JavaScript:
module.exports = (value, list) => {
return require('./createIsIn')(list)(value);
};
Loco não?
Pois é aprenderemos mais sobre Currying no módulo sobre JS Funcional logo após o módulo de ES6!
Só faltou só fazermos o teste mais básico desse módulo!
Qual seria?
Pense comigo, se o módulo testa se um valor está no array então o que ele deve testar antes de verificar se o valor existe no array?
Duas coisas:
- não é vazio?
- é array?
Então bora colocar esses testes no módulo isInArray
:
module.exports = (value, list) => {
const isEmptyValue = require('./../isEmpty/isEmpty')(value);
const isEmptyArray = require('./../isEmpty/isEmpty')(list);
const isArray = require('./../isArray/isArray')(list);
if(!isEmptyValue && !isEmptyArray && isArray)
return require('./createIsIn')(list)(value);
return false;
};
Bom o isEmpty
já conhecemos porém estamos usando o isArray
que ainda não existe, logo precisamos criá-lo:
module.exports = (value) => {
return (value instanceof Array);
}
Super simples né?
Agora podemos fazer esse teste simples com o isArray
:
const assert = require('assert');
const valueTRUE = [1, 2];
const valueFALSE = '1';
assert.equal(true, require('./isArray')(valueTRUE));
assert.equal(false, require('./isArray')(valueFALSE));
console.log(valueTRUE + ' é um Array?', require('./isArray')(valueTRUE));
console.log(valueFALSE + ' é um Array?', require('./isArray')(valueFALSE));
Podemos agora voltar ao isInArray
para ver como usamos esse módulo:
const assert = require('assert');
const list = ['suissa', 'itacir'];
const valueTRUE = 'suissa';
const valueFALSE = 'pitchulo';
assert.equal(true, require('./isInArray')(valueTRUE, list));
assert.equal(false, require('./isInArray')(valueFALSE, list));
Agora levando para o nosso conceito mais genérico precisamos nos ater ao segundo parâmetro list
e pensar então como podemos passar esses valores na forma que aprendemos.
Podemos fazer o seguinte, adicionar o list
no nosso array describes
:
'use strict';
const describes = [
{ list: ['suissa', 'itacir'] }
, { type: true
, message: 'é ENUM'
, values: ['suissa', 'itacir']
}
, { type: false
, message: 'não é ENUM'
, values: [null, undefined, 1, true, {}, ()=>{}]
}
];
require('./testModuleCreate')('isInArray', describes);
Perceba que estou usando um módulo diferente, o testModuleCreate
, pois precisaremos modificar o nosso testModule
e para não dar merda preferi criar um novo.
A primeira coisa que devemos fazer é a lógica do list
:
'use strict';
const expect = require('chai').expect;
module.exports = (testName, describes) => {
const list = describes.splice(0,1);
};
O que fizemos ali em cima foi retirar o primeiro elemento do array describes
que é onde está nosso list
, porém olhe só o resultado de describes.splice(0,1)
:
[ { list: [ 'suissa', 'itacir' ] } ]
Isso acontece porque o splice
nos retorna um array com os elementos retirados, então vamos ajeitar essa função para pegar apenas:
[ 'suissa', 'itacir' ]
Você já deve ter imaginado como né? Isso mesmo assim:
'use strict';
const expect = require('chai').expect;
module.exports = (testName, describes) => {
const list = describes.splice(0,1)[0].list;
};
Onde describes.splice(0,1)[0]
irá pegar o primeiro elemento do array retornado e depois acessamos a propriedade list
com describes.splice(0,1)[0].list
.
Beleza após isso precisamos modificar apenas o require
do módulo pois precisamos adicionar o parâmetro list
dessa forma:
const list = describes.splice(0,1)[0].list;
const test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
expect(require('./../'+testName+'/'+testName)(element, list)).to.equal(valueToTest);
});
});
};
Ou seja, modificamos apenas essas partes para que o módulo aceite um tipo de teste diferente, ficando assim:
'use strict';
const expect = require('chai').expect;
module.exports = (testName, describes) => {
const list = describes.splice(0,1)[0].list;
const test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element, list);
expect(validated).to.equal(valueToTest);
});
});
};
describe(testName, () => {
describes.forEach( (element, index) => {
if(element.type) {
describe(element.message, () => {
test(element.values, element.type);
});
}
else {
describe(element.message, () => {
test(element.values, element.type);
});
}
if(element.list) return true;
});
});
};
Bom você com certeza deve se perguntar:
Bah teve que criar outro módulo só por causa dessa modificação?
Teve apenas para facilitar nossa visualização do padrão, você já percebeu qual é?
Simples!
Em um módulo ele não trabalha com o list
e no outro trabalha, então precisamos criar uma lógica para que possamos trabalhar com apenas 1 módulo.
Basta fazer o que?
Vamos inciar com um simples if/else
:
let test = () => {};
if(describes[0].list) {
const list = describes.splice(0,1)[0].list;
test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element, list);
expect(validated).to.equal(valueToTest);
});
});
};
}
else {
test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element);
expect(validated).to.equal(valueToTest);
});
});
};
}
Porém não precisamos desse else
, consegue ver como ficará?
Assim:
let test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element);
expect(validated).to.equal(valueToTest);
});
});
};
if(describes[0].list) {
const list = describes.splice(0,1)[0].list;
test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element, list);
expect(validated).to.equal(valueToTest);
});
});
};
}
Entendeu o porquê retiramos o else
?
Porque definimos a função test
com o seu padrão no início e depois só sobrescrevemos ela caso o describes
possua list
no nosso padrão de objeto.
Pronto com isso finalizamos esse módulo genérico de testes para nossos Quarks.
'use strict';
const expect = require('chai').expect;
module.exports = (testName, describes) => {
let test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element);
expect(validated).to.equal(valueToTest);
});
});
};
if(describes[0].list) {
const list = describes.splice(0,1)[0].list;
test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element, list);
expect(validated).to.equal(valueToTest);
});
});
};
}
describe(testName, () => {
describes.forEach( (element, index) => {
describe(element.message, () => {
test(element.values, element.type);
});
if(element.list) return true;
});
});
};
Para testar um Quark is você fará assim:
const describes = [
{ type: true
, message: 'é String'
, values: ['Suissa', '1', '', ' ']
}
,
{ type: false
, message: 'não é String'
, values: [null, undefined, 1, true, {}, ()=>{}]
}
];
require('./testModuleGeneric')('isString', describes);
Para testar um Quark isIn você fará assim:
const describes = [
{ list: ['suissa', 'itacir'] }
, { type: true
, message: 'está array'
, values: ['suissa', 'itacir']
}
, { type: false
, message: 'não está array'
, values: ['vai corintia!', null, undefined, 1, true, {}, ()=>{}]
}
];
require('./testModuleGeneric')('isInArray', describes);
Nós já iniciamos a refatoração desse módulo para aceitar também o teste do Quark isIn porém ainda temos os testes para o Quark to, como faremos para adicionar mais esse teste?
Primeiramente vamos criar o array describes
que devemos passar para o Quark:
const describes = [
{ type: true
, message: 'to LOWER'
, values: ['Suissa', 'Itacir']
, valuesExpected: ['suissa', 'itacir']
}
, { type: false
, message: 'não to LOWER'
, values: ['Suissa', 'Itacir']
, valuesExpected: ['Suissa', 'Itacir']
}
];
Percebeu que adicionei o array valuesExpected
?
Ele funciona como um espelho do array values
que são os valores que irão entrar no Quark, sendo cada cada valor de valuesExpected
o valor esperado após a transformação.
Você SEMPRE deve colocar a mesma quantidade de elementos nos 2 arrays!
Para depois chamar assim:
require('./testModuleGeneric')('toLowerCase', describes);
Mas é óbvio que esse teste não irá funcionar pois ainda não criamos a lógica para ele em testModuleGeneric
, se executarmos esse teste seu resultado será:
toLowerCase
to LOWER
1) testando: Suissa
2) testando: Itacir
não to LOWER
3) testando: Suissa
4) testando: Itacir
0 passing (70ms)
4 failing
1) toLowerCase to LOWER testando: Suissa:
AssertionError: expected 'suissa' to equal true
at Context.<anonymous> (testModule/testModuleGeneric.js:10:30)
2) toLowerCase to LOWER testando: Itacir:
AssertionError: expected 'itacir' to equal true
at Context.<anonymous> (testModule/testModuleGeneric.js:10:30)
3) toLowerCase não to LOWER testando: Suissa:
AssertionError: expected 'suissa' to equal false
at Context.<anonymous> (testModule/testModuleGeneric.js:10:30)
4) toLowerCase não to LOWER testando: Itacir:
AssertionError: expected 'itacir' to equal false
at Context.<anonymous> (testModule/testModuleGeneric.js:10:30)
Isso acontece porque ele está testando os valores transformados com o valor de describes type
e nós queremos que ele teste com os valores de valuesExpected
, então como faremos isso?
Primeiramente precisamos testar qual o tipo de teste iremos executar, para isso iremos testar o tipo pelo nome do teste, pois o mesmo deve indicar seu tipo.
Para testar esse valor iremos usar o indexOf
para verificar se o prefixo do teste to
existe no testName
:
let test = (values, valueToTest) => {
if(testName.indexOf('to') > -1){
}
else {
values.forEach( (element, index) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element);
expect(validated).to.equal(valueToTest);
});
});
}
};
Tudo bem e agora o que colocamos dentro do if
?
A mesma coisa do else
?
CLARO QUE NÃO NÉ!!!
Vamos atacar primeiramente a lógica do forEach
:
values.forEach( (element, index) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element);
expect(validated).to.equal(valueToTest);
});
});
Devemos então criar uma lógica para que dentro do forEach
ele consiga testar o retorno de validate
contra o valor correto em valuesExpected
:
values.forEach( (element, index) => {
it('testando: '+element+' com '+valueConverted, () => {
let validated = require('./../'+testName+'/'+testName)(element);
if(valueToTest) expect(validated).to.equal(VALOR_ESPERADO);
});
});
Sabemos que o VALOR_ESPERADO
é um array com essa estrutura:
['suissa', 'itacir']
Sabendo que nosso primeiro teste sempre será verdadeiro, podemos tentar isso:
values.forEach( (element, index) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element);
console.log('valueToTest', valueToTest);
console.log('validated', validated);
console.log('valuesExpected', describes[0].valuesExpected[index]);
if(valueToTest) expect(validated).to.equal(describes[0].valuesExpected[index]);
});
});
Se você rodar esse teste resultará nisso:
to LOWER
valueToTest true
validated suissa
valuesExpected suissa
✓ testando: Suissa
valueToTest true
validated itacir
valuesExpected itacir
✓ testando: Itacir
não to LOWER
valueToTest false
validated suissa
valuesExpected suissa
✓ testando: Suissa
valueToTest false
validated itacir
valuesExpected itacir
✓ testando: Itacir
4 passing (17ms)
Todos os testes passaram mesmo estando errado porque ele está testando validated
com valuesExpected
sendo que o validate
possui o valor transformado e como estamos estando apenas o teste verdadeiro, logo ele sempre dará true
.
Para resolver esse problema precisamos antes de tudo separar os teste de verdadeiro e falso, para isso entenda como a função test
é chamada dentro do describes.forEach
:
describes.forEach( (element, index) => {
if(element.type) {
describe(element.message, () => {
test(element.values, element.type);
});
}
else {
describe(element.message, () => {
test(element.values, element.type);
});
}
if(element.list) return true;
});
O valor element.values
que é passados para a função test
é o array values
do array describes
e o segundo parâmetro element.type
é true
ou false
apenas.
Sabendo disso podemos definir de qual array iremos testar os valores definindo qual o índice dele dessa forma:
let valuesExpectedIndex = 0; //verdadeiro
if(!valueToTest) valuesExpectedIndex = 1; //falso
Pois o valor de valueToTest
será OU true
OU false
, então definimos que o valuesExpectedIndex
será 0
, porém se valueToTest
for false
definimos valuesExpectedIndex
como 1
pois é seu índice em describes
.
Agora precisamos resolver a lógica de qual valor será transformado.
let valueConverted = 0;
values.forEach( (element, index) => {
valueConverted = describes[valuesExpectedIndex].valuesExpected[index];
console.log('valueConverted', valueConverted);
});
Conferindo nossos valores:
valueConverted suissa
valueConverted itacir
valueConverted Suissa
valueConverted Itacir
Ok já conseguimos pegar os valores corretos agora vamos para teste propriamente dito:
if(valueToTest) expect(validated).to.equal(describes[0].valuesExpected[index]);
Com esse código testamos apenas o teste verdadeiro, então fica fácil criar o teste falso:
if(valueToTest) expect(validated).to.deep.equal(describes[valuesExpectedIndex].valuesExpected[index]);
else expect(validated).to.deep.not.equal(describes[valuesExpectedIndex].valuesExpected[index]);
Usamos o to.deep.not.equal
pois testamos o validated
, que é nosso valor transformado, com o valor de valuesExpected
que não estão em lowerCase.
Deixando nosso if
desse tipo de teste assim:
if(testName.indexOf('to') > -1){
let valuesExpectedIndex = 0;
if(!valueToTest) valuesExpectedIndex = 1;
let valueConverted = 0;
values.forEach( (element, index) => {
valueConverted = describes[valuesExpectedIndex].valuesExpected[index];
it('testando: '+element+' com '+valueConverted, () => {
let validated = require('./../'+testName+'/'+testName)(element);
if(valueToTest) expect(validated).to.deep.equal(describes[valuesExpectedIndex].valuesExpected[index]);
else expect(validated).to.deep.not.equal(describes[valuesExpectedIndex].valuesExpected[index]);
});
});
}
O código todo ficou assim:
'use strict';
const expect = require('chai').expect;
module.exports = (testName, describes) => {
let test = (values, valueToTest) => {
if(testName.indexOf('to') > -1){
let valuesExpectedIndex = 0;
if(!valueToTest) valuesExpectedIndex = 1;
console.log('values', values)
let valueConverted = 0;
values.forEach( (element, index) => {
valueConverted = describes[valuesExpectedIndex].valuesExpected[index];
console.log('valueConverted', valueConverted);
it('testando: '+element+' com '+valueConverted, () => {
let validated = require('./../'+testName+'/'+testName)(element);
if(valueToTest) expect(validated).to.deep.equal(describes[valuesExpectedIndex].valuesExpected[index]);
else expect(validated).to.deep.not.equal(describes[valuesExpectedIndex].valuesExpected[index]);
});
});
}
else {
values.forEach( (element, index) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element);
expect(validated).to.equal(valueToTest);
});
});
}
};
if(describes[0].list) {
const list = describes.splice(0,1)[0].list;
test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element, list);
expect(validated).to.equal(valueToTest);
});
});
};
}
describe(testName, () => {
describes.forEach( (element, index) => {
if(element.type) {
describe(element.message, () => {
test(element.values, element.type);
});
}
else {
console.log('aqui começa')
describe(element.message, () => {
console.log('element.values', element.values)
console.log('element.type', element.type)
test(element.values, element.type);
});
}
if(element.list) return true;
});
});
};
Vamos testar esse módulo forçando um erro no teste falso:
const describes = [
{ type: true
, message: 'to LOWER'
, values: ['Suissa', 'Itacir']
, valuesExpected: ['suissa', 'itacir']
}
, { type: false
, message: 'não to LOWER'
, values: ['Suissa', 'Itacir']
, valuesExpected: ['suissa', 'Itacir']
}
];
require('./testModuleGeneric')('toLowerCase', describes);
Executando-o nos retornará isso:
toLowerCase
to LOWER
✓ testando: Suissa com suissa
✓ testando: Itacir com itacir
não to LOWER
1) testando: Suissa com suissa
✓ testando: Itacir com Itacir
3 passing (16ms)
1 failing
1) toLowerCase não to LOWER testando: Suissa com suissa:
AssertionError: expected 'suissa' to not deeply equal 'suissa'
+ expected - actual
at Assertion.assertEqual (node_modules/chai/lib/chai/core/assertions.js:485:19)
at Assertion.ctx.(anonymous function) [as equal] (node_modules/chai/lib/chai/utils/addMethod.js:41:25)
at Context.<anonymous> (testModule/testModuleGeneric.js:19:46)
Beleza nosso módulo já está OK para esse tipo de teste, mas porra como ficou gigantesco!!!
Então é hora do que?
HORA DA REFATORAÇÃO!
Primeira coisa que devemos fazer é encapuslar as lógicas em funções.
Vamos começar pelo teste do Quark is:
else {
values.forEach( (element, index) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element);
expect(validated).to.equal(valueToTest);
});
});
}
Passando essa lógica para a função testQuarkIs
:
const testQuarkIs = (element, index) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element);
expect(validated).to.deep.equal(valueToTest);
});
};
Deixando o else
assim:
else {
values.forEach( (element, index) => {
testQuarkIs(element, index);
});
}
Melhorando mais um pouco:
else {
values.forEach(testQuarkIs);
}
Lindão né?
Agora vamos para o teste do Quark to:
if(testName.indexOf('to') > -1){
let valuesExpectedIndex = 0;
if(!valueToTest) valuesExpectedIndex = 1;
let valueConverted = 0;
values.forEach( (element, index) => {
valueConverted = describes[valuesExpectedIndex].valuesExpected[index];
console.log('valueConverted', valueConverted);
it('testando: '+element+' com '+valueConverted, () => {
let validated = require('./../'+testName+'/'+testName)(element);
if(valueToTest) expect(validated).to.deep.equal(describes[valuesExpectedIndex].valuesExpected[index]);
else expect(validated).to.deep.not.equal(describes[valuesExpectedIndex].valuesExpected[index]);
});
});
}
Passamos sua lógica inicialmente para a função itQuarkTo
:
const itQuarkTo = (element, index, valueToTest, valueConverted, valuesExpectedIndex) => {
it('testando: '+element+' com '+valueConverted, () => {
let validated = require('./../'+testName+'/'+testName)(element);
if(valueToTest) expect(validated).to.deep.equal(describes[valuesExpectedIndex].valuesExpected[index]);
else expect(validated).to.deep.not.equal(describes[valuesExpectedIndex].valuesExpected[index]);
});
};
Pois dentro do forEach
não usamos apenas element, index
por isso ainda não podemos fazer como a função testQuarkIs
:
if(testName.indexOf('to') > -1){
let valuesExpectedIndex = 0;
if(!valueToTest) valuesExpectedIndex = 1;
let valueConverted = 0;
values.forEach((element, index) => {
valueConverted = describes[valuesExpectedIndex].valuesExpected[index];
itQuarkTo(element, index, valueToTest, valueConverted, valuesExpectedIndex)
});
}
Tá mas então como faremos para encapsular toda essa lógica e deixar assim?
if(testName.indexOf('to') > -1){
testQuarkTo(values, valueToTest);
}
SIMPLES!!!
Basta encapsular a porra toda passando os mesmos parâmetros que chegam em test
:
const testQuarkTo = (values, valueToTest) => {
let valuesExpectedIndex = 0;
if(!valueToTest) valuesExpectedIndex = 1;
let valueConverted = 0;
values.forEach((element, index) => {
valueConverted = describes[valuesExpectedIndex].valuesExpected[index];
itQuarkTo(element, index, valueToTest, valueConverted, valuesExpectedIndex)
});
};
Ridicularmente simples né?
Bom se fizemos isso com essa lógica podemos facilmente fazer o mesmo para o Quark is:
const itQuarkIs = (element, index, valueToTest) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element);
expect(validated).to.deep.equal(valueToTest);
});
};
const testQuarkIs = (values, valueToTest) => {
values.forEach((element, index) => itQuarkIs(element, index, valueToTest));
};
Mudei todos os to.equal para to.deep.equal!
Olhe como ficou a função test
agora:
let test = (values, valueToTest) => {
if(testName.indexOf('to') > -1){
testQuarkTo(values, valueToTest);
}
else {
testQuarkIs(values, valueToTest);
}
};
Separando a verificação se o teste é do tipo to
:
let test = (values, valueToTest) => {
let isQuarkTo = (testName.indexOf('to') > -1);
if(isQuarkTo) testQuarkTo(values, valueToTest);
else testQuarkIs(values, valueToTest);
};
Já melhorou porém ainda temos uma parte que nao refatoramos nada, o teste do tipo isIn
:
if(describes[0].list) {
const list = describes.splice(0,1)[0].list;
test = (values, valueToTest) => {
values.forEach( (element) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element, list);
expect(validated).to.equal(valueToTest);
});
});
};
}
Separamos inicialmente seu it
:
const itQuarkIsIn = (element, index, list, valueToTest) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element, list);
expect(validated).to.equal(valueToTest);
});
};
Ficando assim:
if(describes[0].list) {
const list = describes.splice(0,1)[0].list;
test = (values, valueToTest) => {
values.forEach( (element, index) => {
itQuarkIsIn(element, index, list, valueToTest);
});
};
}
Agora basta encapsular o que tem dentro da função test
:
const testQuarkIsIn = (values, valueToTest, list) => {
values.forEach( (element, index) => {
itQuarkIsIn(element, index, list, valueToTest);
});
};
if(describes[0].list) {
const list = describes.splice(0,1)[0].list;
test = (values, valueToTest) => {
testQuarkIsIn(values, valueToTest, list);
};
}
Vai dizer que não ficou BEM MELHOR?
Porém olhe como está nosso módulo agora:
'use strict';
const expect = require('chai').expect;
module.exports = (testName, describes) => {
const itQuarkIs = (element, index, valueToTest) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element);
expect(validated).to.deep.equal(valueToTest);
});
};
const testQuarkIs = (values, valueToTest) => {
values.forEach((element, index) => itQuarkIs(element, index, valueToTest));
};
const itQuarkTo = (element, index, valueToTest, valueConverted, valuesExpectedIndex) => {
it('testando: '+element+' com '+valueConverted, () => {
let validated = require('./../'+testName+'/'+testName)(element);
if(valueToTest) expect(validated).to.deep.equal(describes[valuesExpectedIndex].valuesExpected[index]);
else expect(validated).to.deep.not.equal(describes[valuesExpectedIndex].valuesExpected[index]);
});
};
const testQuarkTo = (values, valueToTest) => {
let valuesExpectedIndex = 0;
if(!valueToTest) valuesExpectedIndex = 1;
let valueConverted = 0;
values.forEach((element, index) => {
valueConverted = describes[valuesExpectedIndex].valuesExpected[index];
itQuarkTo(element, index, valueToTest, valueConverted, valuesExpectedIndex)
});
};
const itQuarkIsIn = (element, index, list, valueToTest) => {
it('testando: '+element, () => {
let validated = require('./../'+testName+'/'+testName)(element, list);
expect(validated).to.equal(valueToTest);
});
};
let test = (values, valueToTest) => {
let isQuarkTo = (testName.indexOf('to') > -1);
if(isQuarkTo) testQuarkTo(values, valueToTest);
else testQuarkIs(values, valueToTest);
};
const testQuarkIsIn = (values, valueToTest, list) => {
values.forEach( (element, index) => {
itQuarkIsIn(element, index, list, valueToTest);
});
};
if(describes[0].list) {
const list = describes.splice(0,1)[0].list;
test = (values, valueToTest) => {
testQuarkIsIn(values, valueToTest, list);
};
}
describe(testName, () => {
describes.forEach( (element, index) => {
if(element.type) {
describe(element.message, () => {
test(element.values, element.type);
});
}
else {
describe(element.message, () => {
test(element.values, element.type);
});
}
if(element.list) return true;
});
});
};
Parece aqueles módulos Megazord!!!
Então precisamos fazer o que??
REFATORAR LÓGICO!!! :p
Vamos iniciar retirando algumas funções para módulos externos, começando pela itQuarkIs
:
const expect = require('chai').expect;
module.exports = (element, index, valueToTest, testName) => {
it('testando: '+element, () => {
let validated = require('./../../'+testName+'/'+testName)(element);
expect(validated).to.deep.equal(valueToTest);
});
};
Utilizando assim:
const itQuarkIs = require('./config/itQuarkIs');
const testQuarkIs = (values, valueToTest) => {
values.forEach((element, index) => {
itQuarkIs(element, index, valueToTest, testName)
});
};
Agora vamos para o itQuarkTo
:
const expect = require('chai').expect;
module.exports = (element, index, valueToTest, valueConverted, valuesExpectedIndex, testName, describes) => {
it('testando: '+element+' com '+valueConverted, () => {
let validated = require('./../../'+testName+'/'+testName)(element);
if(valueToTest) expect(validated).to.deep.equal(describes[valuesExpectedIndex].valuesExpected[index]);
else expect(validated).to.deep.not.equal(describes[valuesExpectedIndex].valuesExpected[index]);
});
}
Perceba que preciso passar 2 parâmetros, testName, describes
, adicionais para funcionar ficando assim sua chamada:
const itQuarkTo = require('./config/itQuarkTo');
const testQuarkTo = (values, valueToTest) => {
let valuesExpectedIndex = 0;
if(!valueToTest) valuesExpectedIndex = 1;
let valueConverted = 0;
values.forEach((element, index) => {
valueConverted = describes[valuesExpectedIndex].valuesExpected[index];
itQuarkTo(element, index, valueToTest, valueConverted, valuesExpectedIndex, testName, describes)
});
};
Agora separamos o itQuarkIsIn
:
const expect = require('chai').expect;
module.exports = (element, index, list, valueToTest, testName) => {
it('testando: '+element, () => {
let validated = require('./../../'+testName+'/'+testName)(element, list);
expect(validated).to.equal(valueToTest);
});
};
Ficando assim:
const itQuarkIsIn = require('./config/itQuarkIsIn');
const testQuarkIsIn = (values, valueToTest, list) => {
values.forEach( (element, index) => {
itQuarkIsIn(element, index, list, valueToTest, testName);
});
};
Agora vem o mais complicado, iremos modularizar as funções testQuark
começando pela testQuarkIs
:
const itQuarkIs = require('./itQuarkIs');
module.exports = (values, valueToTest, testName) => {
values.forEach((element, index) => {
itQuarkIs(element, index, valueToTest, testName)
});
};
// chamada testQuarkIs(values, valueToTest, testName);
Agora o testQuarkTo
:
const itQuarkTo = require('./itQuarkTo');
module.exports = (values, valueToTest, testName, describes) => {
let valuesExpectedIndex = 0;
if(!valueToTest) valuesExpectedIndex = 1;
let valueConverted = 0;
values.forEach((element, index) => {
valueConverted = describes[valuesExpectedIndex].valuesExpected[index];
itQuarkTo(element, index, valueToTest, valueConverted, valuesExpectedIndex, testName, describes)
});
};
Finalizando com o testQuarkIsIn
:
const itQuarkIsIn = require('./itQuarkIsIn');
module.exports = (values, valueToTest, list, testName) => {
values.forEach( (element, index) => {
itQuarkIsIn(element, index, list, valueToTest, testName);
});
};
Deixando nosso código muito mais legível:
'use strict';
const expect = require('chai').expect;
module.exports = (testName, describes) => {
const testQuarkIs = require('./config/testQuarkIs');
const testQuarkTo = require('./config/testQuarkTo');
const testQuarkIsIn = require('./config/testQuarkIsIn');
let test = (values, valueToTest) => {
let isQuarkTo = (testName.indexOf('to') > -1);
if(isQuarkTo) testQuarkTo(values, valueToTest, testName, describes);
else testQuarkIs(values, valueToTest, testName);
};
if(describes[0].list) {
const list = describes.splice(0,1)[0].list;
test = (values, valueToTest) => {
testQuarkIsIn(values, valueToTest, list, testName);
};
}
describe(testName, () => {
describes.forEach( (element, index) => {
if(element.type) {
describe(element.message, () => {
test(element.values, element.type);
});
}
else {
describe(element.message, () => {
test(element.values, element.type);
});
}
if(element.list) return true;
});
});
};
O código até que está legal mas eu ainda acho que dá para melhorar, então vamos lá.
Primeira coisa que iremos refatorar é chamada dos Quarks de teste:
const testQuarkIs = require('./config/testQuarkIs');
const testQuarkTo = require('./config/testQuarkTo');
const testQuarkIsIn = require('./config/testQuarkIsIn');
Então vamos analisar e retirar um padrão desse código:
const testQuark{TYPE} = require('./config/testQuark{TYPE}');
Vamos agrupar o {TYPE}
em um array pois assim poderemos adicionar mais tipos de testes futuramente, então já vamos aproveitar e deixar esse array em módulo separado:
module.exports = ['Is', 'IsIn', 'To'];
Agora vamos criar a mesma lógica da chamada dos Quarks porém de uma forma genérica para que ela fique aberta para expansão e fechada para modificações, remetendo ao Open Closed Principle (OCP) do S.O.L.I.D..
Como já temos o array com os tipos de testes, iremos verificar qual é o teste requisitado, já importando seu módulo:
const testTypes = require('./config/testTypesFactory');
let testQuark = null;
let findTest = (element) => {
let regex = new RegExp(element, 'i');
if(!!testName.match(regex)){
testQuark = require('./config/testQuark'+element);
}
};
testTypes.forEach(findTest);
Na função findTest
, que é usada no forEach
, eu crio uma expressão regular, new RegExp(element, 'i')
, para verificar se esse valor existe no nome do teste,testName
, passado para o nosso módulo.
Mas perceba que estou usando uma safadeza ali:
!!testName.match(regex)
Isso acontece porque o resultado do match
é esse, caso eu teste Quark isIn:
[ 'is', index: 0, input: 'isInArray' ]
[ 'isIn', index: 0, input: 'isInArray' ]
null
Então se eu NEGAR um array ele irá virar false
então se eu negar novamente ele irá virar o que?
TRUUUUUUUUUUU!!!
Após isso precisamos refatorar a chamada dos testQuark{TYPE}
:
let test = (values, valueToTest) => {
let isQuarkTo = (testName.indexOf('to') > -1);
if(isQuarkTo) testQuarkTo(values, valueToTest, testName, describes);
else testQuarkIs(values, valueToTest, testName);
};
if(describes[0].list) {
const list = describes.splice(0,1)[0].list;
test = (values, valueToTest) => {
testQuarkIsIn(values, valueToTest, list, testName);
};
}
Olhe que malandragem vai rolar agora!!!
let test = (values, valueToTest) => {
if(isQuarkTo) testQuark(values, valueToTest, testName, describes);
else testQuark(values, valueToTest, testName);
};
if(describes[0].list) {
const list = describes.splice(0,1)[0].list;
test = (values, valueToTest) => {
testQuark(values, valueToTest, list, testName);
};
}
Substituimos todas as chamadas dos testes de Quark para uma única função: testQuark
.
Perceba que as funções de testQuark
possuem interfaces diferentes remetendo a mais um princípio do S.O.L.I.D. o Princípio da Segregação da Interface.
O princípio de segregação de interface diz o seguinte: se uma interface começa a engordar, devemos parti-la em diversas novas interfaces de tal modo que cada cliente só conheça aquilo que de fato lhe diz respeito.
fonte: http://www.itexto.net/devkico/?p=1105
Estou citando alguns princípios de S.O.L.I.D. mesmo não estando usando OO clássica, porém estou adaptando-os. Caso você não concorde eu agradeceria se você me explicasse o porquê.
Mas eu ainda não estou satisfeito, podemos refatorar ainda mais!
O que você acha que podemos fazer com esse pedaço de código??
const testTypes = require('./config/testTypesFactory');
let testQuark = null;
let findTest = (element) => {
let regex = new RegExp(element, 'i');
if(!!testName.match(regex)){
testQuark = require('./config/testQuark'+element);
}
};
testTypes.forEach(findTest);
Refatorar!
Então vamos refatorar o código acima para esse:
let testQuark = require('./config/testFactory')(testName);
Sim você não está enganado! É só isso mesmo.
Vamos criar um Factory para nossos testes de Quarks:
module.exports = (testName) => {
let test = null;
let findTest = (element) => {
let regex = new RegExp(element, 'i');
if(!!testName.match(regex)){
test = require('./testQuark'+element);
}
};
require('./testTypesFactory').forEach(findTest);
return test;
}
Lembre como está nosso array de tipos de testes:
['Is', 'IsIn', 'To']
Iremos seguir SEMPRE esse padrão do mais básico, Is
, ao mais complexo.
Agora eu lhe pergunto:
Analisando esse código você consegue perceber o porquê?
Lembra do resultado do match
caso seja Quark isIn?
[ 'is', index: 0, input: 'isInArray' ]
[ 'isIn', index: 0, input: 'isInArray' ]
null
Notou que se tivermos um teste isIn
ele também da o match
com is
?
Porém sabemos que se vier o isIn
é ele que queremos, por isso fizemos assim no Factory:
if(!!testName.match(regex)){
test = require('./testQuark'+element);
}
Dessa forma sempre retornaremos o último teste válido.
Nosso módulo de testes genéricos para os Quarks ficou assim:
'use strict';
const expect = require('chai').expect;
module.exports = (testName, describes) => {
let testQuark = require('./config/testFactory')(testName);
let test = (values, valueToTest) => {
if(isQuarkTo) testQuark(values, valueToTest, testName, describes);
else testQuark(values, valueToTest, testName);
};
if(describes[0].list) {
const list = describes.splice(0,1)[0].list;
test = (values, valueToTest) => {
testQuark(values, valueToTest, list, testName);
};
}
describe(testName, () => {
describes.forEach( (element, index) => {
if(element.type) {
describe(element.message, () => {
test(element.values, element.type);
});
}
else {
describe(element.message, () => {
test(element.values, element.type);
});
}
if(element.list) return true;
});
});
};
Saímos de um módulo de 78 linhas para um de 35!