GithubHelp home page GithubHelp logo

automatictdd's Introduction

AutomaticTDD

Fazer TDD para coisas simples é muitooooo chato, IMHO, por isso sempre tento automatiar o máximo possível.

O Problema

Testar funções de validação é algo muito simples que não deveria despender muito tempo do programador.

**Para qualquer teste de validação atômica sempre teremos apenas 2 casos: **

  • true
  • false

Então por que não podemos automatizar a execução desses testes apenas passando:

  • valores a serem testados
  • função/módulo atômico de validação
  • tipo do teste: true ou false

Por exemplo assim:

const describes = [
  { type: true
  , message: 'é String'
  , values: ['Suissa', '1', '', ' ']
  }
, 
  { type: false
  , message: 'não é String'
  , values: [null, undefined, 1, true, {}, ()=>{}]
  }
];
require('./testModule')('isString', describes);

A Solução

Testes

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 describes:

'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 describes:

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 describes separados.

Podemos fazer da seguinte forma:

  1. itere no array describes
  2. teste o type do describe
  3. crie o describe correto a partir do type
  4. 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ê?

Testes diferentes

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

testModuleGenereic

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 suissatestando: Suissa
valueToTest false
validated itacir
valuesExpected itacirtestando: 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;
    });
  });
};

Mais refatoração

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

Acerto mizeravi

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!

automatictdd's People

Contributors

suissa avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.